• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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