Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add suggestions to use-isnan in binary expressions #17996

Merged
merged 13 commits into from Jan 29, 2024
58 changes: 56 additions & 2 deletions lib/rules/use-isnan.js
Expand Up @@ -34,6 +34,7 @@ function isNaNIdentifier(node) {
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
hasSuggestions: true,
type: "problem",

docs: {
Expand Down Expand Up @@ -63,14 +64,45 @@ module.exports = {
comparisonWithNaN: "Use the isNaN function to compare with NaN.",
switchNaN: "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch.",
caseNaN: "'case NaN' can never match. Use Number.isNaN before the switch.",
indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN."
indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN.",
replaceWithIsNaN: "Replace with Number.isNaN.",
replaceWithCastingAndIsNaN: "Replace with Number.isNaN cast to a Number."
}
},

create(context) {

const enforceForSwitchCase = !context.options[0] || context.options[0].enforceForSwitchCase;
const enforceForIndexOf = context.options[0] && context.options[0].enforceForIndexOf;
const sourceCode = context.sourceCode;

const fixableOperators = new Set(["==", "===", "!=", "!=="]);
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.
Expand All @@ -82,7 +114,29 @@ module.exports = {
/^(?:[<>]|[!=]=)=?$/u.test(node.operator) &&
(isNaNIdentifier(node.left) || isNaNIdentifier(node.right))
) {
context.report({ node, messageId: "comparisonWithNaN" });
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",
fix: getBinaryExpressionFixer(node, value => `Number.isNaN(Number(${value}))`)
});
}

context.report({
node,
messageId: "comparisonWithNaN",
suggest: suggestedFixes
});
}
}

Expand Down