eqeqeq.js 5.6 KB

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