no-magic-numbers.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. /**
  2. * @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js)
  3. * @author Vincent Lemeunier
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. module.exports = {
  10. meta: {
  11. type: "suggestion",
  12. docs: {
  13. description: "disallow magic numbers",
  14. category: "Best Practices",
  15. recommended: false,
  16. url: "https://eslint.org/docs/rules/no-magic-numbers"
  17. },
  18. schema: [{
  19. type: "object",
  20. properties: {
  21. detectObjects: {
  22. type: "boolean",
  23. default: false
  24. },
  25. enforceConst: {
  26. type: "boolean",
  27. default: false
  28. },
  29. ignore: {
  30. type: "array",
  31. items: {
  32. type: "number"
  33. },
  34. uniqueItems: true
  35. },
  36. ignoreArrayIndexes: {
  37. type: "boolean",
  38. default: false
  39. }
  40. },
  41. additionalProperties: false
  42. }],
  43. messages: {
  44. useConst: "Number constants declarations must use 'const'.",
  45. noMagic: "No magic number: {{raw}}."
  46. }
  47. },
  48. create(context) {
  49. const config = context.options[0] || {},
  50. detectObjects = !!config.detectObjects,
  51. enforceConst = !!config.enforceConst,
  52. ignore = config.ignore || [],
  53. ignoreArrayIndexes = !!config.ignoreArrayIndexes;
  54. /**
  55. * Returns whether the node is number literal
  56. * @param {Node} node the node literal being evaluated
  57. * @returns {boolean} true if the node is a number literal
  58. */
  59. function isNumber(node) {
  60. return typeof node.value === "number";
  61. }
  62. /**
  63. * Returns whether the number should be ignored
  64. * @param {number} num the number
  65. * @returns {boolean} true if the number should be ignored
  66. */
  67. function shouldIgnoreNumber(num) {
  68. return ignore.indexOf(num) !== -1;
  69. }
  70. /**
  71. * Returns whether the number should be ignored when used as a radix within parseInt() or Number.parseInt()
  72. * @param {ASTNode} parent the non-"UnaryExpression" parent
  73. * @param {ASTNode} node the node literal being evaluated
  74. * @returns {boolean} true if the number should be ignored
  75. */
  76. function shouldIgnoreParseInt(parent, node) {
  77. return parent.type === "CallExpression" && node === parent.arguments[1] &&
  78. (parent.callee.name === "parseInt" ||
  79. parent.callee.type === "MemberExpression" &&
  80. parent.callee.object.name === "Number" &&
  81. parent.callee.property.name === "parseInt");
  82. }
  83. /**
  84. * Returns whether the number should be ignored when used to define a JSX prop
  85. * @param {ASTNode} parent the non-"UnaryExpression" parent
  86. * @returns {boolean} true if the number should be ignored
  87. */
  88. function shouldIgnoreJSXNumbers(parent) {
  89. return parent.type.indexOf("JSX") === 0;
  90. }
  91. /**
  92. * Returns whether the number should be ignored when used as an array index with enabled 'ignoreArrayIndexes' option.
  93. * @param {ASTNode} parent the non-"UnaryExpression" parent.
  94. * @returns {boolean} true if the number should be ignored
  95. */
  96. function shouldIgnoreArrayIndexes(parent) {
  97. return parent.type === "MemberExpression" && ignoreArrayIndexes;
  98. }
  99. return {
  100. Literal(node) {
  101. const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
  102. if (!isNumber(node)) {
  103. return;
  104. }
  105. let fullNumberNode;
  106. let parent;
  107. let value;
  108. let raw;
  109. // For negative magic numbers: update the value and parent node
  110. if (node.parent.type === "UnaryExpression" && node.parent.operator === "-") {
  111. fullNumberNode = node.parent;
  112. parent = fullNumberNode.parent;
  113. value = -node.value;
  114. raw = `-${node.raw}`;
  115. } else {
  116. fullNumberNode = node;
  117. parent = node.parent;
  118. value = node.value;
  119. raw = node.raw;
  120. }
  121. if (shouldIgnoreNumber(value) ||
  122. shouldIgnoreParseInt(parent, fullNumberNode) ||
  123. shouldIgnoreArrayIndexes(parent) ||
  124. shouldIgnoreJSXNumbers(parent)) {
  125. return;
  126. }
  127. if (parent.type === "VariableDeclarator") {
  128. if (enforceConst && parent.parent.kind !== "const") {
  129. context.report({
  130. node: fullNumberNode,
  131. messageId: "useConst"
  132. });
  133. }
  134. } else if (
  135. okTypes.indexOf(parent.type) === -1 ||
  136. (parent.type === "AssignmentExpression" && parent.left.type === "Identifier")
  137. ) {
  138. context.report({
  139. node: fullNumberNode,
  140. messageId: "noMagic",
  141. data: {
  142. raw
  143. }
  144. });
  145. }
  146. }
  147. };
  148. }
  149. };