Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rule Change: support computing modified cyclomatic complexity #18885

Open
1 task done
dpashk-figma opened this issue Sep 11, 2024 · 9 comments · May be fixed by #18896
Open
1 task done

Rule Change: support computing modified cyclomatic complexity #18885

dpashk-figma opened this issue Sep 11, 2024 · 9 comments · May be fixed by #18896
Labels
enhancement This change enhances an existing feature of ESLint rule Relates to ESLint's core rules

Comments

@dpashk-figma
Copy link

dpashk-figma commented Sep 11, 2024

What rule do you want to change?

complexity

What change do you want to make?

Generate fewer warnings

How do you think the change should be implemented?

A new option

Example code

function a(x) {
    switch (x) {
        case 1:
            1;
            break;
        case 2:
            2;
            break;
        case 3:
            3;
            break;
        default:
            4;
    }
}

What does the rule currently do for this code?

The cyclomatic complexity of the above block is 3.

What will the rule do after it's changed?

The modified cyclomatic** complexity of the above block is 1.

Participation

  • I am willing to submit a pull request to implement this change.

Additional comments

Modified cyclomatic complexity is almost exactly the same as the classic cyclomatic complexity, except that a switch statement only increases the value by 1 regardless of how many case statements it contains. The classic cyclomatic complexity, on the other hand, increases the complexity number for each case statement it finds (treating them effectively like separate if statements).

A number of other code analysis tools implement this metric:

The change is very straightforward to implement, just a few lines:

       WhileStatement: increaseComplexity,
       DoWhileStatement: increaseComplexity,
       AssignmentPattern: increaseComplexity,
-
-      // Avoid `default`
-      'SwitchCase[test]': increaseComplexity,
       SwitchStatement: increaseComplexity,

       // Logical assignment operators have short-circuiting behavior

Plus some extra option handling code (as we would of course want to put this behind a new option). I'm happy to make a PR if you'll accept it.

@dpashk-figma dpashk-figma added enhancement This change enhances an existing feature of ESLint rule Relates to ESLint's core rules labels Sep 11, 2024
@dpashk-figma
Copy link
Author

My rationale for adopting modified cyclomatic complexity vs the classic one is that switch statements are often appropriate constructs for patterns such as action matching (think redux), route matching, message matching, and code maintainers may not want to move away from them. Example:

switch (action.type) {
    case 'todos/todoAdded': 
      // ...
      break
    case 'todos/todoToggled': 
      // ...
      break

   // more cases...

    default:
      return state
  }

Of course it is possible to replace the above construct with something else (a map of action handlers, a builder pattern, or using some macros/decorators/metaprogramming) but that may not always be preferred.

@nzakas
Copy link
Member

nzakas commented Sep 12, 2024

👍 This seems like a nice addition to the complexity rule. We should put it behind an option so people can opt-in to the new behavior while keeping the default behavior the same.

@eslint/eslint-team do others agree?

@amareshsm
Copy link
Member

If a switch statement is used without break statements, cases will fall through to the next case. For example:

function a(x) {
    switch (x) {
        case 1:
            // no break
        case 2:
            // no break
        case 3:
            // no break
        default:
            4;
    }
}

In this case, if x is 1, it will fall through all cases and eventually execute the default case. Treating this switch statement as having a cyclomatic complexity of 1 might not capture the additional complexity introduced by the fall-through behaviour right? 🤔

@nzakas
Copy link
Member

nzakas commented Sep 13, 2024

@amareshsm the fall through behavior isn't part of the calculation of complexity. You might be confusing complexity with code paths.

@dpashk-figma
Copy link
Author

Right. Breaks in the switch statement do not affect the rule in its current version either.

dpashk-figma added a commit to dpashk-figma/eslint that referenced this issue Sep 13, 2024
Implements an option to use the modified cyclomatic complexity, where a switch statement
only increases the value by 1 regardless of how many case statements it contains.

Fixes eslint#18885
@dpashk-figma dpashk-figma linked a pull request Sep 13, 2024 that will close this issue
1 task
@dpashk-figma
Copy link
Author

Here's the PR: #18896

@mdjermanovic
Copy link
Member

We have never accepted options for this rule since it is intended to work per the definition of cyclomatic complexity, and the advice was always to create a custom rule for any variations or other metrics. For example, this was the same request for switch statements: #15001

@dpashk-figma
Copy link
Author

Custom rule is a fine path, especially if you want something truly custom (e.g. define your own team-specific complexity rules that don't fit any of the standard models). But Modified Complexity is a pretty common metric that a number of other code analysis tools implement consistently (I provided the references in the description), so I would consider it "standard" at this point. I could make a separate rule for it, but given how small the implementation changes are with the classic complexity metric, I think it's more practical to implement it as an option to the existing rule instead. What do you think @mdjermanovic?

@nzakas
Copy link
Member

nzakas commented Sep 13, 2024

@mdjermanovic I think the difference between this request and #15001 is that the latter was represented as a personal preference, and we didn't want to allow people to start arbitrarily changing how complexity was calculated. This request notes that there is a known alternative called "modified cyclomatic complexity" that changes how switch is calculated, so this is actually an option to cyclomatic complexity in the real world. That's why I think it makes sense to add as an option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement This change enhances an existing feature of ESLint rule Relates to ESLint's core rules
Projects
Status: Feedback Needed
Development

Successfully merging a pull request may close this issue.

4 participants