eqeqeq.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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. /** @type {import('../types').Rule.RuleModule} */
  14. module.exports = {
  15. meta: {
  16. type: "suggestion",
  17. hasSuggestions: true,
  18. docs: {
  19. description: "Require the use of `===` and `!==`",
  20. recommended: false,
  21. url: "https://eslint.org/docs/latest/rules/eqeqeq",
  22. },
  23. schema: {
  24. anyOf: [
  25. {
  26. type: "array",
  27. items: [
  28. {
  29. enum: ["always"],
  30. },
  31. {
  32. type: "object",
  33. properties: {
  34. null: {
  35. enum: ["always", "never", "ignore"],
  36. },
  37. },
  38. additionalProperties: false,
  39. },
  40. ],
  41. additionalItems: false,
  42. },
  43. {
  44. type: "array",
  45. items: [
  46. {
  47. enum: ["smart", "allow-null"],
  48. },
  49. ],
  50. additionalItems: false,
  51. },
  52. ],
  53. },
  54. fixable: "code",
  55. messages: {
  56. unexpected:
  57. "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'.",
  58. replaceOperator:
  59. "Use '{{expectedOperator}}' instead of '{{actualOperator}}'.",
  60. },
  61. },
  62. create(context) {
  63. const config = context.options[0] || "always";
  64. const options = context.options[1] || {};
  65. const sourceCode = context.sourceCode;
  66. const nullOption =
  67. config === "always" ? options.null || "always" : "ignore";
  68. const enforceRuleForNull = nullOption === "always";
  69. const enforceInverseRuleForNull = nullOption === "never";
  70. /**
  71. * Checks if an expression is a typeof expression
  72. * @param {ASTNode} node The node to check
  73. * @returns {boolean} if the node is a typeof expression
  74. */
  75. function isTypeOf(node) {
  76. return (
  77. node.type === "UnaryExpression" && node.operator === "typeof"
  78. );
  79. }
  80. /**
  81. * Checks if either operand of a binary expression is a typeof operation
  82. * @param {ASTNode} node The node to check
  83. * @returns {boolean} if one of the operands is typeof
  84. * @private
  85. */
  86. function isTypeOfBinary(node) {
  87. return isTypeOf(node.left) || isTypeOf(node.right);
  88. }
  89. /**
  90. * Checks if operands are literals of the same type (via typeof)
  91. * @param {ASTNode} node The node to check
  92. * @returns {boolean} if operands are of same type
  93. * @private
  94. */
  95. function areLiteralsAndSameType(node) {
  96. return (
  97. node.left.type === "Literal" &&
  98. node.right.type === "Literal" &&
  99. typeof node.left.value === typeof node.right.value
  100. );
  101. }
  102. /**
  103. * Checks if one of the operands is a literal null
  104. * @param {ASTNode} node The node to check
  105. * @returns {boolean} if operands are null
  106. * @private
  107. */
  108. function isNullCheck(node) {
  109. return (
  110. astUtils.isNullLiteral(node.right) ||
  111. astUtils.isNullLiteral(node.left)
  112. );
  113. }
  114. /**
  115. * Reports a message for this rule.
  116. * @param {ASTNode} node The binary expression node that was checked
  117. * @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==')
  118. * @returns {void}
  119. * @private
  120. */
  121. function report(node, expectedOperator) {
  122. const operatorToken = sourceCode.getFirstTokenBetween(
  123. node.left,
  124. node.right,
  125. token => token.value === node.operator,
  126. );
  127. const commonReportParams = {
  128. node,
  129. loc: operatorToken.loc,
  130. messageId: "unexpected",
  131. data: { expectedOperator, actualOperator: node.operator },
  132. };
  133. if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {
  134. context.report({
  135. ...commonReportParams,
  136. fix(fixer) {
  137. return fixer.replaceText(
  138. operatorToken,
  139. expectedOperator,
  140. );
  141. },
  142. });
  143. } else {
  144. context.report({
  145. ...commonReportParams,
  146. suggest: [
  147. {
  148. messageId: "replaceOperator",
  149. data: {
  150. expectedOperator,
  151. actualOperator: node.operator,
  152. },
  153. fix: fixer =>
  154. fixer.replaceText(
  155. operatorToken,
  156. expectedOperator,
  157. ),
  158. },
  159. ],
  160. });
  161. }
  162. }
  163. return {
  164. BinaryExpression(node) {
  165. const isNull = isNullCheck(node);
  166. if (node.operator !== "==" && node.operator !== "!=") {
  167. if (enforceInverseRuleForNull && isNull) {
  168. report(node, node.operator.slice(0, -1));
  169. }
  170. return;
  171. }
  172. if (
  173. config === "smart" &&
  174. (isTypeOfBinary(node) ||
  175. areLiteralsAndSameType(node) ||
  176. isNull)
  177. ) {
  178. return;
  179. }
  180. if (!enforceRuleForNull && isNull) {
  181. return;
  182. }
  183. report(node, `${node.operator}=`);
  184. },
  185. };
  186. },
  187. };