UNPKG

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