S1871 Conditional structure should not have exactly the same implementation

  • Operating system: Windows 11 Home Single Language 22H2
  • SonarLint plugin version: v4.2.2
  • Programming language you’re coding in: Typescript
  • Is connected mode used: no

Question about RSPEC-1871

type Action =
  | { _tag: 'startJob'; payload: string }
  | { _tag: 'stopJob' ; payload: number }

class Job {
  handleAction(action: Action): void {
    const actionName = action._tag;
    switch (actionName) {
      case 'startJob' : return this[actionName](action)
      case 'stopJob'  : return this[actionName](action) // (*)
      //  ^ 
      //  This case's code block is the same as the block for the case on line 14.
      //  [+1 location]sonarlint(typescript:S1871)

      default         : actionName satisfies never
      // or Eslint `@typescript-eslint/switch-exhaustiveness-check`

  startJob( arg: { payload: string } ) { /*...*/ }
  stopJob(  arg: { payload: number } ) { /*...*/ }

With strict Typescript configuration such switch narrows the type of action tagged union and everything typecheks and looks safe. But the linter gives the warning (*).

Do anyone have any suggestions on how to, maybe, organize the code in a different way? Or is this valid usage of equal blocks of conditional branches, and then the lint rule S1871 should be improved?

Hey John,

Thank you for your feedback. What do you think about the following:

case 'startJob' : return this['startJob'](action)
case 'stopJob'  : return this['stopJob'](action)

Yes, similar this.startJob(action) removes warnings too.
So this means Sonar does not take Typescript types into consideration?
Surely, the branches look similar, becuase it is the same variable name. But on type level, actionName is “different” in every case.

Hello John,

The current rule implementation simply compares the tokens and AST node types found in different branches.
It doesn’t take into account the TS inferred types.