blob: b7b296f45155ff7d57f291818e2ef04baf478461 [file] [log] [blame]
/**
* @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";
/**
* 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 numbers.
* - 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" && typeof radix.value !== "number") ||
(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"
},
schema: [
{
enum: ["always", "as-needed"]
}
]
},
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,
message: "Missing parameters."
});
break;
case 1:
if (mode === MODE_ALWAYS) {
context.report({
node,
message: "Missing radix parameter."
});
}
break;
default:
if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) {
context.report({
node,
message: "Redundant radix parameter."
});
} else if (!isValidRadix(args[1])) {
context.report({
node,
message: "Invalid radix parameter."
});
}
break;
}
}
return {
"Program:exit"() {
const scope = context.getScope();
let variable;
// Check `parseInt()`
variable = astUtils.getVariableByName(scope, "parseInt");
if (!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 (!isShadowed(variable)) {
variable.references.forEach(reference => {
const node = reference.identifier.parent;
if (isParseIntMethod(node) && astUtils.isCallee(node)) {
checkArguments(node.parent);
}
});
}
}
};
}
};