UNPKG

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