complexity.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /**
  2. * @fileoverview Counts the cyclomatic complexity of each function of the script. See http://en.wikipedia.org/wiki/Cyclomatic_complexity.
  3. * Counts the number of if, conditional, for, while, try, switch/case,
  4. * @author Patrick Brosset
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const astUtils = require("./utils/ast-utils");
  11. const { upperCaseFirst } = require("../shared/string-utils");
  12. //------------------------------------------------------------------------------
  13. // Rule Definition
  14. //------------------------------------------------------------------------------
  15. const THRESHOLD_DEFAULT = 20;
  16. /** @type {import('../types').Rule.RuleModule} */
  17. module.exports = {
  18. meta: {
  19. type: "suggestion",
  20. defaultOptions: [THRESHOLD_DEFAULT],
  21. docs: {
  22. description:
  23. "Enforce a maximum cyclomatic complexity allowed in a program",
  24. recommended: false,
  25. url: "https://eslint.org/docs/latest/rules/complexity",
  26. },
  27. schema: [
  28. {
  29. oneOf: [
  30. {
  31. type: "integer",
  32. minimum: 0,
  33. },
  34. {
  35. type: "object",
  36. properties: {
  37. maximum: {
  38. type: "integer",
  39. minimum: 0,
  40. },
  41. max: {
  42. type: "integer",
  43. minimum: 0,
  44. },
  45. variant: {
  46. enum: ["classic", "modified"],
  47. },
  48. },
  49. additionalProperties: false,
  50. },
  51. ],
  52. },
  53. ],
  54. messages: {
  55. complex:
  56. "{{name}} has a complexity of {{complexity}}. Maximum allowed is {{max}}.",
  57. },
  58. },
  59. create(context) {
  60. const sourceCode = context.sourceCode;
  61. const option = context.options[0];
  62. let threshold = THRESHOLD_DEFAULT;
  63. let VARIANT = "classic";
  64. if (typeof option === "object") {
  65. if (
  66. Object.hasOwn(option, "maximum") ||
  67. Object.hasOwn(option, "max")
  68. ) {
  69. threshold = option.maximum || option.max;
  70. }
  71. if (Object.hasOwn(option, "variant")) {
  72. VARIANT = option.variant;
  73. }
  74. } else if (typeof option === "number") {
  75. threshold = option;
  76. }
  77. const IS_MODIFIED_COMPLEXITY = VARIANT === "modified";
  78. //--------------------------------------------------------------------------
  79. // Helpers
  80. //--------------------------------------------------------------------------
  81. // Using a stack to store complexity per code path
  82. const complexities = [];
  83. /**
  84. * Increase the complexity of the code path in context
  85. * @returns {void}
  86. * @private
  87. */
  88. function increaseComplexity() {
  89. complexities[complexities.length - 1]++;
  90. }
  91. //--------------------------------------------------------------------------
  92. // Public API
  93. //--------------------------------------------------------------------------
  94. return {
  95. onCodePathStart() {
  96. // The initial complexity is 1, representing one execution path in the CodePath
  97. complexities.push(1);
  98. },
  99. // Each branching in the code adds 1 to the complexity
  100. CatchClause: increaseComplexity,
  101. ConditionalExpression: increaseComplexity,
  102. LogicalExpression: increaseComplexity,
  103. ForStatement: increaseComplexity,
  104. ForInStatement: increaseComplexity,
  105. ForOfStatement: increaseComplexity,
  106. IfStatement: increaseComplexity,
  107. WhileStatement: increaseComplexity,
  108. DoWhileStatement: increaseComplexity,
  109. AssignmentPattern: increaseComplexity,
  110. // Avoid `default`
  111. "SwitchCase[test]": () =>
  112. IS_MODIFIED_COMPLEXITY || increaseComplexity(),
  113. SwitchStatement: () =>
  114. IS_MODIFIED_COMPLEXITY && increaseComplexity(),
  115. // Logical assignment operators have short-circuiting behavior
  116. AssignmentExpression(node) {
  117. if (astUtils.isLogicalAssignmentOperator(node.operator)) {
  118. increaseComplexity();
  119. }
  120. },
  121. MemberExpression(node) {
  122. if (node.optional === true) {
  123. increaseComplexity();
  124. }
  125. },
  126. CallExpression(node) {
  127. if (node.optional === true) {
  128. increaseComplexity();
  129. }
  130. },
  131. onCodePathEnd(codePath, node) {
  132. const complexity = complexities.pop();
  133. /*
  134. * This rule only evaluates complexity of functions, so "program" is excluded.
  135. * Class field initializers and class static blocks are implicit functions. Therefore,
  136. * they shouldn't contribute to the enclosing function's complexity, but their
  137. * own complexity should be evaluated.
  138. */
  139. if (
  140. codePath.origin !== "function" &&
  141. codePath.origin !== "class-field-initializer" &&
  142. codePath.origin !== "class-static-block"
  143. ) {
  144. return;
  145. }
  146. if (complexity > threshold) {
  147. let name;
  148. let loc = node.loc;
  149. if (codePath.origin === "class-field-initializer") {
  150. name = "class field initializer";
  151. } else if (codePath.origin === "class-static-block") {
  152. name = "class static block";
  153. loc = sourceCode.getFirstToken(node).loc;
  154. } else {
  155. name = astUtils.getFunctionNameWithKind(node);
  156. loc = astUtils.getFunctionHeadLoc(node, sourceCode);
  157. }
  158. context.report({
  159. node,
  160. loc,
  161. messageId: "complex",
  162. data: {
  163. name: upperCaseFirst(name),
  164. complexity,
  165. max: threshold,
  166. },
  167. });
  168. }
  169. },
  170. };
  171. },
  172. };