eqeqeq.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. /**
  2. * @fileoverview Rule to flag statements that use != and == instead of !== and ===
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("../ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Rule Definition
  12. //------------------------------------------------------------------------------
  13. module.exports = {
  14. meta: {
  15. docs: {
  16. description: "require the use of `===` and `!==`",
  17. category: "Best Practices",
  18. recommended: false,
  19. url: "https://eslint.org/docs/rules/eqeqeq"
  20. },
  21. schema: {
  22. anyOf: [
  23. {
  24. type: "array",
  25. items: [
  26. {
  27. enum: ["always"]
  28. },
  29. {
  30. type: "object",
  31. properties: {
  32. null: {
  33. enum: ["always", "never", "ignore"]
  34. }
  35. },
  36. additionalProperties: false
  37. }
  38. ],
  39. additionalItems: false
  40. },
  41. {
  42. type: "array",
  43. items: [
  44. {
  45. enum: ["smart", "allow-null"]
  46. }
  47. ],
  48. additionalItems: false
  49. }
  50. ]
  51. },
  52. fixable: "code",
  53. messages: {
  54. unexpected: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'."
  55. }
  56. },
  57. create(context) {
  58. const config = context.options[0] || "always";
  59. const options = context.options[1] || {};
  60. const sourceCode = context.getSourceCode();
  61. const nullOption = (config === "always")
  62. ? options.null || "always"
  63. : "ignore";
  64. const enforceRuleForNull = (nullOption === "always");
  65. const enforceInverseRuleForNull = (nullOption === "never");
  66. /**
  67. * Checks if an expression is a typeof expression
  68. * @param {ASTNode} node The node to check
  69. * @returns {boolean} if the node is a typeof expression
  70. */
  71. function isTypeOf(node) {
  72. return node.type === "UnaryExpression" && node.operator === "typeof";
  73. }
  74. /**
  75. * Checks if either operand of a binary expression is a typeof operation
  76. * @param {ASTNode} node The node to check
  77. * @returns {boolean} if one of the operands is typeof
  78. * @private
  79. */
  80. function isTypeOfBinary(node) {
  81. return isTypeOf(node.left) || isTypeOf(node.right);
  82. }
  83. /**
  84. * Checks if operands are literals of the same type (via typeof)
  85. * @param {ASTNode} node The node to check
  86. * @returns {boolean} if operands are of same type
  87. * @private
  88. */
  89. function areLiteralsAndSameType(node) {
  90. return node.left.type === "Literal" && node.right.type === "Literal" &&
  91. typeof node.left.value === typeof node.right.value;
  92. }
  93. /**
  94. * Checks if one of the operands is a literal null
  95. * @param {ASTNode} node The node to check
  96. * @returns {boolean} if operands are null
  97. * @private
  98. */
  99. function isNullCheck(node) {
  100. return astUtils.isNullLiteral(node.right) || astUtils.isNullLiteral(node.left);
  101. }
  102. /**
  103. * Gets the location (line and column) of the binary expression's operator
  104. * @param {ASTNode} node The binary expression node to check
  105. * @param {string} operator The operator to find
  106. * @returns {Object} { line, column } location of operator
  107. * @private
  108. */
  109. function getOperatorLocation(node) {
  110. const opToken = sourceCode.getTokenAfter(node.left);
  111. return { line: opToken.loc.start.line, column: opToken.loc.start.column };
  112. }
  113. /**
  114. * Reports a message for this rule.
  115. * @param {ASTNode} node The binary expression node that was checked
  116. * @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==')
  117. * @returns {void}
  118. * @private
  119. */
  120. function report(node, expectedOperator) {
  121. context.report({
  122. node,
  123. loc: getOperatorLocation(node),
  124. messageId: "unexpected",
  125. data: { expectedOperator, actualOperator: node.operator },
  126. fix(fixer) {
  127. // If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix.
  128. if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {
  129. const operatorToken = sourceCode.getFirstTokenBetween(
  130. node.left,
  131. node.right,
  132. token => token.value === node.operator
  133. );
  134. return fixer.replaceText(operatorToken, expectedOperator);
  135. }
  136. return null;
  137. }
  138. });
  139. }
  140. return {
  141. BinaryExpression(node) {
  142. const isNull = isNullCheck(node);
  143. if (node.operator !== "==" && node.operator !== "!=") {
  144. if (enforceInverseRuleForNull && isNull) {
  145. report(node, node.operator.slice(0, -1));
  146. }
  147. return;
  148. }
  149. if (config === "smart" && (isTypeOfBinary(node) ||
  150. areLiteralsAndSameType(node) || isNull)) {
  151. return;
  152. }
  153. if (!enforceRuleForNull && isNull) {
  154. return;
  155. }
  156. report(node, `${node.operator}=`);
  157. }
  158. };
  159. }
  160. };