Skip to content

Commit

Permalink
feat: add suggestions to use-isnan in indexOf & lastIndexOf cal…
Browse files Browse the repository at this point in the history
…ls (#18063)

* feat: add suggestions to `use-isnan` in `indexOf` & `lastIndexOf` calls

Closes #17978

* wip

* wip

* shorten names

* fix text

* use const

* wip

* isSuggestable

* computed
  • Loading branch information
StyleShit committed Feb 14, 2024
1 parent 1a65d3e commit 74124c2
Show file tree
Hide file tree
Showing 2 changed files with 241 additions and 26 deletions.
37 changes: 33 additions & 4 deletions lib/rules/use-isnan.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ module.exports = {
caseNaN: "'case NaN' can never match. Use Number.isNaN before the switch.",
indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN.",
replaceWithIsNaN: "Replace with Number.isNaN.",
replaceWithCastingAndIsNaN: "Replace with Number.isNaN cast to a Number."
replaceWithCastingAndIsNaN: "Replace with Number.isNaN and cast to a Number.",
replaceWithFindIndex: "Replace with Array.prototype.{{ methodName }}."
}
},

Expand Down Expand Up @@ -126,10 +127,10 @@ module.exports = {
const NaNNode = isNaNIdentifier(node.left) ? node.left : node.right;

const isSequenceExpression = NaNNode.type === "SequenceExpression";
const isFixable = fixableOperators.has(node.operator) && !isSequenceExpression;
const isSuggestable = fixableOperators.has(node.operator) && !isSequenceExpression;
const isCastable = castableOperators.has(node.operator);

if (isFixable) {
if (isSuggestable) {
suggestedFixes.push({
messageId: "replaceWithIsNaN",
fix: getBinaryExpressionFixer(node, value => `Number.isNaN(${value})`)
Expand Down Expand Up @@ -184,7 +185,35 @@ module.exports = {
node.arguments.length === 1 &&
isNaNIdentifier(node.arguments[0])
) {
context.report({ node, messageId: "indexOfNaN", data: { methodName } });

/*
* To retain side effects, it's essential to address `NaN` beforehand, which
* is not possible with fixes like `arr.findIndex(Number.isNaN)`.
*/
const isSuggestable = node.arguments[0].type !== "SequenceExpression";
const suggestedFixes = [];

if (isSuggestable) {
const shouldWrap = callee.computed;
const findIndexMethod = methodName === "indexOf" ? "findIndex" : "findLastIndex";
const propertyName = shouldWrap ? `"${findIndexMethod}"` : findIndexMethod;

suggestedFixes.push({
messageId: "replaceWithFindIndex",
data: { methodName: findIndexMethod },
fix: fixer => [
fixer.replaceText(callee.property, propertyName),
fixer.replaceText(node.arguments[0], "Number.isNaN")
]
});
}

context.report({
node,
messageId: "indexOfNaN",
data: { methodName },
suggest: suggestedFixes
});
}
}
}
Expand Down
230 changes: 208 additions & 22 deletions tests/lib/rules/use-isnan.js
Original file line number Diff line number Diff line change
Expand Up @@ -987,122 +987,308 @@ ruleTester.run("use-isnan", rule, {
{
code: "foo.indexOf(NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: "foo.findIndex(Number.isNaN)"
}]
}]
},
{
code: "foo.lastIndexOf(NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "lastIndexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findLastIndex" },
output: "foo.findLastIndex(Number.isNaN)"
}]
}]
},
{
code: "foo['indexOf'](NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: 'foo["findIndex"](Number.isNaN)'
}]
}]
},
{
code: "foo[`indexOf`](NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: 'foo["findIndex"](Number.isNaN)'
}]
}]
},
{
code: "foo['lastIndexOf'](NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "lastIndexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findLastIndex" },
output: 'foo["findLastIndex"](Number.isNaN)'
}]
}]
},
{
code: "foo().indexOf(NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: "foo().findIndex(Number.isNaN)"
}]
}]
},
{
code: "foo.bar.lastIndexOf(NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "lastIndexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findLastIndex" },
output: "foo.bar.findLastIndex(Number.isNaN)"
}]
}]
},
{
code: "foo.indexOf?.(NaN)",
options: [{ enforceForIndexOf: true }],
languageOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: "foo.findIndex?.(Number.isNaN)"
}]
}]
},
{
code: "foo?.indexOf(NaN)",
options: [{ enforceForIndexOf: true }],
languageOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: "foo?.findIndex(Number.isNaN)"
}]
}]
},
{
code: "(foo?.indexOf)(NaN)",
options: [{ enforceForIndexOf: true }],
languageOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: "(foo?.findIndex)(Number.isNaN)"
}]
}]
},
{
code: "foo.indexOf(Number.NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: "foo.findIndex(Number.isNaN)"
}]
}]
},
{
code: "foo.lastIndexOf(Number.NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "lastIndexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findLastIndex" },
output: "foo.findLastIndex(Number.isNaN)"
}]
}]
},
{
code: "foo['indexOf'](Number.NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: 'foo["findIndex"](Number.isNaN)'
}]
}]
},
{
code: "foo['lastIndexOf'](Number.NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "lastIndexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findLastIndex" },
output: 'foo["findLastIndex"](Number.isNaN)'
}]
}]
},
{
code: "foo().indexOf(Number.NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: "foo().findIndex(Number.isNaN)"
}]
}]
},
{
code: "foo.bar.lastIndexOf(Number.NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
errors: [{
messageId: "indexOfNaN",
type: "CallExpression",
data: { methodName: "lastIndexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findLastIndex" },
output: "foo.bar.findLastIndex(Number.isNaN)"
}]
}]
},
{
code: "foo.indexOf?.(Number.NaN)",
options: [{ enforceForIndexOf: true }],
languageOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: "foo.findIndex?.(Number.isNaN)"
}]
}]
},
{
code: "foo?.indexOf(Number.NaN)",
options: [{ enforceForIndexOf: true }],
languageOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: "foo?.findIndex(Number.isNaN)"
}]
}]
},
{
code: "(foo?.indexOf)(Number.NaN)",
options: [{ enforceForIndexOf: true }],
languageOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
data: { methodName: "indexOf" },
suggestions: [{
messageId: "replaceWithFindIndex",
data: { methodName: "findIndex" },
output: "(foo?.findIndex)(Number.isNaN)"
}]
}]
},
{
code: "foo.indexOf((1, NaN))",
options: [{ enforceForIndexOf: true }],
languageOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
data: { methodName: "indexOf" },
suggestions: []
}]
},
{
code: "foo.indexOf((1, Number.NaN))",
options: [{ enforceForIndexOf: true }],
languageOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
errors: [{
messageId: "indexOfNaN",
data: { methodName: "indexOf" },
suggestions: []
}]
},
{
code: "foo.lastIndexOf((1, NaN))",
options: [{ enforceForIndexOf: true }],
languageOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "indexOfNaN", data: { methodName: "lastIndexOf" } }]
errors: [{
messageId: "indexOfNaN",
data: { methodName: "lastIndexOf" },
suggestions: []
}]
},
{
code: "foo.lastIndexOf((1, Number.NaN))",
options: [{ enforceForIndexOf: true }],
languageOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "indexOfNaN", data: { methodName: "lastIndexOf" } }]
errors: [{
messageId: "indexOfNaN",
data: { methodName: "lastIndexOf" },
suggestions: []
}]
}
]
});

0 comments on commit 74124c2

Please sign in to comment.