use-isnan.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /**
  2. * @fileoverview Rule to flag comparisons to the value NaN
  3. * @author James Allardice
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. /**
  14. * Determines if the given node is a NaN `Identifier` node.
  15. * @param {ASTNode|null} node The node to check.
  16. * @returns {boolean} `true` if the node is 'NaN' identifier.
  17. */
  18. function isNaNIdentifier(node) {
  19. return Boolean(node) && (
  20. astUtils.isSpecificId(node, "NaN") ||
  21. astUtils.isSpecificMemberAccess(node, "Number", "NaN")
  22. );
  23. }
  24. //------------------------------------------------------------------------------
  25. // Rule Definition
  26. //------------------------------------------------------------------------------
  27. /** @type {import('../shared/types').Rule} */
  28. module.exports = {
  29. meta: {
  30. type: "problem",
  31. docs: {
  32. description: "Require calls to `isNaN()` when checking for `NaN`",
  33. recommended: true,
  34. url: "https://eslint.org/docs/rules/use-isnan"
  35. },
  36. schema: [
  37. {
  38. type: "object",
  39. properties: {
  40. enforceForSwitchCase: {
  41. type: "boolean",
  42. default: true
  43. },
  44. enforceForIndexOf: {
  45. type: "boolean",
  46. default: false
  47. }
  48. },
  49. additionalProperties: false
  50. }
  51. ],
  52. messages: {
  53. comparisonWithNaN: "Use the isNaN function to compare with NaN.",
  54. switchNaN: "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch.",
  55. caseNaN: "'case NaN' can never match. Use Number.isNaN before the switch.",
  56. indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN."
  57. }
  58. },
  59. create(context) {
  60. const enforceForSwitchCase = !context.options[0] || context.options[0].enforceForSwitchCase;
  61. const enforceForIndexOf = context.options[0] && context.options[0].enforceForIndexOf;
  62. /**
  63. * Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons.
  64. * @param {ASTNode} node The node to check.
  65. * @returns {void}
  66. */
  67. function checkBinaryExpression(node) {
  68. if (
  69. /^(?:[<>]|[!=]=)=?$/u.test(node.operator) &&
  70. (isNaNIdentifier(node.left) || isNaNIdentifier(node.right))
  71. ) {
  72. context.report({ node, messageId: "comparisonWithNaN" });
  73. }
  74. }
  75. /**
  76. * Checks the discriminant and all case clauses of the given `SwitchStatement` node for `switch(NaN)` and `case NaN:`
  77. * @param {ASTNode} node The node to check.
  78. * @returns {void}
  79. */
  80. function checkSwitchStatement(node) {
  81. if (isNaNIdentifier(node.discriminant)) {
  82. context.report({ node, messageId: "switchNaN" });
  83. }
  84. for (const switchCase of node.cases) {
  85. if (isNaNIdentifier(switchCase.test)) {
  86. context.report({ node: switchCase, messageId: "caseNaN" });
  87. }
  88. }
  89. }
  90. /**
  91. * Checks the given `CallExpression` node for `.indexOf(NaN)` and `.lastIndexOf(NaN)`.
  92. * @param {ASTNode} node The node to check.
  93. * @returns {void}
  94. */
  95. function checkCallExpression(node) {
  96. const callee = astUtils.skipChainExpression(node.callee);
  97. if (callee.type === "MemberExpression") {
  98. const methodName = astUtils.getStaticPropertyName(callee);
  99. if (
  100. (methodName === "indexOf" || methodName === "lastIndexOf") &&
  101. node.arguments.length === 1 &&
  102. isNaNIdentifier(node.arguments[0])
  103. ) {
  104. context.report({ node, messageId: "indexOfNaN", data: { methodName } });
  105. }
  106. }
  107. }
  108. const listeners = {
  109. BinaryExpression: checkBinaryExpression
  110. };
  111. if (enforceForSwitchCase) {
  112. listeners.SwitchStatement = checkSwitchStatement;
  113. }
  114. if (enforceForIndexOf) {
  115. listeners.CallExpression = checkCallExpression;
  116. }
  117. return listeners;
  118. }
  119. };