Repository: eslint/eslint
File: lib/rules/use-isnan.js (JavaScript)

Code before:
         * Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons.
         * @param {ASTNode} node The node to check.
         * @returns {void}
         */
        function checkBinaryExpression(node) {
            if (
                /^(?:[<>]|[!=]=)=?$/u.test(node.operator) &&
                (isNaNIdentifier(node.left) || isNaNIdentifier(node.right))
            ) {
                const isFixable = fixableOperators.has(node.operator);

                if (isFixable) {
                    context.report({
                        node,
                        messageId: "comparisonWithNaN",
                        suggest: [
                            {
                                messageId: "replaceWithIsNaN",
                                fix(fixer) {
                                    const comparedValue = isNaNIdentifier(node.left) ? node.right : node.left;
                                    const shouldNegate = node.operator[0] === "!";

                                    const negation = shouldNegate ? "!" : "";
                                    const comparedValueText = sourceCode.getText(comparedValue);

                                    return fixer.replaceText(node, `${negation}Number.isNaN(${comparedValueText})`);
                                }
                            }
                        ]
                    });
                } else {
                    context.report({ node, messageId: "comparisonWithNaN" });
                }
            }
        }

        /**
         * Checks the discriminant and all case clauses of the given `SwitchStatement` node for `switch(NaN)` and `case NaN:`
         * @param {ASTNode} node The node to check.
         * @returns {void}
         */
        function checkSwitchStatement(node) {
            if (isNaNIdentifier(node.discriminant)) {
                context.report({ node, messageId: "switchNaN" });
            }

            for (const switchCase of node.cases) {
                if (isNaNIdentifier(switchCase.test)) {
                    context.report({ node: switchCase, messageId: "caseNaN" });
                }
            }

Review comment (bug):
If `comparedValue` is a SequenceExpression, we should wrap it in parentheses.

For example:

```js
(foo, bar) === NaN // looks like intent to check if `bar` is NaN
```

Current suggestion:

```js
Number.isNaN(foo, bar) // incorrect, this evaluates to Number.isNaN(foo)  
```

It should be:

```js
Number.isNaN((foo, bar)) // correct, this evaluates to Number.isNaN(bar)  
```

An example of how this could be implemented:

https://github.com/eslint/eslint/blob/1fedfd28a46d86b2fbcf06a2328befafd6535a88/lib/rules/no-prototype-builtins.js#L132-L134



Code after:
        const castableOperators = new Set(["==", "!="]);

        /**
         * Get a fixer for a binary expression that compares to NaN.
         * @param  {ASTNode} node The node to fix.
         * @param {function(string): string} wrapValue A function that wraps the compared value with a fix.
         * @returns {function(Fixer): Fix} The fixer function.
         */
        function getBinaryExpressionFixer(node, wrapValue) {
            return fixer => {
                const comparedValue = isNaNIdentifier(node.left) ? node.right : node.left;
                const shouldWrap = comparedValue.type === "SequenceExpression";
                const shouldNegate = node.operator[0] === "!";

                const negation = shouldNegate ? "!" : "";
                let comparedValueText = sourceCode.getText(comparedValue);

                if (shouldWrap) {
                    comparedValueText = `(${comparedValueText})`;
                }

                const fixedValue = wrapValue(comparedValueText);

                return fixer.replaceText(node, `${negation}${fixedValue}`);
            };
        }

        /**
         * Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons.
         * @param {ASTNode} node The node to check.
         * @returns {void}
         */
        function checkBinaryExpression(node) {
            if (
                /^(?:[<>]|[!=]=)=?$/u.test(node.operator) &&
                (isNaNIdentifier(node.left) || isNaNIdentifier(node.right))
            ) {
                const suggestedFixes = [];
                const isFixable = fixableOperators.has(node.operator);
                const isCastable = castableOperators.has(node.operator);

                if (isFixable) {
                    suggestedFixes.push({
                        messageId: "replaceWithIsNaN",
                        fix: getBinaryExpressionFixer(node, value => `Number.isNaN(${value})`)
                    });
                }

                if (isCastable) {
                    suggestedFixes.push({
                        messageId: "replaceWithCastingAndIsNaN",