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.
 
 
 
 

162 line
5.7 KiB

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