|                                                                                                                                                                                                                                                   |  | /** * @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js)
 * @author Vincent Lemeunier */
"use strict";
const astUtils = require("./utils/ast-utils");
// Maximum array length by the ECMAScript Specification.
const MAX_ARRAY_LENGTH = 2 ** 32 - 1;
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** * Convert the value to bigint if it's a string. Otherwise return the value as-is. * @param {bigint|number|string} x The value to normalize. * @returns {bigint|number} The normalized value. */function normalizeIgnoreValue(x) {    if (typeof x === "string") {        return BigInt(x.slice(0, -1));    }    return x;}
/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",
        docs: {            description: "Disallow magic numbers",            recommended: false,            url: "https://eslint.org/docs/latest/rules/no-magic-numbers"        },
        schema: [{            type: "object",            properties: {                detectObjects: {                    type: "boolean",                    default: false                },                enforceConst: {                    type: "boolean",                    default: false                },                ignore: {                    type: "array",                    items: {                        anyOf: [                            { type: "number" },                            { type: "string", pattern: "^[+-]?(?:0|[1-9][0-9]*)n$" }                        ]                    },                    uniqueItems: true                },                ignoreArrayIndexes: {                    type: "boolean",                    default: false                },                ignoreDefaultValues: {                    type: "boolean",                    default: false                },                ignoreClassFieldInitialValues: {                    type: "boolean",                    default: false                }            },            additionalProperties: false        }],
        messages: {            useConst: "Number constants declarations must use 'const'.",            noMagic: "No magic number: {{raw}}."        }    },
    create(context) {        const config = context.options[0] || {},            detectObjects = !!config.detectObjects,            enforceConst = !!config.enforceConst,            ignore = new Set((config.ignore || []).map(normalizeIgnoreValue)),            ignoreArrayIndexes = !!config.ignoreArrayIndexes,            ignoreDefaultValues = !!config.ignoreDefaultValues,            ignoreClassFieldInitialValues = !!config.ignoreClassFieldInitialValues;
        const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
        /**         * Returns whether the rule is configured to ignore the given value         * @param {bigint|number} value The value to check         * @returns {boolean} true if the value is ignored         */        function isIgnoredValue(value) {            return ignore.has(value);        }
        /**         * Returns whether the number is a default value assignment.         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node         * @returns {boolean} true if the number is a default value         */        function isDefaultValue(fullNumberNode) {            const parent = fullNumberNode.parent;
            return parent.type === "AssignmentPattern" && parent.right === fullNumberNode;        }
        /**         * Returns whether the number is the initial value of a class field.         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node         * @returns {boolean} true if the number is the initial value of a class field.         */        function isClassFieldInitialValue(fullNumberNode) {            const parent = fullNumberNode.parent;
            return parent.type === "PropertyDefinition" && parent.value === fullNumberNode;        }
        /**         * Returns whether the given node is used as a radix within parseInt() or Number.parseInt()         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node         * @returns {boolean} true if the node is radix         */        function isParseIntRadix(fullNumberNode) {            const parent = fullNumberNode.parent;
            return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] &&                (                    astUtils.isSpecificId(parent.callee, "parseInt") ||                    astUtils.isSpecificMemberAccess(parent.callee, "Number", "parseInt")                );        }
        /**         * Returns whether the given node is a direct child of a JSX node.         * In particular, it aims to detect numbers used as prop values in JSX tags.         * Example: <input maxLength={10} />         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node         * @returns {boolean} true if the node is a JSX number         */        function isJSXNumber(fullNumberNode) {            return fullNumberNode.parent.type.indexOf("JSX") === 0;        }
        /**         * Returns whether the given node is used as an array index.         * Value must coerce to a valid array index name: "0", "1", "2" ... "4294967294".         *         * All other values, like "-1", "2.5", or "4294967295", are just "normal" object properties,         * which can be created and accessed on an array in addition to the array index properties,         * but they don't affect array's length and are not considered by methods such as .map(), .forEach() etc.         *         * The maximum array length by the specification is 2 ** 32 - 1 = 4294967295,         * thus the maximum valid index is 2 ** 32 - 2 = 4294967294.         *         * All notations are allowed, as long as the value coerces to one of "0", "1", "2" ... "4294967294".         *         * Valid examples:         * a[0], a[1], a[1.2e1], a[0xAB], a[0n], a[1n]         * a[-0] (same as a[0] because -0 coerces to "0")         * a[-0n] (-0n evaluates to 0n)         *         * Invalid examples:         * a[-1], a[-0xAB], a[-1n], a[2.5], a[1.23e1], a[12e-1]         * a[4294967295] (above the max index, it's an access to a regular property a["4294967295"])         * a[999999999999999999999] (even if it wasn't above the max index, it would be a["1e+21"])         * a[1e310] (same as a["Infinity"])         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node         * @param {bigint|number} value Value expressed by the fullNumberNode         * @returns {boolean} true if the node is a valid array index         */        function isArrayIndex(fullNumberNode, value) {            const parent = fullNumberNode.parent;
            return parent.type === "MemberExpression" && parent.property === fullNumberNode &&                (Number.isInteger(value) || typeof value === "bigint") &&                value >= 0 && value < MAX_ARRAY_LENGTH;        }
        return {            Literal(node) {                if (!astUtils.isNumericLiteral(node)) {                    return;                }
                let fullNumberNode;                let value;                let raw;
                // Treat unary minus as a part of the number
                if (node.parent.type === "UnaryExpression" && node.parent.operator === "-") {                    fullNumberNode = node.parent;                    value = -node.value;                    raw = `-${node.raw}`;                } else {                    fullNumberNode = node;                    value = node.value;                    raw = node.raw;                }
                const parent = fullNumberNode.parent;
                // Always allow radix arguments and JSX props
                if (                    isIgnoredValue(value) ||                    (ignoreDefaultValues && isDefaultValue(fullNumberNode)) ||                    (ignoreClassFieldInitialValues && isClassFieldInitialValue(fullNumberNode)) ||                    isParseIntRadix(fullNumberNode) ||                    isJSXNumber(fullNumberNode) ||                    (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value))                ) {                    return;                }
                if (parent.type === "VariableDeclarator") {                    if (enforceConst && parent.parent.kind !== "const") {                        context.report({                            node: fullNumberNode,                            messageId: "useConst"                        });                    }                } else if (                    !okTypes.includes(parent.type) ||                    (parent.type === "AssignmentExpression" && parent.left.type === "Identifier")                ) {                    context.report({                        node: fullNumberNode,                        messageId: "noMagic",                        data: {                            raw                        }                    });                }            }        };    }};
 |