UNPKG

5.7 kBJavaScriptView Raw
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//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = {
13 meta: {
14 docs: {
15 description: "disallow magic numbers",
16 category: "Best Practices",
17 recommended: false,
18 url: "https://eslint.org/docs/rules/no-magic-numbers"
19 },
20
21 schema: [{
22 type: "object",
23 properties: {
24 detectObjects: {
25 type: "boolean"
26 },
27 enforceConst: {
28 type: "boolean"
29 },
30 ignore: {
31 type: "array",
32 items: {
33 type: "number"
34 },
35 uniqueItems: true
36 },
37 ignoreArrayIndexes: {
38 type: "boolean"
39 }
40 },
41 additionalProperties: false
42 }]
43 },
44
45 create(context) {
46 const config = context.options[0] || {},
47 detectObjects = !!config.detectObjects,
48 enforceConst = !!config.enforceConst,
49 ignore = config.ignore || [],
50 ignoreArrayIndexes = !!config.ignoreArrayIndexes;
51
52 /**
53 * Returns whether the node is number literal
54 * @param {Node} node - the node literal being evaluated
55 * @returns {boolean} true if the node is a number literal
56 */
57 function isNumber(node) {
58 return typeof node.value === "number";
59 }
60
61 /**
62 * Returns whether the number should be ignored
63 * @param {number} num - the number
64 * @returns {boolean} true if the number should be ignored
65 */
66 function shouldIgnoreNumber(num) {
67 return ignore.indexOf(num) !== -1;
68 }
69
70 /**
71 * Returns whether the number should be ignored when used as a radix within parseInt() or Number.parseInt()
72 * @param {ASTNode} parent - the non-"UnaryExpression" parent
73 * @param {ASTNode} node - the node literal being evaluated
74 * @returns {boolean} true if the number should be ignored
75 */
76 function shouldIgnoreParseInt(parent, node) {
77 return parent.type === "CallExpression" && node === parent.arguments[1] &&
78 (parent.callee.name === "parseInt" ||
79 parent.callee.type === "MemberExpression" &&
80 parent.callee.object.name === "Number" &&
81 parent.callee.property.name === "parseInt");
82 }
83
84 /**
85 * Returns whether the number should be ignored when used to define a JSX prop
86 * @param {ASTNode} parent - the non-"UnaryExpression" parent
87 * @returns {boolean} true if the number should be ignored
88 */
89 function shouldIgnoreJSXNumbers(parent) {
90 return parent.type.indexOf("JSX") === 0;
91 }
92
93 /**
94 * Returns whether the number should be ignored when used as an array index with enabled 'ignoreArrayIndexes' option.
95 * @param {ASTNode} parent - the non-"UnaryExpression" parent.
96 * @returns {boolean} true if the number should be ignored
97 */
98 function shouldIgnoreArrayIndexes(parent) {
99 return parent.type === "MemberExpression" && ignoreArrayIndexes;
100 }
101
102 return {
103 Literal(node) {
104 const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
105
106 if (!isNumber(node)) {
107 return;
108 }
109
110 let fullNumberNode;
111 let parent;
112 let value;
113 let raw;
114
115 // For negative magic numbers: update the value and parent node
116 if (node.parent.type === "UnaryExpression" && node.parent.operator === "-") {
117 fullNumberNode = node.parent;
118 parent = fullNumberNode.parent;
119 value = -node.value;
120 raw = `-${node.raw}`;
121 } else {
122 fullNumberNode = node;
123 parent = node.parent;
124 value = node.value;
125 raw = node.raw;
126 }
127
128 if (shouldIgnoreNumber(value) ||
129 shouldIgnoreParseInt(parent, fullNumberNode) ||
130 shouldIgnoreArrayIndexes(parent) ||
131 shouldIgnoreJSXNumbers(parent)) {
132 return;
133 }
134
135 if (parent.type === "VariableDeclarator") {
136 if (enforceConst && parent.parent.kind !== "const") {
137 context.report({
138 node: fullNumberNode,
139 message: "Number constants declarations must use 'const'."
140 });
141 }
142 } else if (
143 okTypes.indexOf(parent.type) === -1 ||
144 (parent.type === "AssignmentExpression" && parent.left.type === "Identifier")
145 ) {
146 context.report({
147 node: fullNumberNode,
148 message: "No magic number: {{raw}}.",
149 data: {
150 raw
151 }
152 });
153 }
154 }
155 };
156 }
157};