use-isnan.js 4.6 KB

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