source: imaps-frontend/node_modules/eslint/lib/rules/no-magic-numbers.js@ d565449

main
Last change on this file since d565449 was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 9.3 KB
RevLine 
[d565449]1/**
2 * @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js)
3 * @author Vincent Lemeunier
4 */
5
6"use strict";
7
8const astUtils = require("./utils/ast-utils");
9
10// Maximum array length by the ECMAScript Specification.
11const MAX_ARRAY_LENGTH = 2 ** 32 - 1;
12
13//------------------------------------------------------------------------------
14// Rule Definition
15//------------------------------------------------------------------------------
16
17/**
18 * Convert the value to bigint if it's a string. Otherwise return the value as-is.
19 * @param {bigint|number|string} x The value to normalize.
20 * @returns {bigint|number} The normalized value.
21 */
22function normalizeIgnoreValue(x) {
23 if (typeof x === "string") {
24 return BigInt(x.slice(0, -1));
25 }
26 return x;
27}
28
29/** @type {import('../shared/types').Rule} */
30module.exports = {
31 meta: {
32 type: "suggestion",
33
34 docs: {
35 description: "Disallow magic numbers",
36 recommended: false,
37 url: "https://eslint.org/docs/latest/rules/no-magic-numbers"
38 },
39
40 schema: [{
41 type: "object",
42 properties: {
43 detectObjects: {
44 type: "boolean",
45 default: false
46 },
47 enforceConst: {
48 type: "boolean",
49 default: false
50 },
51 ignore: {
52 type: "array",
53 items: {
54 anyOf: [
55 { type: "number" },
56 { type: "string", pattern: "^[+-]?(?:0|[1-9][0-9]*)n$" }
57 ]
58 },
59 uniqueItems: true
60 },
61 ignoreArrayIndexes: {
62 type: "boolean",
63 default: false
64 },
65 ignoreDefaultValues: {
66 type: "boolean",
67 default: false
68 },
69 ignoreClassFieldInitialValues: {
70 type: "boolean",
71 default: false
72 }
73 },
74 additionalProperties: false
75 }],
76
77 messages: {
78 useConst: "Number constants declarations must use 'const'.",
79 noMagic: "No magic number: {{raw}}."
80 }
81 },
82
83 create(context) {
84 const config = context.options[0] || {},
85 detectObjects = !!config.detectObjects,
86 enforceConst = !!config.enforceConst,
87 ignore = new Set((config.ignore || []).map(normalizeIgnoreValue)),
88 ignoreArrayIndexes = !!config.ignoreArrayIndexes,
89 ignoreDefaultValues = !!config.ignoreDefaultValues,
90 ignoreClassFieldInitialValues = !!config.ignoreClassFieldInitialValues;
91
92 const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
93
94 /**
95 * Returns whether the rule is configured to ignore the given value
96 * @param {bigint|number} value The value to check
97 * @returns {boolean} true if the value is ignored
98 */
99 function isIgnoredValue(value) {
100 return ignore.has(value);
101 }
102
103 /**
104 * Returns whether the number is a default value assignment.
105 * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
106 * @returns {boolean} true if the number is a default value
107 */
108 function isDefaultValue(fullNumberNode) {
109 const parent = fullNumberNode.parent;
110
111 return parent.type === "AssignmentPattern" && parent.right === fullNumberNode;
112 }
113
114 /**
115 * Returns whether the number is the initial value of a class field.
116 * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
117 * @returns {boolean} true if the number is the initial value of a class field.
118 */
119 function isClassFieldInitialValue(fullNumberNode) {
120 const parent = fullNumberNode.parent;
121
122 return parent.type === "PropertyDefinition" && parent.value === fullNumberNode;
123 }
124
125 /**
126 * Returns whether the given node is used as a radix within parseInt() or Number.parseInt()
127 * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
128 * @returns {boolean} true if the node is radix
129 */
130 function isParseIntRadix(fullNumberNode) {
131 const parent = fullNumberNode.parent;
132
133 return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] &&
134 (
135 astUtils.isSpecificId(parent.callee, "parseInt") ||
136 astUtils.isSpecificMemberAccess(parent.callee, "Number", "parseInt")
137 );
138 }
139
140 /**
141 * Returns whether the given node is a direct child of a JSX node.
142 * In particular, it aims to detect numbers used as prop values in JSX tags.
143 * Example: <input maxLength={10} />
144 * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
145 * @returns {boolean} true if the node is a JSX number
146 */
147 function isJSXNumber(fullNumberNode) {
148 return fullNumberNode.parent.type.indexOf("JSX") === 0;
149 }
150
151 /**
152 * Returns whether the given node is used as an array index.
153 * Value must coerce to a valid array index name: "0", "1", "2" ... "4294967294".
154 *
155 * All other values, like "-1", "2.5", or "4294967295", are just "normal" object properties,
156 * which can be created and accessed on an array in addition to the array index properties,
157 * but they don't affect array's length and are not considered by methods such as .map(), .forEach() etc.
158 *
159 * The maximum array length by the specification is 2 ** 32 - 1 = 4294967295,
160 * thus the maximum valid index is 2 ** 32 - 2 = 4294967294.
161 *
162 * All notations are allowed, as long as the value coerces to one of "0", "1", "2" ... "4294967294".
163 *
164 * Valid examples:
165 * a[0], a[1], a[1.2e1], a[0xAB], a[0n], a[1n]
166 * a[-0] (same as a[0] because -0 coerces to "0")
167 * a[-0n] (-0n evaluates to 0n)
168 *
169 * Invalid examples:
170 * a[-1], a[-0xAB], a[-1n], a[2.5], a[1.23e1], a[12e-1]
171 * a[4294967295] (above the max index, it's an access to a regular property a["4294967295"])
172 * a[999999999999999999999] (even if it wasn't above the max index, it would be a["1e+21"])
173 * a[1e310] (same as a["Infinity"])
174 * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
175 * @param {bigint|number} value Value expressed by the fullNumberNode
176 * @returns {boolean} true if the node is a valid array index
177 */
178 function isArrayIndex(fullNumberNode, value) {
179 const parent = fullNumberNode.parent;
180
181 return parent.type === "MemberExpression" && parent.property === fullNumberNode &&
182 (Number.isInteger(value) || typeof value === "bigint") &&
183 value >= 0 && value < MAX_ARRAY_LENGTH;
184 }
185
186 return {
187 Literal(node) {
188 if (!astUtils.isNumericLiteral(node)) {
189 return;
190 }
191
192 let fullNumberNode;
193 let value;
194 let raw;
195
196 // Treat unary minus as a part of the number
197 if (node.parent.type === "UnaryExpression" && node.parent.operator === "-") {
198 fullNumberNode = node.parent;
199 value = -node.value;
200 raw = `-${node.raw}`;
201 } else {
202 fullNumberNode = node;
203 value = node.value;
204 raw = node.raw;
205 }
206
207 const parent = fullNumberNode.parent;
208
209 // Always allow radix arguments and JSX props
210 if (
211 isIgnoredValue(value) ||
212 (ignoreDefaultValues && isDefaultValue(fullNumberNode)) ||
213 (ignoreClassFieldInitialValues && isClassFieldInitialValue(fullNumberNode)) ||
214 isParseIntRadix(fullNumberNode) ||
215 isJSXNumber(fullNumberNode) ||
216 (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value))
217 ) {
218 return;
219 }
220
221 if (parent.type === "VariableDeclarator") {
222 if (enforceConst && parent.parent.kind !== "const") {
223 context.report({
224 node: fullNumberNode,
225 messageId: "useConst"
226 });
227 }
228 } else if (
229 !okTypes.includes(parent.type) ||
230 (parent.type === "AssignmentExpression" && parent.left.type === "Identifier")
231 ) {
232 context.report({
233 node: fullNumberNode,
234 messageId: "noMagic",
235 data: {
236 raw
237 }
238 });
239 }
240 }
241 };
242 }
243};
Note: See TracBrowser for help on using the repository browser.