no-fallthrough

January 6, 2021 ยท View on GitHub

:mag_right: works better with type information

Prevents unintentional fallthough in switch statements from one clause to another. If the fallthrough is intended, add a comment that matches /^\s*falls? ?through\b/i.

Rationale

In JavaScript switch statements a clause (case or default) that doesn't end the control flow falls through to the next clause without matching it's condition.

This is useful to mimic logical OR (||) in switch statements:

switch (v) {
  case 1:
  case 2:
    console.log('few');
}
// same as
if (v === 1 || v === 2) {
  console.log('few');
}

The confusing part are clauses that actually contain statements where you wouldn't expect a fallthrough:

switch (v) {
  default:
    console.log('default');
  case 1: {
    console.log('one');
  }
  case 2:
    console.log('two');
}

In the above code snippet if v === 2 it logs 'two'; if v === 1 it logs 'one' and 'two', because there is no break; at the end of the clause; for every other value it logs 'default', 'one' and 'two' because of missing break; statements.

Examples

:thumbsdown: Examples of incorrect code

switch (v) {
  default:
    console.log('default');
    // missing 'break;' here
  case 1: {
    console.log('one');
    // missing 'break;' here
  }
  case 2:
    if (condition) {
      console.log('two');
      break;
    } else {
      console.log('two and a half');
      // missing 'break;' in this branch
    }
  case 3:
    console.log('three');
}

:thumbsup: Examples of correct code

switch (v) {
  default:
    console.log('default'); // valid because of the comment below
    // falls through
  case 0:
    console.log('zero'):
    break; // 'break' ends thr control flow
  case 1: {
    console.log('one');
    return; // 'return' ends the control flow
  }
  case 2:
    if (condition) {
      console.log('two');
      break;
    } else {
      throw new Error(); // 'throw' ends control flow
    }
  case 3: // this is valid because it has no statements
  case 4:
    console.log('other');
    // there is no following clause so 'break;' is not necessary here
}

// when linting with type information the following patterns are also recognized
declare function fail(): never;

function fn(p: {kind: 'a', nested: boolean} | {kind: 'b' | 'c'}) {
  switch (p.kind) {
    case 'a':
      switch (p.nested) {
        case true:
          return 1;
        case false:
          return 2;
      }
      // switch statement is exhaustive (handles every possible value), so there's no possibility to fall through
    case 'b':
      fail();
      // ends control flow by returning 'never'
    case 'c':
      return 3;
  }
}