--- /dev/null
+/**
+ * @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
+ * @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) {
+ 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
+ }
+ });
+ }
+ }
+ };
+ }
+};