|                                                                                                                                                                                                      |  | /** * @fileoverview Rule to flag use of parseInt without a radix argument * @author James Allardice */
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const MODE_ALWAYS = "always",    MODE_AS_NEEDED = "as-needed";
const validRadixValues = new Set(Array.from({ length: 37 - 2 }, (_, index) => index + 2));
/** * Checks whether a given variable is shadowed or not. * @param {eslint-scope.Variable} variable A variable to check. * @returns {boolean} `true` if the variable is shadowed. */function isShadowed(variable) {    return variable.defs.length >= 1;}
/** * Checks whether a given node is a MemberExpression of `parseInt` method or not. * @param {ASTNode} node A node to check. * @returns {boolean} `true` if the node is a MemberExpression of `parseInt` *      method. */function isParseIntMethod(node) {    return (        node.type === "MemberExpression" &&        !node.computed &&        node.property.type === "Identifier" &&        node.property.name === "parseInt"    );}
/** * Checks whether a given node is a valid value of radix or not. * * The following values are invalid. * * - A literal except integers between 2 and 36. * - undefined. * @param {ASTNode} radix A node of radix to check. * @returns {boolean} `true` if the node is valid. */function isValidRadix(radix) {    return !(        (radix.type === "Literal" && !validRadixValues.has(radix.value)) ||        (radix.type === "Identifier" && radix.name === "undefined")    );}
/** * Checks whether a given node is a default value of radix or not. * @param {ASTNode} radix A node of radix to check. * @returns {boolean} `true` if the node is the literal node of `10`. */function isDefaultRadix(radix) {    return radix.type === "Literal" && radix.value === 10;}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",
        docs: {            description: "Enforce the consistent use of the radix argument when using `parseInt()`",            recommended: false,            url: "https://eslint.org/docs/latest/rules/radix"        },
        hasSuggestions: true,
        schema: [            {                enum: ["always", "as-needed"]            }        ],
        messages: {            missingParameters: "Missing parameters.",            redundantRadix: "Redundant radix parameter.",            missingRadix: "Missing radix parameter.",            invalidRadix: "Invalid radix parameter, must be an integer between 2 and 36.",            addRadixParameter10: "Add radix parameter `10` for parsing decimal numbers."        }    },
    create(context) {        const mode = context.options[0] || MODE_ALWAYS;        const sourceCode = context.sourceCode;
        /**         * Checks the arguments of a given CallExpression node and reports it if it         * offends this rule.         * @param {ASTNode} node A CallExpression node to check.         * @returns {void}         */        function checkArguments(node) {            const args = node.arguments;
            switch (args.length) {                case 0:                    context.report({                        node,                        messageId: "missingParameters"                    });                    break;
                case 1:                    if (mode === MODE_ALWAYS) {                        context.report({                            node,                            messageId: "missingRadix",                            suggest: [                                {                                    messageId: "addRadixParameter10",                                    fix(fixer) {                                        const tokens = sourceCode.getTokens(node);                                        const lastToken = tokens[tokens.length - 1]; // Parenthesis.
                                        const secondToLastToken = tokens[tokens.length - 2]; // May or may not be a comma.
                                        const hasTrailingComma = secondToLastToken.type === "Punctuator" && secondToLastToken.value === ",";
                                        return fixer.insertTextBefore(lastToken, hasTrailingComma ? " 10," : ", 10");                                    }                                }                            ]                        });                    }                    break;
                default:                    if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) {                        context.report({                            node,                            messageId: "redundantRadix"                        });                    } else if (!isValidRadix(args[1])) {                        context.report({                            node,                            messageId: "invalidRadix"                        });                    }                    break;            }        }
        return {            "Program:exit"(node) {                const scope = sourceCode.getScope(node);                let variable;
                // Check `parseInt()`
                variable = astUtils.getVariableByName(scope, "parseInt");                if (variable && !isShadowed(variable)) {                    variable.references.forEach(reference => {                        const idNode = reference.identifier;
                        if (astUtils.isCallee(idNode)) {                            checkArguments(idNode.parent);                        }                    });                }
                // Check `Number.parseInt()`
                variable = astUtils.getVariableByName(scope, "Number");                if (variable && !isShadowed(variable)) {                    variable.references.forEach(reference => {                        const parentNode = reference.identifier.parent;                        const maybeCallee = parentNode.parent.type === "ChainExpression"                            ? parentNode.parent                            : parentNode;
                        if (isParseIntMethod(parentNode) && astUtils.isCallee(maybeCallee)) {                            checkArguments(maybeCallee.parent);                        }                    });                }            }        };    }};
 |