/**
							 | 
						|
								 * @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
							 | 
						|
								                        }
							 | 
						|
								                    });
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								        };
							 | 
						|
								    }
							 | 
						|
								};
							 |