UNPKG

5.21 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 {escope.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 },
86
87 schema: [
88 {
89 enum: ["always", "as-needed"]
90 }
91 ]
92 },
93
94 create(context) {
95 const mode = context.options[0] || MODE_ALWAYS;
96
97 /**
98 * Checks the arguments of a given CallExpression node and reports it if it
99 * offends this rule.
100 *
101 * @param {ASTNode} node - A CallExpression node to check.
102 * @returns {void}
103 */
104 function checkArguments(node) {
105 const args = node.arguments;
106
107 switch (args.length) {
108 case 0:
109 context.report({
110 node,
111 message: "Missing parameters."
112 });
113 break;
114
115 case 1:
116 if (mode === MODE_ALWAYS) {
117 context.report({
118 node,
119 message: "Missing radix parameter."
120 });
121 }
122 break;
123
124 default:
125 if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) {
126 context.report({
127 node,
128 message: "Redundant radix parameter."
129 });
130 } else if (!isValidRadix(args[1])) {
131 context.report({
132 node,
133 message: "Invalid radix parameter."
134 });
135 }
136 break;
137 }
138 }
139
140 return {
141 "Program:exit"() {
142 const scope = context.getScope();
143 let variable;
144
145 // Check `parseInt()`
146 variable = astUtils.getVariableByName(scope, "parseInt");
147 if (!isShadowed(variable)) {
148 variable.references.forEach(function(reference) {
149 const node = reference.identifier;
150
151 if (astUtils.isCallee(node)) {
152 checkArguments(node.parent);
153 }
154 });
155 }
156
157 // Check `Number.parseInt()`
158 variable = astUtils.getVariableByName(scope, "Number");
159 if (!isShadowed(variable)) {
160 variable.references.forEach(function(reference) {
161 const node = reference.identifier.parent;
162
163 if (isParseIntMethod(node) && astUtils.isCallee(node)) {
164 checkArguments(node.parent);
165 }
166 });
167 }
168 }
169 };
170 }
171};