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