UNPKG

5.25 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("../ast-utils");
13
14//------------------------------------------------------------------------------
15// Helpers
16//------------------------------------------------------------------------------
17
18const MODE_ALWAYS = "always",
19 MODE_AS_NEEDED = "as-needed";
20
21/**
22 * Checks whether a given variable is shadowed or not.
23 *
24 * @param {eslint-scope.Variable} variable - A variable to check.
25 * @returns {boolean} `true` if the variable is shadowed.
26 */
27function isShadowed(variable) {
28 return variable.defs.length >= 1;
29}
30
31/**
32 * Checks whether a given node is a MemberExpression of `parseInt` method or not.
33 *
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 numbers.
53 * - undefined.
54 *
55 * @param {ASTNode} radix - A node of radix to check.
56 * @returns {boolean} `true` if the node is valid.
57 */
58function isValidRadix(radix) {
59 return !(
60 (radix.type === "Literal" && typeof radix.value !== "number") ||
61 (radix.type === "Identifier" && radix.name === "undefined")
62 );
63}
64
65/**
66 * Checks whether a given node is a default value of radix or not.
67 *
68 * @param {ASTNode} radix - A node of radix to check.
69 * @returns {boolean} `true` if the node is the literal node of `10`.
70 */
71function isDefaultRadix(radix) {
72 return radix.type === "Literal" && radix.value === 10;
73}
74
75//------------------------------------------------------------------------------
76// Rule Definition
77//------------------------------------------------------------------------------
78
79module.exports = {
80 meta: {
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
95 create(context) {
96 const mode = context.options[0] || MODE_ALWAYS;
97
98 /**
99 * Checks the arguments of a given CallExpression node and reports it if it
100 * offends this rule.
101 *
102 * @param {ASTNode} node - A CallExpression node to check.
103 * @returns {void}
104 */
105 function checkArguments(node) {
106 const args = node.arguments;
107
108 switch (args.length) {
109 case 0:
110 context.report({
111 node,
112 message: "Missing parameters."
113 });
114 break;
115
116 case 1:
117 if (mode === MODE_ALWAYS) {
118 context.report({
119 node,
120 message: "Missing radix parameter."
121 });
122 }
123 break;
124
125 default:
126 if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) {
127 context.report({
128 node,
129 message: "Redundant radix parameter."
130 });
131 } else if (!isValidRadix(args[1])) {
132 context.report({
133 node,
134 message: "Invalid radix parameter."
135 });
136 }
137 break;
138 }
139 }
140
141 return {
142 "Program:exit"() {
143 const scope = context.getScope();
144 let variable;
145
146 // Check `parseInt()`
147 variable = astUtils.getVariableByName(scope, "parseInt");
148 if (!isShadowed(variable)) {
149 variable.references.forEach(reference => {
150 const node = reference.identifier;
151
152 if (astUtils.isCallee(node)) {
153 checkArguments(node.parent);
154 }
155 });
156 }
157
158 // Check `Number.parseInt()`
159 variable = astUtils.getVariableByName(scope, "Number");
160 if (!isShadowed(variable)) {
161 variable.references.forEach(reference => {
162 const node = reference.identifier.parent;
163
164 if (isParseIntMethod(node) && astUtils.isCallee(node)) {
165 checkArguments(node.parent);
166 }
167 });
168 }
169 }
170 };
171 }
172};