You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

149 lines
5.2 KiB

/**
* @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js)
* @author Vincent Lemeunier
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "disallow magic numbers",
category: "Best Practices",
recommended: false
},
schema: [{
type: "object",
properties: {
detectObjects: {
type: "boolean"
},
enforceConst: {
type: "boolean"
},
ignore: {
type: "array",
items: {
type: "number"
},
uniqueItems: true
},
ignoreArrayIndexes: {
type: "boolean"
}
},
additionalProperties: false
}]
},
create(context) {
const config = context.options[0] || {},
detectObjects = !!config.detectObjects,
enforceConst = !!config.enforceConst,
ignore = config.ignore || [],
ignoreArrayIndexes = !!config.ignoreArrayIndexes;
/**
* Returns whether the node is number literal
* @param {Node} node - the node literal being evaluated
* @returns {boolean} true if the node is a number literal
*/
function isNumber(node) {
return typeof node.value === "number";
}
/**
* Returns whether the number should be ignored
* @param {number} num - the number
* @returns {boolean} true if the number should be ignored
*/
function shouldIgnoreNumber(num) {
return ignore.indexOf(num) !== -1;
}
/**
* Returns whether the number should be ignored when used as a radix within parseInt() or Number.parseInt()
* @param {ASTNode} parent - the non-"UnaryExpression" parent
* @param {ASTNode} node - the node literal being evaluated
* @returns {boolean} true if the number should be ignored
*/
function shouldIgnoreParseInt(parent, node) {
return parent.type === "CallExpression" && node === parent.arguments[1] &&
(parent.callee.name === "parseInt" ||
parent.callee.type === "MemberExpression" &&
parent.callee.object.name === "Number" &&
parent.callee.property.name === "parseInt");
}
/**
* Returns whether the number should be ignored when used to define a JSX prop
* @param {ASTNode} parent - the non-"UnaryExpression" parent
* @returns {boolean} true if the number should be ignored
*/
function shouldIgnoreJSXNumbers(parent) {
return parent.type.indexOf("JSX") === 0;
}
/**
* Returns whether the number should be ignored when used as an array index with enabled 'ignoreArrayIndexes' option.
* @param {ASTNode} parent - the non-"UnaryExpression" parent.
* @returns {boolean} true if the number should be ignored
*/
function shouldIgnoreArrayIndexes(parent) {
return parent.type === "MemberExpression" && ignoreArrayIndexes;
}
return {
Literal(node) {
let parent = node.parent,
value = node.value,
raw = node.raw;
const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
if (!isNumber(node)) {
return;
}
// For negative magic numbers: update the value and parent node
if (parent.type === "UnaryExpression" && parent.operator === "-") {
node = parent;
parent = node.parent;
value = -value;
raw = `-${raw}`;
}
if (shouldIgnoreNumber(value) ||
shouldIgnoreParseInt(parent, node) ||
shouldIgnoreArrayIndexes(parent) ||
shouldIgnoreJSXNumbers(parent)) {
return;
}
if (parent.type === "VariableDeclarator") {
if (enforceConst && parent.parent.kind !== "const") {
context.report({
node,
message: "Number constants declarations must use 'const'."
});
}
} else if (
okTypes.indexOf(parent.type) === -1 ||
(parent.type === "AssignmentExpression" && parent.left.type === "Identifier")
) {
context.report({
node,
message: "No magic number: {{raw}}.",
data: {
raw
}
});
}
}
};
}
};