Skip to content

Commit

Permalink
feat: add suggestions to use-isnan in binary expressions (#17996)
Browse files Browse the repository at this point in the history
* feat: add suggestions to `use-isnan` in binary expressions

* remove `>` `<` from fixes

* move the array up

* use Set

* wip

* small refactor

* remove empty line

* add multiple suggestions

* change wording

* suggest casting only in `==` and `!=`
  • Loading branch information
StyleShit committed Jan 29, 2024
1 parent c25b4af commit 2d11d46
Show file tree
Hide file tree
Showing 2 changed files with 353 additions and 36 deletions.
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

0 comments on commit 2d11d46

Please sign in to comment.