.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-magic-numbers.js
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
8 const astUtils = require("./utils/ast-utils");
9
10 // Maximum array length by the ECMAScript Specification.
11 const 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  */
22 function normalizeIgnoreValue(x) {
23     if (typeof x === "string") {
24         return BigInt(x.slice(0, -1));
25     }
26     return x;
27 }
28
29 module.exports = {
30     meta: {
31         type: "suggestion",
32
33         docs: {
34             description: "disallow magic numbers",
35             category: "Best Practices",
36             recommended: false,
37             url: "https://eslint.org/docs/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             },
70             additionalProperties: false
71         }],
72
73         messages: {
74             useConst: "Number constants declarations must use 'const'.",
75             noMagic: "No magic number: {{raw}}."
76         }
77     },
78
79     create(context) {
80         const config = context.options[0] || {},
81             detectObjects = !!config.detectObjects,
82             enforceConst = !!config.enforceConst,
83             ignore = (config.ignore || []).map(normalizeIgnoreValue),
84             ignoreArrayIndexes = !!config.ignoreArrayIndexes,
85             ignoreDefaultValues = !!config.ignoreDefaultValues;
86
87         const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
88
89         /**
90          * Returns whether the rule is configured to ignore the given value
91          * @param {bigint|number} value The value to check
92          * @returns {boolean} true if the value is ignored
93          */
94         function isIgnoredValue(value) {
95             return ignore.indexOf(value) !== -1;
96         }
97
98         /**
99          * Returns whether the number is a default value assignment.
100          * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
101          * @returns {boolean} true if the number is a default value
102          */
103         function isDefaultValue(fullNumberNode) {
104             const parent = fullNumberNode.parent;
105
106             return parent.type === "AssignmentPattern" && parent.right === fullNumberNode;
107         }
108
109         /**
110          * Returns whether the given node is used as a radix within parseInt() or Number.parseInt()
111          * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
112          * @returns {boolean} true if the node is radix
113          */
114         function isParseIntRadix(fullNumberNode) {
115             const parent = fullNumberNode.parent;
116
117             return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] &&
118                 (
119                     astUtils.isSpecificId(parent.callee, "parseInt") ||
120                     astUtils.isSpecificMemberAccess(parent.callee, "Number", "parseInt")
121                 );
122         }
123
124         /**
125          * Returns whether the given node is a direct child of a JSX node.
126          * In particular, it aims to detect numbers used as prop values in JSX tags.
127          * Example: <input maxLength={10} />
128          * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
129          * @returns {boolean} true if the node is a JSX number
130          */
131         function isJSXNumber(fullNumberNode) {
132             return fullNumberNode.parent.type.indexOf("JSX") === 0;
133         }
134
135         /**
136          * Returns whether the given node is used as an array index.
137          * Value must coerce to a valid array index name: "0", "1", "2" ... "4294967294".
138          *
139          * All other values, like "-1", "2.5", or "4294967295", are just "normal" object properties,
140          * which can be created and accessed on an array in addition to the array index properties,
141          * but they don't affect array's length and are not considered by methods such as .map(), .forEach() etc.
142          *
143          * The maximum array length by the specification is 2 ** 32 - 1 = 4294967295,
144          * thus the maximum valid index is 2 ** 32 - 2 = 4294967294.
145          *
146          * All notations are allowed, as long as the value coerces to one of "0", "1", "2" ... "4294967294".
147          *
148          * Valid examples:
149          * a[0], a[1], a[1.2e1], a[0xAB], a[0n], a[1n]
150          * a[-0] (same as a[0] because -0 coerces to "0")
151          * a[-0n] (-0n evaluates to 0n)
152          *
153          * Invalid examples:
154          * a[-1], a[-0xAB], a[-1n], a[2.5], a[1.23e1], a[12e-1]
155          * a[4294967295] (above the max index, it's an access to a regular property a["4294967295"])
156          * a[999999999999999999999] (even if it wasn't above the max index, it would be a["1e+21"])
157          * a[1e310] (same as a["Infinity"])
158          * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
159          * @param {bigint|number} value Value expressed by the fullNumberNode
160          * @returns {boolean} true if the node is a valid array index
161          */
162         function isArrayIndex(fullNumberNode, value) {
163             const parent = fullNumberNode.parent;
164
165             return parent.type === "MemberExpression" && parent.property === fullNumberNode &&
166                 (Number.isInteger(value) || typeof value === "bigint") &&
167                 value >= 0 && value < MAX_ARRAY_LENGTH;
168         }
169
170         return {
171             Literal(node) {
172                 if (!astUtils.isNumericLiteral(node)) {
173                     return;
174                 }
175
176                 let fullNumberNode;
177                 let value;
178                 let raw;
179
180                 // Treat unary minus as a part of the number
181                 if (node.parent.type === "UnaryExpression" && node.parent.operator === "-") {
182                     fullNumberNode = node.parent;
183                     value = -node.value;
184                     raw = `-${node.raw}`;
185                 } else {
186                     fullNumberNode = node;
187                     value = node.value;
188                     raw = node.raw;
189                 }
190
191                 const parent = fullNumberNode.parent;
192
193                 // Always allow radix arguments and JSX props
194                 if (
195                     isIgnoredValue(value) ||
196                     (ignoreDefaultValues && isDefaultValue(fullNumberNode)) ||
197                     isParseIntRadix(fullNumberNode) ||
198                     isJSXNumber(fullNumberNode) ||
199                     (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value))
200                 ) {
201                     return;
202                 }
203
204                 if (parent.type === "VariableDeclarator") {
205                     if (enforceConst && parent.parent.kind !== "const") {
206                         context.report({
207                             node: fullNumberNode,
208                             messageId: "useConst"
209                         });
210                     }
211                 } else if (
212                     okTypes.indexOf(parent.type) === -1 ||
213                     (parent.type === "AssignmentExpression" && parent.left.type === "Identifier")
214                 ) {
215                     context.report({
216                         node: fullNumberNode,
217                         messageId: "noMagic",
218                         data: {
219                             raw
220                         }
221                     });
222                 }
223             }
224         };
225     }
226 };