UNPKG

5.82 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag use of parseInt without a radix argument
3 * @author James Allardice
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
13
14//------------------------------------------------------------------------------
15// Helpers
16//------------------------------------------------------------------------------
17
18const MODE_ALWAYS = "always",
19 MODE_AS_NEEDED = "as-needed";
20
21const validRadixValues = new Set(Array.from({ length: 37 - 2 }, (_, index) => index + 2));
22
23/**
24 * Checks whether a given variable is shadowed or not.
25 * @param {eslint-scope.Variable} variable A variable to check.
26 * @returns {boolean} `true` if the variable is shadowed.
27 */
28function isShadowed(variable) {
29 return variable.defs.length >= 1;
30}
31
32/**
33 * Checks whether a given node is a MemberExpression of `parseInt` method or not.
34 * @param {ASTNode} node A node to check.
35 * @returns {boolean} `true` if the node is a MemberExpression of `parseInt`
36 * method.
37 */
38function isParseIntMethod(node) {
39 return (
40 node.type === "MemberExpression" &&
41 !node.computed &&
42 node.property.type === "Identifier" &&
43 node.property.name === "parseInt"
44 );
45}
46
47/**
48 * Checks whether a given node is a valid value of radix or not.
49 *
50 * The following values are invalid.
51 *
52 * - A literal except integers between 2 and 36.
53 * - undefined.
54 * @param {ASTNode} radix A node of radix to check.
55 * @returns {boolean} `true` if the node is valid.
56 */
57function isValidRadix(radix) {
58 return !(
59 (radix.type === "Literal" && !validRadixValues.has(radix.value)) ||
60 (radix.type === "Identifier" && radix.name === "undefined")
61 );
62}
63
64/**
65 * Checks whether a given node is a default value of radix or not.
66 * @param {ASTNode} radix A node of radix to check.
67 * @returns {boolean} `true` if the node is the literal node of `10`.
68 */
69function isDefaultRadix(radix) {
70 return radix.type === "Literal" && radix.value === 10;
71}
72
73//------------------------------------------------------------------------------
74// Rule Definition
75//------------------------------------------------------------------------------
76
77module.exports = {
78 meta: {
79 type: "suggestion",
80
81 docs: {
82 description: "enforce the consistent use of the radix argument when using `parseInt()`",
83 category: "Best Practices",
84 recommended: false,
85 url: "https://eslint.org/docs/rules/radix"
86 },
87
88 schema: [
89 {
90 enum: ["always", "as-needed"]
91 }
92 ],
93
94 messages: {
95 missingParameters: "Missing parameters.",
96 redundantRadix: "Redundant radix parameter.",
97 missingRadix: "Missing radix parameter.",
98 invalidRadix: "Invalid radix parameter, must be an integer between 2 and 36."
99 }
100 },
101
102 create(context) {
103 const mode = context.options[0] || MODE_ALWAYS;
104
105 /**
106 * Checks the arguments of a given CallExpression node and reports it if it
107 * offends this rule.
108 * @param {ASTNode} node A CallExpression node to check.
109 * @returns {void}
110 */
111 function checkArguments(node) {
112 const args = node.arguments;
113
114 switch (args.length) {
115 case 0:
116 context.report({
117 node,
118 messageId: "missingParameters"
119 });
120 break;
121
122 case 1:
123 if (mode === MODE_ALWAYS) {
124 context.report({
125 node,
126 messageId: "missingRadix"
127 });
128 }
129 break;
130
131 default:
132 if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) {
133 context.report({
134 node,
135 messageId: "redundantRadix"
136 });
137 } else if (!isValidRadix(args[1])) {
138 context.report({
139 node,
140 messageId: "invalidRadix"
141 });
142 }
143 break;
144 }
145 }
146
147 return {
148 "Program:exit"() {
149 const scope = context.getScope();
150 let variable;
151
152 // Check `parseInt()`
153 variable = astUtils.getVariableByName(scope, "parseInt");
154 if (variable && !isShadowed(variable)) {
155 variable.references.forEach(reference => {
156 const node = reference.identifier;
157
158 if (astUtils.isCallee(node)) {
159 checkArguments(node.parent);
160 }
161 });
162 }
163
164 // Check `Number.parseInt()`
165 variable = astUtils.getVariableByName(scope, "Number");
166 if (variable && !isShadowed(variable)) {
167 variable.references.forEach(reference => {
168 const node = reference.identifier.parent;
169 const maybeCallee = node.parent.type === "ChainExpression"
170 ? node.parent
171 : node;
172
173 if (isParseIntMethod(node) && astUtils.isCallee(maybeCallee)) {
174 checkArguments(maybeCallee.parent);
175 }
176 });
177 }
178 }
179 };
180 }
181};