An error occurred while loading the file. Please try again.
radix.js 6.73 KiB
/**
 * @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 //------------------------------------------------------------------------------ module.exports = { meta: { type: "suggestion", docs: { description: "enforce the consistent use of the radix argument when using `parseInt()`", category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/radix", suggestion: 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; /** * 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 sourceCode = context.getSourceCode(); 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"() { const scope = context.getScope(); let variable; // Check `parseInt()` variable = astUtils.getVariableByName(scope, "parseInt"); if (variable && !isShadowed(variable)) { variable.references.forEach(reference => { const node = reference.identifier; if (astUtils.isCallee(node)) { checkArguments(node.parent); } }); } // Check `Number.parseInt()` variable = astUtils.getVariableByName(scope, "Number"); if (variable && !isShadowed(variable)) { variable.references.forEach(reference => { const node = reference.identifier.parent; const maybeCallee = node.parent.type === "ChainExpression" ? node.parent : node; if (isParseIntMethod(node) && astUtils.isCallee(maybeCallee)) { checkArguments(maybeCallee.parent); } }); } } }; } };