2020-03-07 11:45:40 +08:00
|
|
|
/**
|
|
|
|
* @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: {
|
|
|
|
type: "suggestion",
|
|
|
|
|
|
|
|
docs: {
|
|
|
|
description: "disallow magic numbers",
|
|
|
|
category: "Best Practices",
|
|
|
|
recommended: false,
|
|
|
|
url: "https://eslint.org/docs/rules/no-magic-numbers"
|
|
|
|
},
|
|
|
|
|
|
|
|
schema: [{
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
detectObjects: {
|
|
|
|
type: "boolean",
|
|
|
|
default: false
|
|
|
|
},
|
|
|
|
enforceConst: {
|
|
|
|
type: "boolean",
|
|
|
|
default: false
|
|
|
|
},
|
|
|
|
ignore: {
|
|
|
|
type: "array",
|
|
|
|
items: {
|
|
|
|
type: "number"
|
|
|
|
},
|
|
|
|
uniqueItems: true
|
|
|
|
},
|
|
|
|
ignoreArrayIndexes: {
|
|
|
|
type: "boolean",
|
|
|
|
default: false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
additionalProperties: false
|
|
|
|
}],
|
|
|
|
|
|
|
|
messages: {
|
|
|
|
useConst: "Number constants declarations must use 'const'.",
|
|
|
|
noMagic: "No magic number: {{raw}}."
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
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
|
2020-03-31 20:40:00 +08:00
|
|
|
* @param {Node} node the node literal being evaluated
|
2020-03-07 11:45:40 +08:00
|
|
|
* @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
|
2020-03-31 20:40:00 +08:00
|
|
|
* @param {number} num the number
|
2020-03-07 11:45:40 +08:00
|
|
|
* @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()
|
2020-03-31 20:40:00 +08:00
|
|
|
* @param {ASTNode} parent the non-"UnaryExpression" parent
|
|
|
|
* @param {ASTNode} node the node literal being evaluated
|
2020-03-07 11:45:40 +08:00
|
|
|
* @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
|
2020-03-31 20:40:00 +08:00
|
|
|
* @param {ASTNode} parent the non-"UnaryExpression" parent
|
2020-03-07 11:45:40 +08:00
|
|
|
* @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.
|
2020-03-31 20:40:00 +08:00
|
|
|
* @param {ASTNode} parent the non-"UnaryExpression" parent.
|
2020-03-07 11:45:40 +08:00
|
|
|
* @returns {boolean} true if the number should be ignored
|
|
|
|
*/
|
|
|
|
function shouldIgnoreArrayIndexes(parent) {
|
|
|
|
return parent.type === "MemberExpression" && ignoreArrayIndexes;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
Literal(node) {
|
|
|
|
const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
|
|
|
|
|
|
|
|
if (!isNumber(node)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let fullNumberNode;
|
|
|
|
let parent;
|
|
|
|
let value;
|
|
|
|
let raw;
|
|
|
|
|
|
|
|
// For negative magic numbers: update the value and parent node
|
|
|
|
if (node.parent.type === "UnaryExpression" && node.parent.operator === "-") {
|
|
|
|
fullNumberNode = node.parent;
|
|
|
|
parent = fullNumberNode.parent;
|
|
|
|
value = -node.value;
|
|
|
|
raw = `-${node.raw}`;
|
|
|
|
} else {
|
|
|
|
fullNumberNode = node;
|
|
|
|
parent = node.parent;
|
|
|
|
value = node.value;
|
|
|
|
raw = node.raw;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldIgnoreNumber(value) ||
|
|
|
|
shouldIgnoreParseInt(parent, fullNumberNode) ||
|
|
|
|
shouldIgnoreArrayIndexes(parent) ||
|
|
|
|
shouldIgnoreJSXNumbers(parent)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parent.type === "VariableDeclarator") {
|
|
|
|
if (enforceConst && parent.parent.kind !== "const") {
|
|
|
|
context.report({
|
|
|
|
node: fullNumberNode,
|
|
|
|
messageId: "useConst"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
okTypes.indexOf(parent.type) === -1 ||
|
|
|
|
(parent.type === "AssignmentExpression" && parent.left.type === "Identifier")
|
|
|
|
) {
|
|
|
|
context.report({
|
|
|
|
node: fullNumberNode,
|
|
|
|
messageId: "noMagic",
|
|
|
|
data: {
|
|
|
|
raw
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|