Skip to content

feat(eslint-plugin): [switch-exhaustiveness-check] add considerDefaultExhaustiveForUnions option #9954

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

Conversation

developer-bandi
Copy link
Contributor

@developer-bandi developer-bandi commented Sep 8, 2024

PR Checklist

Overview

  • add considerDefaultExhaustiveForUnions option to dismiss default case when decide exhaustiveness
  • In this situation, even if there is a default case, an error must be added to the key that does not exist, so we changed some of the code modification logic.

@typescript-eslint
Copy link
Contributor

Thanks for the PR, @developer-bandi!

typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community.

The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately.

Thanks again!


🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint.

Copy link

netlify bot commented Sep 8, 2024

Deploy Preview for typescript-eslint ready!

Name Link
🔨 Latest commit 76c8716
🔍 Latest deploy log https://app.netlify.com/sites/typescript-eslint/deploys/671e5622186e610008fce626
😎 Deploy Preview https://deploy-preview-9954--typescript-eslint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 99 (🟢 up 5 from production)
Accessibility: 100 (no change from production)
Best Practices: 92 (no change from production)
SEO: 98 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify site configuration.

@developer-bandi developer-bandi changed the title feat(eslint-plugin):[switch-exhaustiveness-check] add allowDefaultCaseMatchUnionMember option feat(eslint-plugin): [switch-exhaustiveness-check] add allowDefaultCaseMatchUnionMember option Sep 8, 2024
Copy link

codecov bot commented Sep 8, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 86.19%. Comparing base (74ace4d) to head (76c8716).
Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #9954   +/-   ##
=======================================
  Coverage   86.19%   86.19%           
=======================================
  Files         430      430           
  Lines       15064    15071    +7     
  Branches     4371     4373    +2     
=======================================
+ Hits        12984    12991    +7     
  Misses       1728     1728           
  Partials      352      352           
Flag Coverage Δ
unittest 86.19% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...nt-plugin/src/rules/switch-exhaustiveness-check.ts 98.78% <100.00%> (+0.11%) ⬆️

Copy link
Member

@JoshuaKGoldberg JoshuaKGoldberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🆒

@JoshuaKGoldberg JoshuaKGoldberg added the awaiting response Issues waiting for a reply from the OP or another party label Sep 30, 2024
@github-actions github-actions bot removed the awaiting response Issues waiting for a reply from the OP or another party label Oct 3, 2024
Copy link
Member

@kirkwaiblinger kirkwaiblinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code logic looks great to me, but some cleanup is needed for the option naming and docs.

allowDefaultCaseMatchUnionMember is ungrammatical, and requireDefaultCaseForUnions is not accurate, to my understanding.

Maybe requireExplicitCasesForUnion or considerDefaultExhaustiveForUnion depending on which direction the true/false goes?

