Skip to content

⬅️ Back to Table of Contents

📄 switch-exhaustiveness-check

📊 Analysis Summary

Metric Count
🔧 Functions 10
📦 Imports 10
📊 Variables & Constants 1
📐 Interfaces 1
📑 Type Aliases 2

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'switch-exhaustiveness-check'
meta.type 'suggestion'
meta.docs.description 'Require switch-case statements to be exhaustive'
meta.docs.requiresTypeChecking true
meta.hasSuggestions true
meta.messages.addMissingCases 'Add branches for missing cases.'
meta.messages.dangerousDefaultCase 'The switch statement is exhaustive, so the default case is unnecessary.'
meta.messages.switchIsNotExhaustive 'Switch is not exhaustive. Cases not matched: {{missingBranches}}'
meta.schema [ { type: 'object', additionalProperties: false, properties: { allowDefaultCaseForExhaustiveSwitch: { type: 'boolean'...
defaultOptions [ { allowDefaultCaseForExhaustiveSwitch: true, considerDefaultExhaustiveForUnions: false, requireDefaultForNonUnion: ...

Entry point: create — documented under Functions.


📦 Imports

Name Source
TSESLint @typescript-eslint/utils
TSESTree @typescript-eslint/utils
createRule ../util
getConstrainedTypeAtLocation ../util
getParserServices ../util
isClosingBraceToken ../util
isOpeningBraceToken ../util
nullThrows ../util
NullThrowsReasons ../util
requiresQuoting ../util

Variables & Constants

Name Type Kind Value Exported
DEFAULT_COMMENT_PATTERN RegExp const /^no default$/iu

Functions

create(context: any, [ { allowDefaultCaseForExhausti…: any): { SwitchStatement(node: any): void; }

Parameters:

  • context any
  • [ { allowDefaultCaseForExhaustiveSwitch, considerDefaultExhaustiveForUnions, defaultCaseCommentPattern, requireDefaultForNonUnion, }, ] any

Returns: { SwitchStatement(node: any): void; }

Calls:

  • getParserServices (from ../util)
  • services.program.getTypeChecker
  • services.program.getCompilerOptions
  • node.cases.at
  • context.sourceCode.getCommentsAfter
  • commentsAfterLastCase.at
  • commentRegExp.test
  • defaultCaseComment?.value.trim
  • checker.typeToString
  • node.cases.find
  • getConstrainedTypeAtLocation (from ../util)
  • discriminantType.getSymbol
  • doesTypeContainNonLiteralType
  • caseTypes.add
  • tsutils.unionConstituents
  • tsutils.intersectionConstituents
  • caseTypes.has
  • isTypeLiteralLikeType
  • [...caseTypes].some
  • tsutils.isIntrinsicUndefinedType
  • missingLiteralBranchTypes.push
  • getCommentDefaultCase
  • context.report
  • missingLiteralBranchTypes .map(missingType => tsutils.isTypeFlagSet(missingType, ts.TypeFlags.ESSymbolLike) ?typeof ${missingType.getSymbol()?.escapedName as string}: typeToString(missingType), ) .join
  • fixSwitch
  • symbolName?.toString
  • ' '.repeat
  • missingCases.push
  • missingBranchType.getSymbol
  • tsutils.isTypeFlagSet
  • typeToString
  • requiresQuoting (from ../util)
  • missingBranchName.toString
  • missingBranchName .replaceAll("'", "\\'") .replaceAll('\n', '\\n') .replaceAll
  • caseTest .replaceAll('\\', '\\\\') .replaceAll
  • missingCases .map(code =>${caseIndent}${code}) .join
  • missingCases .map(code =>${code}\n${caseIndent}) .join
  • fixer.insertTextBefore
  • fixer.insertTextAfter
  • nullThrows (from ../util)
  • context.sourceCode.getTokenAfter
  • NullThrowsReasons.MissingToken
  • fixer.replaceTextRange
  • ['{', fixString,${caseIndent}}].join
  • getSwitchMetadata
  • checkSwitchExhaustive
  • checkSwitchUnnecessaryDefaultCase
  • checkSwitchNoUnionDefaultCase

Internal Comments:

// If the `test` property of the switch case is `null`, then we are on a
// `default` case.
// "missing", "optional" and "undefined" types are different runtime objects,
// but all of them have TypeFlags.Undefined type flag
// If considerDefaultExhaustiveForUnions is enabled, the presence of a default case
// always makes the switch exhaustive.
// leave it to the user to format it correctly. (x3)
// There were no existing cases. (x2)

Code
create(
    context,
    [
      {
        allowDefaultCaseForExhaustiveSwitch,
        considerDefaultExhaustiveForUnions,
        defaultCaseCommentPattern,
        requireDefaultForNonUnion,
      },
    ],
  ) {
    const services = getParserServices(context);
    const checker = services.program.getTypeChecker();
    const compilerOptions = services.program.getCompilerOptions();
    const commentRegExp =
      defaultCaseCommentPattern != null
        ? new RegExp(defaultCaseCommentPattern, 'u')
        : DEFAULT_COMMENT_PATTERN;

    function getCommentDefaultCase(
      node: TSESTree.SwitchStatement,
    ): TSESTree.Comment | undefined {
      const lastCase = node.cases.at(-1);
      const commentsAfterLastCase = lastCase
        ? context.sourceCode.getCommentsAfter(lastCase)
        : [];
      const defaultCaseComment = commentsAfterLastCase.at(-1);

      if (commentRegExp.test(defaultCaseComment?.value.trim() || '')) {
        return defaultCaseComment;
      }

      return;
    }

    function typeToString(type: ts.Type): string {
      return checker.typeToString(
        type,
        undefined,
        ts.TypeFormatFlags.AllowUniqueESSymbolType |
          ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope |
          ts.TypeFormatFlags.UseFullyQualifiedType,
      );
    }

    function getSwitchMetadata(node: TSESTree.SwitchStatement): SwitchMetadata {
      const defaultCase = node.cases.find(
        switchCase => switchCase.test == null,
      );

      const discriminantType = getConstrainedTypeAtLocation(
        services,
        node.discriminant,
      );

      const symbolName = discriminantType.getSymbol()?.escapedName as
        string | undefined;

      const containsNonLiteralType =
        doesTypeContainNonLiteralType(discriminantType);

      const caseTypes = new Set<ts.Type>();
      for (const switchCase of node.cases) {
        // If the `test` property of the switch case is `null`, then we are on a
        // `default` case.
        if (switchCase.test == null) {
          continue;
        }

        const caseType = getConstrainedTypeAtLocation(
          services,
          switchCase.test,
        );
        caseTypes.add(caseType);
      }

      const missingLiteralBranchTypes: ts.Type[] = [];

      for (const unionPart of tsutils.unionConstituents(discriminantType)) {
        for (const intersectionPart of tsutils.intersectionConstituents(
          unionPart,
        )) {
          if (
            caseTypes.has(intersectionPart) ||
            !isTypeLiteralLikeType(intersectionPart)
          ) {
            continue;
          }

          // "missing", "optional" and "undefined" types are different runtime objects,
          // but all of them have TypeFlags.Undefined type flag
          if (
            [...caseTypes].some(tsutils.isIntrinsicUndefinedType) &&
            tsutils.isIntrinsicUndefinedType(intersectionPart)
          ) {
            continue;
          }

          missingLiteralBranchTypes.push(intersectionPart);
        }
      }

      return {
        containsNonLiteralType,
        defaultCase: defaultCase ?? getCommentDefaultCase(node),
        missingLiteralBranchTypes,
        symbolName,
      };
    }

    function checkSwitchExhaustive(
      node: TSESTree.SwitchStatement,
      switchMetadata: SwitchMetadata,
    ): void {
      const { defaultCase, missingLiteralBranchTypes, symbolName } =
        switchMetadata;

      // If considerDefaultExhaustiveForUnions is enabled, the presence of a default case
      // always makes the switch exhaustive.
      if (considerDefaultExhaustiveForUnions && defaultCase != null) {
        return;
      }

      if (missingLiteralBranchTypes.length > 0) {
        context.report({
          node: node.discriminant,
          messageId: 'switchIsNotExhaustive',
          data: {
            missingBranches: missingLiteralBranchTypes
              .map(missingType =>
                tsutils.isTypeFlagSet(missingType, ts.TypeFlags.ESSymbolLike)
                  ? `typeof ${missingType.getSymbol()?.escapedName as string}`
                  : typeToString(missingType),
              )
              .join(' | '),
          },
          suggest: [
            {
              messageId: 'addMissingCases',
              fix(fixer): TSESLint.RuleFix | null {
                return fixSwitch(
                  fixer,
                  node,
                  missingLiteralBranchTypes,
                  defaultCase,
                  symbolName?.toString(),
                );
              },
            },
          ],
        });
      }
    }

    function fixSwitch(
      fixer: TSESLint.RuleFixer,
      node: TSESTree.SwitchStatement,
      missingBranchTypes: (ts.Type | null)[], // null means default branch
      defaultCase: TSESTree.Comment | TSESTree.SwitchCase | undefined,
      symbolName?: string,
    ): TSESLint.RuleFix {
      const lastCase =
        node.cases.length > 0 ? node.cases[node.cases.length - 1] : null;

      const caseIndent = lastCase
        ? ' '.repeat(lastCase.loc.start.column)
        : // If there are no cases, use indentation of the switch statement and
          // leave it to the user to format it correctly.
          ' '.repeat(node.loc.start.column);

      const missingCases = [];
      for (const missingBranchType of missingBranchTypes) {
        if (missingBranchType == null) {
          missingCases.push(`default: { throw new Error('default case') }`);
          continue;
        }

        const missingBranchName = missingBranchType.getSymbol()?.escapedName;
        let caseTest = tsutils.isTypeFlagSet(
          missingBranchType,
          ts.TypeFlags.ESSymbolLike,
        )
          ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            missingBranchName!
          : typeToString(missingBranchType);

        if (
          symbolName &&
          (missingBranchName || missingBranchName === '') &&
          requiresQuoting(missingBranchName.toString(), compilerOptions.target)
        ) {
          const escapedBranchName = missingBranchName
            .replaceAll("'", "\\'")
            .replaceAll('\n', '\\n')
            .replaceAll('\r', '\\r');

          caseTest = `${symbolName}['${escapedBranchName}']`;
        }

        missingCases.push(
          `case ${caseTest}: { throw new Error('Not implemented yet: ${caseTest
            .replaceAll('\\', '\\\\')
            .replaceAll("'", "\\'")} case') }`,
        );
      }

      const fixString = missingCases
        .map(code => `${caseIndent}${code}`)
        .join('\n');

      if (lastCase) {
        if (defaultCase) {
          const beforeFixString = missingCases
            .map(code => `${code}\n${caseIndent}`)
            .join('');

          return fixer.insertTextBefore(defaultCase, beforeFixString);
        }
        return fixer.insertTextAfter(lastCase, `\n${fixString}`);
      }

      // There were no existing cases.
      const openingBrace = nullThrows(
        context.sourceCode.getTokenAfter(
          node.discriminant,
          isOpeningBraceToken,
        ),
        NullThrowsReasons.MissingToken('{', 'discriminant'),
      );
      const closingBrace = nullThrows(
        context.sourceCode.getTokenAfter(
          node.discriminant,
          isClosingBraceToken,
        ),
        NullThrowsReasons.MissingToken('}', 'discriminant'),
      );

      return fixer.replaceTextRange(
        [openingBrace.range[0], closingBrace.range[1]],
        ['{', fixString, `${caseIndent}}`].join('\n'),
      );
    }

    function checkSwitchUnnecessaryDefaultCase(
      switchMetadata: SwitchMetadata,
    ): void {
      if (allowDefaultCaseForExhaustiveSwitch) {
        return;
      }

      const { containsNonLiteralType, defaultCase, missingLiteralBranchTypes } =
        switchMetadata;

      if (
        missingLiteralBranchTypes.length === 0 &&
        defaultCase != null &&
        !containsNonLiteralType
      ) {
        context.report({
          node: defaultCase,
          messageId: 'dangerousDefaultCase',
        });
      }
    }

    function checkSwitchNoUnionDefaultCase(
      node: TSESTree.SwitchStatement,
      switchMetadata: SwitchMetadata,
    ): void {
      if (!requireDefaultForNonUnion) {
        return;
      }

      const { containsNonLiteralType, defaultCase } = switchMetadata;

      if (containsNonLiteralType && defaultCase == null) {
        context.report({
          node: node.discriminant,
          messageId: 'switchIsNotExhaustive',
          data: { missingBranches: 'default' },
          suggest: [
            {
              messageId: 'addMissingCases',
              fix(fixer): TSESLint.RuleFix {
                return fixSwitch(fixer, node, [null], defaultCase);
              },
            },
          ],
        });
      }
    }

    return {
      SwitchStatement(node): void {
        const switchMetadata = getSwitchMetadata(node);

        checkSwitchExhaustive(node, switchMetadata);
        checkSwitchUnnecessaryDefaultCase(switchMetadata);
        checkSwitchNoUnionDefaultCase(node, switchMetadata);
      },
    };
  }

isTypeLiteralLikeType(type: ts.Type): boolean

Parameters:

  • type ts.Type

Returns: boolean

Calls:

  • tsutils.isTypeFlagSet
Code
function isTypeLiteralLikeType(type: ts.Type): boolean {
  return tsutils.isTypeFlagSet(
    type,
    ts.TypeFlags.Literal |
      ts.TypeFlags.Undefined |
      ts.TypeFlags.Null |
      ts.TypeFlags.UniqueESSymbol,
  );
}

doesTypeContainNonLiteralType(type: ts.Type): boolean

For example:

  • "foo" | "bar" is a type with all literal types.
  • "foo" | number is a type that contains non-literal types.
  • "foo" & { bar: 1 } is a type that contains non-literal types.

Default cases are never superfluous in switches with non-literal types.

Raw JSDoc
/**
 * For example:
 *
 * - `"foo" | "bar"` is a type with all literal types.
 * - `"foo" | number` is a type that contains non-literal types.
 * - `"foo" & { bar: 1 }` is a type that contains non-literal types.
 *
 * Default cases are never superfluous in switches with non-literal types.
 */

Calls:

  • tsutils .unionConstituents(type) .some
  • tsutils .intersectionConstituents(type) .every
  • isTypeLiteralLikeType
Code
function doesTypeContainNonLiteralType(type: ts.Type): boolean {
  return tsutils
    .unionConstituents(type)
    .some(type =>
      tsutils
        .intersectionConstituents(type)
        .every(subType => !isTypeLiteralLikeType(subType)),
    );
}

Internal helpers

Declared inside another function in this file.

getCommentDefaultCase(node: TSESTree.SwitchStatement): TSESTree.Comment | undefined

Parameters:

  • node TSESTree.SwitchStatement

Returns: TSESTree.Comment | undefined

Calls:

  • node.cases.at
  • context.sourceCode.getCommentsAfter
  • commentsAfterLastCase.at
  • commentRegExp.test
  • defaultCaseComment?.value.trim
Code
function getCommentDefaultCase(
      node: TSESTree.SwitchStatement,
    ): TSESTree.Comment | undefined {
      const lastCase = node.cases.at(-1);
      const commentsAfterLastCase = lastCase
        ? context.sourceCode.getCommentsAfter(lastCase)
        : [];
      const defaultCaseComment = commentsAfterLastCase.at(-1);

      if (commentRegExp.test(defaultCaseComment?.value.trim() || '')) {
        return defaultCaseComment;
      }

      return;
    }

typeToString(type: ts.Type): string

Parameters:

  • type ts.Type

Returns: string

Calls:

  • checker.typeToString
Code
function typeToString(type: ts.Type): string {
      return checker.typeToString(
        type,
        undefined,
        ts.TypeFormatFlags.AllowUniqueESSymbolType |
          ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope |
          ts.TypeFormatFlags.UseFullyQualifiedType,
      );
    }

getSwitchMetadata(node: TSESTree.SwitchStatement): SwitchMetadata

Parameters:

  • node TSESTree.SwitchStatement

Returns: SwitchMetadata

Calls:

  • node.cases.find
  • getConstrainedTypeAtLocation (from ../util)
  • discriminantType.getSymbol
  • doesTypeContainNonLiteralType
  • caseTypes.add
  • tsutils.unionConstituents
  • tsutils.intersectionConstituents
  • caseTypes.has
  • isTypeLiteralLikeType
  • [...caseTypes].some
  • tsutils.isIntrinsicUndefinedType
  • missingLiteralBranchTypes.push
  • getCommentDefaultCase

Internal Comments:

// If the `test` property of the switch case is `null`, then we are on a
// `default` case.
// "missing", "optional" and "undefined" types are different runtime objects,
// but all of them have TypeFlags.Undefined type flag

Code
function getSwitchMetadata(node: TSESTree.SwitchStatement): SwitchMetadata {
      const defaultCase = node.cases.find(
        switchCase => switchCase.test == null,
      );

      const discriminantType = getConstrainedTypeAtLocation(
        services,
        node.discriminant,
      );

      const symbolName = discriminantType.getSymbol()?.escapedName as
        string | undefined;

      const containsNonLiteralType =
        doesTypeContainNonLiteralType(discriminantType);

      const caseTypes = new Set<ts.Type>();
      for (const switchCase of node.cases) {
        // If the `test` property of the switch case is `null`, then we are on a
        // `default` case.
        if (switchCase.test == null) {
          continue;
        }

        const caseType = getConstrainedTypeAtLocation(
          services,
          switchCase.test,
        );
        caseTypes.add(caseType);
      }

      const missingLiteralBranchTypes: ts.Type[] = [];

      for (const unionPart of tsutils.unionConstituents(discriminantType)) {
        for (const intersectionPart of tsutils.intersectionConstituents(
          unionPart,
        )) {
          if (
            caseTypes.has(intersectionPart) ||
            !isTypeLiteralLikeType(intersectionPart)
          ) {
            continue;
          }

          // "missing", "optional" and "undefined" types are different runtime objects,
          // but all of them have TypeFlags.Undefined type flag
          if (
            [...caseTypes].some(tsutils.isIntrinsicUndefinedType) &&
            tsutils.isIntrinsicUndefinedType(intersectionPart)
          ) {
            continue;
          }

          missingLiteralBranchTypes.push(intersectionPart);
        }
      }

      return {
        containsNonLiteralType,
        defaultCase: defaultCase ?? getCommentDefaultCase(node),
        missingLiteralBranchTypes,
        symbolName,
      };
    }

checkSwitchExhaustive(node: TSESTree.SwitchStatement, switchMetadata: SwitchMetadata): void

Parameters:

  • node TSESTree.SwitchStatement
  • switchMetadata SwitchMetadata

Returns: void

Calls:

  • context.report
  • missingLiteralBranchTypes .map(missingType => tsutils.isTypeFlagSet(missingType, ts.TypeFlags.ESSymbolLike) ?typeof ${missingType.getSymbol()?.escapedName as string}: typeToString(missingType), ) .join
  • fixSwitch
  • symbolName?.toString

Internal Comments:

// If considerDefaultExhaustiveForUnions is enabled, the presence of a default case
// always makes the switch exhaustive.

Code
function checkSwitchExhaustive(
      node: TSESTree.SwitchStatement,
      switchMetadata: SwitchMetadata,
    ): void {
      const { defaultCase, missingLiteralBranchTypes, symbolName } =
        switchMetadata;

      // If considerDefaultExhaustiveForUnions is enabled, the presence of a default case
      // always makes the switch exhaustive.
      if (considerDefaultExhaustiveForUnions && defaultCase != null) {
        return;
      }

      if (missingLiteralBranchTypes.length > 0) {
        context.report({
          node: node.discriminant,
          messageId: 'switchIsNotExhaustive',
          data: {
            missingBranches: missingLiteralBranchTypes
              .map(missingType =>
                tsutils.isTypeFlagSet(missingType, ts.TypeFlags.ESSymbolLike)
                  ? `typeof ${missingType.getSymbol()?.escapedName as string}`
                  : typeToString(missingType),
              )
              .join(' | '),
          },
          suggest: [
            {
              messageId: 'addMissingCases',
              fix(fixer): TSESLint.RuleFix | null {
                return fixSwitch(
                  fixer,
                  node,
                  missingLiteralBranchTypes,
                  defaultCase,
                  symbolName?.toString(),
                );
              },
            },
          ],
        });
      }
    }

fixSwitch(…): TSESLint.RuleFix

Parameters:

  • fixer TSESLint.RuleFixer
  • node TSESTree.SwitchStatement
  • missingBranchTypes (ts.Type | null)[]
  • defaultCase TSESTree.Comment | TSESTree.SwitchCase | undefined
  • symbolName string

Returns: TSESLint.RuleFix

Calls:

  • ' '.repeat
  • missingCases.push
  • missingBranchType.getSymbol
  • tsutils.isTypeFlagSet
  • typeToString
  • requiresQuoting (from ../util)
  • missingBranchName.toString
  • missingBranchName .replaceAll("'", "\\'") .replaceAll('\n', '\\n') .replaceAll
  • caseTest .replaceAll('\\', '\\\\') .replaceAll
  • missingCases .map(code =>${caseIndent}${code}) .join
  • missingCases .map(code =>${code}\n${caseIndent}) .join
  • fixer.insertTextBefore
  • fixer.insertTextAfter
  • nullThrows (from ../util)
  • context.sourceCode.getTokenAfter
  • NullThrowsReasons.MissingToken
  • fixer.replaceTextRange
  • ['{', fixString,${caseIndent}}].join

Internal Comments:

// leave it to the user to format it correctly. (x3)
// There were no existing cases. (x2)

Code
function fixSwitch(
      fixer: TSESLint.RuleFixer,
      node: TSESTree.SwitchStatement,
      missingBranchTypes: (ts.Type | null)[], // null means default branch
      defaultCase: TSESTree.Comment | TSESTree.SwitchCase | undefined,
      symbolName?: string,
    ): TSESLint.RuleFix {
      const lastCase =
        node.cases.length > 0 ? node.cases[node.cases.length - 1] : null;

      const caseIndent = lastCase
        ? ' '.repeat(lastCase.loc.start.column)
        : // If there are no cases, use indentation of the switch statement and
          // leave it to the user to format it correctly.
          ' '.repeat(node.loc.start.column);

      const missingCases = [];
      for (const missingBranchType of missingBranchTypes) {
        if (missingBranchType == null) {
          missingCases.push(`default: { throw new Error('default case') }`);
          continue;
        }

        const missingBranchName = missingBranchType.getSymbol()?.escapedName;
        let caseTest = tsutils.isTypeFlagSet(
          missingBranchType,
          ts.TypeFlags.ESSymbolLike,
        )
          ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            missingBranchName!
          : typeToString(missingBranchType);

        if (
          symbolName &&
          (missingBranchName || missingBranchName === '') &&
          requiresQuoting(missingBranchName.toString(), compilerOptions.target)
        ) {
          const escapedBranchName = missingBranchName
            .replaceAll("'", "\\'")
            .replaceAll('\n', '\\n')
            .replaceAll('\r', '\\r');

          caseTest = `${symbolName}['${escapedBranchName}']`;
        }

        missingCases.push(
          `case ${caseTest}: { throw new Error('Not implemented yet: ${caseTest
            .replaceAll('\\', '\\\\')
            .replaceAll("'", "\\'")} case') }`,
        );
      }

      const fixString = missingCases
        .map(code => `${caseIndent}${code}`)
        .join('\n');

      if (lastCase) {
        if (defaultCase) {
          const beforeFixString = missingCases
            .map(code => `${code}\n${caseIndent}`)
            .join('');

          return fixer.insertTextBefore(defaultCase, beforeFixString);
        }
        return fixer.insertTextAfter(lastCase, `\n${fixString}`);
      }

      // There were no existing cases.
      const openingBrace = nullThrows(
        context.sourceCode.getTokenAfter(
          node.discriminant,
          isOpeningBraceToken,
        ),
        NullThrowsReasons.MissingToken('{', 'discriminant'),
      );
      const closingBrace = nullThrows(
        context.sourceCode.getTokenAfter(
          node.discriminant,
          isClosingBraceToken,
        ),
        NullThrowsReasons.MissingToken('}', 'discriminant'),
      );

      return fixer.replaceTextRange(
        [openingBrace.range[0], closingBrace.range[1]],
        ['{', fixString, `${caseIndent}}`].join('\n'),
      );
    }

checkSwitchUnnecessaryDefaultCase(switchMetadata: SwitchMetadata): void

Parameters:

  • switchMetadata SwitchMetadata

Returns: void

Calls:

  • context.report
Code
function checkSwitchUnnecessaryDefaultCase(
      switchMetadata: SwitchMetadata,
    ): void {
      if (allowDefaultCaseForExhaustiveSwitch) {
        return;
      }

      const { containsNonLiteralType, defaultCase, missingLiteralBranchTypes } =
        switchMetadata;

      if (
        missingLiteralBranchTypes.length === 0 &&
        defaultCase != null &&
        !containsNonLiteralType
      ) {
        context.report({
          node: defaultCase,
          messageId: 'dangerousDefaultCase',
        });
      }
    }

checkSwitchNoUnionDefaultCase(node: TSESTree.SwitchStatement, switchMetadata: SwitchMetadata): void

Parameters:

  • node TSESTree.SwitchStatement
  • switchMetadata SwitchMetadata

Returns: void

Calls:

  • context.report
  • fixSwitch
Code
function checkSwitchNoUnionDefaultCase(
      node: TSESTree.SwitchStatement,
      switchMetadata: SwitchMetadata,
    ): void {
      if (!requireDefaultForNonUnion) {
        return;
      }

      const { containsNonLiteralType, defaultCase } = switchMetadata;

      if (containsNonLiteralType && defaultCase == null) {
        context.report({
          node: node.discriminant,
          messageId: 'switchIsNotExhaustive',
          data: { missingBranches: 'default' },
          suggest: [
            {
              messageId: 'addMissingCases',
              fix(fixer): TSESLint.RuleFix {
                return fixSwitch(fixer, node, [null], defaultCase);
              },
            },
          ],
        });
      }
    }

Interfaces

SwitchMetadata

Interface Code
interface SwitchMetadata {
  readonly containsNonLiteralType: boolean;
  readonly defaultCase: TSESTree.Comment | TSESTree.SwitchCase | undefined;
  readonly missingLiteralBranchTypes: ts.Type[];
  readonly symbolName: string | undefined;
}

Properties

Name Type Optional Description
containsNonLiteralType boolean not shown
defaultCase TSESTree.Comment \| TSESTree.SwitchCase \| undefined not shown
missingLiteralBranchTypes ts.Type[] not shown
symbolName string \| undefined not shown

Type Aliases

Options

type Options = [
  {
    /**
     * If `true`, allow `default` cases on switch statements with exhaustive
     * cases.
     *
     * @default true
     */
    allowDefaultCaseForExhaustiveSwitch?: boolean;

    /**
     * If `true`, require a `default` clause for switches on non-union types.
     *
     * @default false
     */
    requireDefaultForNonUnion?: boolean;

    /**
     * Regular expression for a comment that can indicate an intentionally omitted default case.
     */
    defaultCaseCommentPattern?: string;

    /**
     * If `true`, the `default` clause is used to determine whether the switch statement is exhaustive for union types.
     *
     * @default false
     */
    considerDefaultExhaustiveForUnions?: boolean;
  },
];

MessageIds

type MessageIds = 'addMissingCases' | 'dangerousDefaultCase' | 'switchIsNotExhaustive';

Generated by Syntax Scribe