1 | /**
|
2 | * @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js)
|
3 | * @author Vincent Lemeunier
|
4 | * @copyright 2015 Vincent Lemeunier. All rights reserved.
|
5 | *
|
6 | * This rule was adapted from danielstjules/buddy.js
|
7 | * The MIT License (MIT)
|
8 | *
|
9 | * Copyright (c) 2014 Daniel St. Jules
|
10 | *
|
11 | * Permission is hereby granted, free of charge, to any person obtaining a copy
|
12 | * of this software and associated documentation files (the "Software"), to deal
|
13 | * in the Software without restriction, including without limitation the rights
|
14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
15 | * copies of the Software, and to permit persons to whom the Software is
|
16 | * furnished to do so, subject to the following conditions:
|
17 |
|
18 | * The above copyright notice and this permission notice shall be included in all
|
19 | * copies or substantial portions of the Software.
|
20 |
|
21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
27 | * SOFTWARE.
|
28 | *
|
29 | * See LICENSE file in root directory for full license.
|
30 | */
|
31 |
|
32 | ;
|
33 |
|
34 | //------------------------------------------------------------------------------
|
35 | // Rule Definition
|
36 | //------------------------------------------------------------------------------
|
37 |
|
38 | module.exports = function(context) {
|
39 | var config = context.options[0] || {},
|
40 | detectObjects = !!config.detectObjects,
|
41 | enforceConst = !!config.enforceConst,
|
42 | ignore = config.ignore || [],
|
43 | ignoreArrayIndexes = !!config.ignoreArrayIndexes;
|
44 |
|
45 | /**
|
46 | * Returns whether the node is number literal
|
47 | * @param {Node} node - the node literal being evaluated
|
48 | * @returns {boolean} true if the node is a number literal
|
49 | */
|
50 | function isNumber(node) {
|
51 | return typeof node.value === "number";
|
52 | }
|
53 |
|
54 | /**
|
55 | * Returns whether the number should be ignored
|
56 | * @param {number} num - the number
|
57 | * @returns {boolean} true if the number should be ignored
|
58 | */
|
59 | function shouldIgnoreNumber(num) {
|
60 | return ignore.indexOf(num) !== -1;
|
61 | }
|
62 |
|
63 | /**
|
64 | * Returns whether the number should be ignored when used as a radix within parseInt() or Number.parseInt()
|
65 | * @param {ASTNode} parent - the non-"UnaryExpression" parent
|
66 | * @param {ASTNode} node - the node literal being evaluated
|
67 | * @returns {boolean} true if the number should be ignored
|
68 | */
|
69 | function shouldIgnoreParseInt(parent, node) {
|
70 | return parent.type === "CallExpression" && node === parent.arguments[1] &&
|
71 | (parent.callee.name === "parseInt" ||
|
72 | parent.callee.type === "MemberExpression" &&
|
73 | parent.callee.object.name === "Number" &&
|
74 | parent.callee.property.name === "parseInt");
|
75 | }
|
76 |
|
77 | /**
|
78 | * Returns whether the number should be ignored when used as an array index with enabled 'ignoreArrayIndexes' option.
|
79 | * @param {ASTNode} parent - the non-"UnaryExpression" parent.
|
80 | * @returns {boolean} true if the number should be ignored
|
81 | */
|
82 | function shouldIgnoreArrayIndexes(parent) {
|
83 | return parent.type === "MemberExpression" && ignoreArrayIndexes;
|
84 | }
|
85 |
|
86 | return {
|
87 | "Literal": function(node) {
|
88 | var parent = node.parent,
|
89 | value = node.value,
|
90 | raw = node.raw,
|
91 | okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
|
92 |
|
93 | if (!isNumber(node)) {
|
94 | return;
|
95 | }
|
96 |
|
97 | // For negative magic numbers: update the value and parent node
|
98 | if (parent.type === "UnaryExpression" && parent.operator === "-") {
|
99 | node = parent;
|
100 | parent = node.parent;
|
101 | value = -value;
|
102 | raw = "-" + raw;
|
103 | }
|
104 |
|
105 | if (shouldIgnoreNumber(value) ||
|
106 | shouldIgnoreParseInt(parent, node) ||
|
107 | shouldIgnoreArrayIndexes(parent)) {
|
108 | return;
|
109 | }
|
110 |
|
111 | if (parent.type === "VariableDeclarator") {
|
112 | if (enforceConst && parent.parent.kind !== "const") {
|
113 | context.report({
|
114 | node: node,
|
115 | message: "Number constants declarations must use 'const'"
|
116 | });
|
117 | }
|
118 | } else if (okTypes.indexOf(parent.type) === -1) {
|
119 | context.report({
|
120 | node: node,
|
121 | message: "No magic number: " + raw
|
122 | });
|
123 | }
|
124 | }
|
125 | };
|
126 | };
|
127 |
|
128 | module.exports.schema = [{
|
129 | "type": "object",
|
130 | "properties": {
|
131 | "detectObjects": {
|
132 | "type": "boolean"
|
133 | },
|
134 | "enforceConst": {
|
135 | "type": "boolean"
|
136 | },
|
137 | "ignore": {
|
138 | "type": "array",
|
139 | "items": {
|
140 | "type": "number"
|
141 | },
|
142 | "uniqueItems": true
|
143 | },
|
144 | "ignoreArrayIndexes": {
|
145 | "type": "boolean"
|
146 | }
|
147 | },
|
148 | "additionalProperties": false
|
149 | }];
|