@@ -53,6 +53,29 @@ switch (value) {

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

### `allowDefaultCaseMatchUnionMember`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

option name in docs differs from code

@@ -155,10 +173,13 @@ export default createRule<Options, MessageIds>({
const { missingLiteralBranchTypes, symbolName, defaultCase } =
switchMetadata;

// We only trigger the rule if a `default` case does not exist, since that
// would disqualify the switch statement from having cases that exactly
// We only trigger the rule if a `default` case does not exist when requireDefaultCaseForUnions option
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(clarity nit)

Maybe just have a standalone condition like

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

@@ -243,6 +264,15 @@ export default createRule<Options, MessageIds>({
.join('\n');

if (lastCase) {
const isLastCaseDefaultCase = lastCase.test == null;
if (isLastCaseDefaultCase) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(optional)

food for thought - what if the default case isn't the last case?

This change makes it so that we avoid suggesting to fix

declare const literal: 'a' | 'b';

switch (literal) {
  case 'b':
  default:
}

to

declare const literal: 'a' | 'b';

switch (literal) {
  case 'b':
  default:
  case "a": { throw new Error('Not implemented yet: "a" case') }
}

and instead fix it to

declare const literal: 'a' | 'b';

switch (literal) {
  case 'b':
  case "a": { throw new Error('Not implemented yet: "a" case') }
  default:
}

. (also note that this has its own issue of no longer having case 'b' be handled the same as default, instead having it throw as not implemented yet).

But it will happily fix

declare const literal: 'a' | 'b';

switch (literal) {
  default:
  case 'b':
}

to

declare const literal: 'a' | 'b';

switch (literal) {
  default:
  case 'b':
  case "a": { throw new Error('Not implemented yet: "a" case') }
}

which still has the added case as part of the default 🤔, even though the first examples implies this code is trying to avoid it. Maybe to be more consistent we could say "if default case is present, add it above it, else add it at the end"?


I don't think that this is super important since the user will have to do manual cleanup regardless, but just throwing this out there in case you want to tweak some edge cases. I'm ok with either way though.

@@ -65,6 +72,10 @@ export default createRule<Options, MessageIds>({
description: `If 'true', require a 'default' clause for switches on non-union types.`,
type: 'boolean',
},
requireDefaultCaseForUnions: {
description: `If 'true', the 'default' clause is used to determine whether the switch statement is Exhaustive for union type`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description: `If 'true', the 'default' clause is used to determine whether the switch statement is Exhaustive for union type`,
description: `If 'true', the 'default' clause is used to determine whether the switch statement is exhaustive for union type`,

@kirkwaiblinger
Copy link
Member

kirkwaiblinger commented Oct 9, 2024

(Further thoughts on naming and options) -

My understanding is that there are two independent things going on with union types and defaults.

  1. When all union constituents do have their own explicit case, whether a default case is prohibited|ignored|required. This is currently controlled by the existing allowDefaultCaseForExhaustiveSwitch option, and the only possibilities currently supported by this rule are prohibited|ignored (If you want the "required" behavior you can use the separate rule default-case).

  2. When some union constituents do not have their own explicit case, whether a default case will silence the rule. This is the new option being created.

Perhaps thinking about it in this framework can help clarify the docs and the naming.

When I read requireDefaultCaseForUnions, that describes the behavior in 1., not in 2.

@kirkwaiblinger kirkwaiblinger added awaiting response Issues waiting for a reply from the OP or another party enhancement New feature or request labels Oct 9, 2024
@developer-bandi
Copy link
Contributor Author

developer-bandi commented Oct 10, 2024

As for the option name, the considerDefaultExhaustiveForUnion you suggested looks good.

It seems that including both Default and Exhaustive keywords is more appropriate in the context you explained.

  • Once the option name is decided, related modifications will be reflected.

@github-actions github-actions bot removed the awaiting response Issues waiting for a reply from the OP or another party label Oct 10, 2024
@kirkwaiblinger
Copy link
Member

looks pretty close. Let's just change the option name to considerDefaultExhaustiveForUnions and if anyone else has a conflicting opinion they can object before it gets merged 👍


Defaults to false. Whether a `default` clause will be considered as making a switch statement over a union type exhaustive.

By default, if a `switch` statement over a union type includes a `default` case, then it's considered exhaustive.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the opposite way (since the option defaults to false). By default, every branch will be required to be explicitly handled, and one may opt out by enabling considerDefaultExhaustiveForUnions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you I think I missed changing it at the same time when changing the default value.

Copy link
Member

@kirkwaiblinger kirkwaiblinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions github-actions bot removed the awaiting response Issues waiting for a reply from the OP or another party label Oct 19, 2024
kirkwaiblinger
kirkwaiblinger previously approved these changes Oct 27, 2024
Copy link
Member

@kirkwaiblinger kirkwaiblinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good stuff! I'm super excited to get this change released! This has been a pain point for me as a user of the rule, and I'm glad to see it addressed 🙂

FYI - I've made a few edits to your branch primarily in order to get the website to build (we added in the {/* insert option description */} thing recently, which prevented the CI from building).

@kirkwaiblinger kirkwaiblinger added the 1 approval >=1 team member has approved this PR; we're now leaving it open for more reviews before we merge label Oct 27, 2024
@kirkwaiblinger kirkwaiblinger requested review from JoshuaKGoldberg and removed request for JoshuaKGoldberg October 27, 2024 07:14
@github-actions github-actions bot removed the 1 approval >=1 team member has approved this PR; we're now leaving it open for more reviews before we merge label Oct 27, 2024
Copy link
Member

@JoshuaKGoldberg JoshuaKGoldberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :)

@JoshuaKGoldberg JoshuaKGoldberg dismissed stale reviews from kirkwaiblinger and themself via 3e1a24e October 27, 2024 15:00
@JoshuaKGoldberg JoshuaKGoldberg merged commit 3c8978d into typescript-eslint:main Oct 27, 2024
62 checks passed
@kirkwaiblinger kirkwaiblinger changed the title feat(eslint-plugin): [switch-exhaustiveness-check] add allowDefaultCaseMatchUnionMember option feat(eslint-plugin): [switch-exhaustiveness-check] add considerDefaultExhaustiveForUnion option Oct 28, 2024
@kirkwaiblinger kirkwaiblinger changed the title feat(eslint-plugin): [switch-exhaustiveness-check] add considerDefaultExhaustiveForUnion option feat(eslint-plugin): [switch-exhaustiveness-check] add considerDefaultExhaustiveForUnions option Oct 28, 2024
renovate bot added a commit to mmkal/eslint-plugin-mmkal that referenced this pull request Oct 28, 2024
##### [v8.12.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8120-2024-10-28)

##### 🚀 Features

-   **eslint-plugin:** \[no-base-to-string] handle String() ([#10005](typescript-eslint/typescript-eslint#10005))
-   **eslint-plugin:** \[switch-exhaustiveness-check] add allowDefaultCaseMatchUnionMember option ([#9954](typescript-eslint/typescript-eslint#9954))
-   **eslint-plugin:** \[consistent-indexed-object-style] report mapped types ([#10160](typescript-eslint/typescript-eslint#10160))
-   **eslint-plugin:** \[prefer-nullish-coalescing] add support for assignment expressions ([#10152](typescript-eslint/typescript-eslint#10152))

##### ❤️  Thank You

-   Abraham Guo
-   Kim Sang Du [@developer-bandi](https://github.com/developer-bandi)
-   Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger)
-   YeonJuan [@yeonjuan](https://github.com/yeonjuan)

You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.
@JoshuaKGoldberg JoshuaKGoldberg changed the title feat(eslint-plugin): [switch-exhaustiveness-check] add considerDefaultExhaustiveForUnions option fix(eslint-plugin): [switch-exhaustiveness-check] add considerDefaultExhaustiveForUnions option Oct 29, 2024
@JoshuaKGoldberg JoshuaKGoldberg changed the title fix(eslint-plugin): [switch-exhaustiveness-check] add considerDefaultExhaustiveForUnions option feat(eslint-plugin): [switch-exhaustiveness-check] add considerDefaultExhaustiveForUnions option Oct 29, 2024
renovate bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Oct 30, 2024
| datasource | package                          | from   | to     |
| ---------- | -------------------------------- | ------ | ------ |
| npm        | @typescript-eslint/eslint-plugin | 8.11.0 | 8.12.2 |
| npm        | @typescript-eslint/parser        | 8.11.0 | 8.12.2 |


## [v8.12.2](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8122-2024-10-29)

##### 🩹 Fixes

-   **eslint-plugin:** \[switch-exhaustiveness-check] invert `considerDefaultExhaustiveForUnions` ([#10223](typescript-eslint/typescript-eslint#10223))

##### ❤️  Thank You

-   Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger)

You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.


## [v8.12.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8121-2024-10-28)

This was a version bump only for eslint-plugin to align it with other projects, there were no code changes.

You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.


## [v8.12.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8120-2024-10-28)

##### 🚀 Features

-   **eslint-plugin:** \[no-base-to-string] handle String() ([#10005](typescript-eslint/typescript-eslint#10005))
-   **eslint-plugin:** \[switch-exhaustiveness-check] add allowDefaultCaseMatchUnionMember option ([#9954](typescript-eslint/typescript-eslint#9954))
-   **eslint-plugin:** \[consistent-indexed-object-style] report mapped types ([#10160](typescript-eslint/typescript-eslint#10160))
-   **eslint-plugin:** \[prefer-nullish-coalescing] add support for assignment expressions ([#10152](typescript-eslint/typescript-eslint#10152))

##### ❤️  Thank You

-   Abraham Guo
-   Kim Sang Du [@developer-bandi](https://github.com/developer-bandi)
-   Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger)
-   YeonJuan [@yeonjuan](https://github.com/yeonjuan)

You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.
renovate bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Oct 30, 2024
| datasource | package                          | from   | to     |
| ---------- | -------------------------------- | ------ | ------ |
| npm        | @typescript-eslint/eslint-plugin | 8.11.0 | 8.12.2 |
| npm        | @typescript-eslint/parser        | 8.11.0 | 8.12.2 |


## [v8.12.2](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8122-2024-10-29)

##### 🩹 Fixes

-   **eslint-plugin:** \[switch-exhaustiveness-check] invert `considerDefaultExhaustiveForUnions` ([#10223](typescript-eslint/typescript-eslint#10223))

##### ❤️  Thank You

-   Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger)

You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.


## [v8.12.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8121-2024-10-28)

This was a version bump only for eslint-plugin to align it with other projects, there were no code changes.

You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.


## [v8.12.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8120-2024-10-28)

##### 🚀 Features

-   **eslint-plugin:** \[no-base-to-string] handle String() ([#10005](typescript-eslint/typescript-eslint#10005))
-   **eslint-plugin:** \[switch-exhaustiveness-check] add allowDefaultCaseMatchUnionMember option ([#9954](typescript-eslint/typescript-eslint#9954))
-   **eslint-plugin:** \[consistent-indexed-object-style] report mapped types ([#10160](typescript-eslint/typescript-eslint#10160))
-   **eslint-plugin:** \[prefer-nullish-coalescing] add support for assignment expressions ([#10152](typescript-eslint/typescript-eslint#10152))

##### ❤️  Thank You

-   Abraham Guo
-   Kim Sang Du [@developer-bandi](https://github.com/developer-bandi)
-   Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger)
-   YeonJuan [@yeonjuan](https://github.com/yeonjuan)

You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.
renovate bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Oct 31, 2024
| datasource | package                          | from   | to     |
| ---------- | -------------------------------- | ------ | ------ |
| npm        | @typescript-eslint/eslint-plugin | 8.11.0 | 8.12.2 |
| npm        | @typescript-eslint/parser        | 8.11.0 | 8.12.2 |


## [v8.12.2](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8122-2024-10-29)

##### 🩹 Fixes

-   **eslint-plugin:** \[switch-exhaustiveness-check] invert `considerDefaultExhaustiveForUnions` ([#10223](typescript-eslint/typescript-eslint#10223))

##### ❤️  Thank You

-   Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger)

You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.


## [v8.12.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8121-2024-10-28)

This was a version bump only for eslint-plugin to align it with other projects, there were no code changes.

You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.


## [v8.12.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8120-2024-10-28)

##### 🚀 Features

-   **eslint-plugin:** \[no-base-to-string] handle String() ([#10005](typescript-eslint/typescript-eslint#10005))
-   **eslint-plugin:** \[switch-exhaustiveness-check] add allowDefaultCaseMatchUnionMember option ([#9954](typescript-eslint/typescript-eslint#9954))
-   **eslint-plugin:** \[consistent-indexed-object-style] report mapped types ([#10160](typescript-eslint/typescript-eslint#10160))
-   **eslint-plugin:** \[prefer-nullish-coalescing] add support for assignment expressions ([#10152](typescript-eslint/typescript-eslint#10152))

##### ❤️  Thank You

-   Abraham Guo
-   Kim Sang Du [@developer-bandi](https://github.com/developer-bandi)
-   Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger)
-   YeonJuan [@yeonjuan](https://github.com/yeonjuan)

You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 6, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enhancement: [switch-exhaustiveness-check] Add ability to ignore default case
3 participants