UNPKG

5.42 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 * @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"use strict";
33
34//------------------------------------------------------------------------------
35// Rule Definition
36//------------------------------------------------------------------------------
37
38module.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
128module.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}];