Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(eslint-plugin): [switch-exhaustiveness-check] add requireDefaultForNonUnion option#7880

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
JoshuaKGoldberg merged 9 commits intotypescript-eslint:mainfromST-DDT:rule/switch-exhaustiveness-check/requireDefaultForNonUnion
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
---
description: 'Require switch-case statements to be exhaustive with union types and enums.'
description: 'Require switch-case statements to be exhaustive.'
---

> 🛑 This file is source code, not the primary documentation location! 🛑
Expand All@@ -11,6 +11,8 @@ However, if the union type or the enum changes, it's easy to forget to modify th

This rule reports when a `switch` statement over a value typed as a union of literals or as an enum is missing a case for any of those literal types and does not have a `default` clause.

There is also an option to check the exhaustiveness of switches on non-union types by requiring a default clause.

## Examples

When the switch doesn't have exhaustive cases, either filling them all out or adding a default will correct the rule's complaint.
Expand DownExpand Up@@ -179,6 +181,27 @@ switch (fruit) {

<!--/tabs-->

## Options

### `requireDefaultForNonUnion`

Examples of additional **incorrect** code for this rule with `{ requireDefaultForNonUnion: true }`:

```ts option='{ "requireDefaultForNonUnion": true }' showPlaygroundButton
const value: number = Math.floor(Math.random() * 3);

switch (value) {
case 0:
return 0;
case 1:
return 1;
}
```

Since `value` is a non-union type it requires the switch case to have a default clause only with `requireDefaultForNonUnion` enabled.

<!--/tabs-->

## When Not To Use It

If you don't frequently `switch` over union types or enums with many parts, or intentionally wish to leave out some parts.
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,25 +12,47 @@ import {
requiresQuoting,
} from '../util';

export default createRule({
type MessageIds = 'switchIsNotExhaustive' | 'addMissingCases';
type Options = [
{
/**
* If `true`, require a `default` clause for switches on non-union types.
*
* @default false
*/
requireDefaultForNonUnion?: boolean;
},
];

export default createRule<Options, MessageIds>({
name: 'switch-exhaustiveness-check',
meta: {
type: 'suggestion',
docs: {
description:
'Require switch-case statements to be exhaustive with union types and enums',
description: 'Require switch-case statements to be exhaustive',
requiresTypeChecking: true,
},
hasSuggestions: true,
schema: [],
schema: [
{
type: 'object',
additionalProperties: false,
properties: {
requireDefaultForNonUnion: {
description: `If 'true', require a 'default' clause for switches on non-union types.`,
type: 'boolean',
},
},
},
],
messages: {
switchIsNotExhaustive:
'Switch is not exhaustive. Cases not matched: {{missingBranches}}',
addMissingCases: 'Add branches for missing cases.',
},
},
defaultOptions: [],
create(context) {
defaultOptions: [{ requireDefaultForNonUnion: false }],
create(context, [{ requireDefaultForNonUnion }]) {
const sourceCode = getSourceCode(context);
const services = getParserServices(context);
const checker = services.program.getTypeChecker();
Expand All@@ -39,9 +61,9 @@ export default createRule({
function fixSwitch(
fixer: TSESLint.RuleFixer,
node: TSESTree.SwitchStatement,
missingBranchTypes: ts.Type[],
missingBranchTypes:(ts.Type | null)[], // null means default branch
symbolName?: string,
): TSESLint.RuleFix| null{
): TSESLint.RuleFix {
const lastCase =
node.cases.length > 0 ? node.cases[node.cases.length - 1] : null;
const caseIndent = lastCase
Expand All@@ -52,6 +74,10 @@ export default createRule({

const missingCases = [];
for (const missingBranchType of missingBranchTypes) {
if (missingBranchType == null) {
missingCases.push(`default: { throw new Error('default case') }`);
continue;
}
// While running this rule on checker.ts of TypeScript project
// the fix introduced a compiler error due to:
//
Expand DownExpand Up@@ -159,7 +185,7 @@ export default createRule({
suggest: [
{
messageId: 'addMissingCases',
fix(fixer): TSESLint.RuleFix| null{
fix(fixer): TSESLint.RuleFix {
return fixSwitch(
fixer,
node,
Expand All@@ -170,6 +196,28 @@ export default createRule({
},
],
});
} else if (requireDefaultForNonUnion) {
const hasDefault = node.cases.some(
switchCase => switchCase.test == null,
);

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

Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -209,6 +209,21 @@ function test(value: ObjectUnion): number {
}
}
`,
// switch with default clause on non-union type
{
code: `
declare const value: number;
switch (value) {
case 0:
return 0;
case 1:
return 1;
default:
return -1;
}
`,
options: [{ requireDefaultForNonUnion: true }],
},
],
invalid: [
{
Expand DownExpand Up@@ -595,6 +610,38 @@ function test(arg: Enum): string {
case Enum['9test']: { throw new Error('Not implemented yet: Enum[\\'9test\\'] case') }
case Enum.test: { throw new Error('Not implemented yet: Enum.test case') }
}
}
`,
},
],
},
],
},
{
code: `
const value: number = Math.floor(Math.random() * 3);
switch (value) {
case 0:
return 0;
case 1:
return 1;
}
`,
options: [{ requireDefaultForNonUnion: true }],
errors: [
{
messageId: 'switchIsNotExhaustive',
suggestions: [
{
messageId: 'addMissingCases',
output: `
const value: number = Math.floor(Math.random() * 3);
switch (value) {
case 0:
return 0;
case 1:
return 1;
default: { throw new Error('default case') }
}
`,
},
Expand Down
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.


[8]ページ先頭

©2009-2025 Movatter.jp