UNPKG

4.67 kBJavaScriptView Raw
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 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
\No newline at end of file