Assignment5_new/node_modules/eslint/lib/rules/use-isnan.js

238 lines
8.5 KiB
JavaScript
Raw Normal View History

2025-01-07 05:14:59 +00:00
/**
* @fileoverview Rule to flag comparisons to the value NaN
* @author James Allardice
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Determines if the given node is a NaN `Identifier` node.
* @param {ASTNode|null} node The node to check.
* @returns {boolean} `true` if the node is 'NaN' identifier.
*/
function isNaNIdentifier(node) {
if (!node) {
return false;
}
const nodeToCheck = node.type === "SequenceExpression"
? node.expressions.at(-1)
: node;
return (
astUtils.isSpecificId(nodeToCheck, "NaN") ||
astUtils.isSpecificMemberAccess(nodeToCheck, "Number", "NaN")
);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
hasSuggestions: true,
type: "problem",
docs: {
description: "Require calls to `isNaN()` when checking for `NaN`",
recommended: true,
url: "https://eslint.org/docs/latest/rules/use-isnan"
},
schema: [
{
type: "object",
properties: {
enforceForSwitchCase: {
type: "boolean"
},
enforceForIndexOf: {
type: "boolean"
}
},
additionalProperties: false
}
],
defaultOptions: [{
enforceForIndexOf: false,
enforceForSwitchCase: true
}],
messages: {
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.",
replaceWithIsNaN: "Replace with Number.isNaN.",
replaceWithCastingAndIsNaN: "Replace with Number.isNaN and cast to a Number.",
replaceWithFindIndex: "Replace with Array.prototype.{{ methodName }}."
}
},
create(context) {
const [{ enforceForIndexOf, enforceForSwitchCase }] = context.options;
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.
* @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 NaNNode = isNaNIdentifier(node.left) ? node.left : node.right;
const isSequenceExpression = NaNNode.type === "SequenceExpression";
const isSuggestable = fixableOperators.has(node.operator) && !isSequenceExpression;
const isCastable = castableOperators.has(node.operator);
if (isSuggestable) {
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
});
}
}
/**
* 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" });
}
}
}
/**
* Checks the given `CallExpression` node for `.indexOf(NaN)` and `.lastIndexOf(NaN)`.
* @param {ASTNode} node The node to check.
* @returns {void}
*/
function checkCallExpression(node) {
const callee = astUtils.skipChainExpression(node.callee);
if (callee.type === "MemberExpression") {
const methodName = astUtils.getStaticPropertyName(callee);
if (
(methodName === "indexOf" || methodName === "lastIndexOf") &&
node.arguments.length <= 2 &&
isNaNIdentifier(node.arguments[0])
) {
/*
* 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" && !node.arguments[1];
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
});
}
}
}
const listeners = {
BinaryExpression: checkBinaryExpression
};
if (enforceForSwitchCase) {
listeners.SwitchStatement = checkSwitchStatement;
}
if (enforceForIndexOf) {
listeners.CallExpression = checkCallExpression;
}
return listeners;
}
};