Skip to content

feat(eslint-plugin): prefer-consistent-enums rule #3891

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

Closed

Conversation

brianconnoly
Copy link

@brianconnoly brianconnoly commented Sep 16, 2021

Added new rule — prefer-consistent-enums

Enum members can have members of different types
Enums with string initializers can be used as object to iterate over them using Object.keys / Object.values

enum Status {
  Open = 'open',
  Closed = 'closed',
}

Object.values(Status); // ['open','closed']

But if member with number initializer will be added (or without one) — results of Object.keys / Object.values will have additional auto generated items

enum Status {
  Pending,
  Open = 'open',
  Closed = 'closed',
}

Object.values(Status); //  ["Pending", 0, "open", "closed"]
enum Status {
  Pending = 123,
  Open = 'open',
  Closed = 'closed',
}

Object.values(Status); //  ["123", "Pending", "open", "closed"]

This can lead to bugs, so this rule can prevent them by requiring enums to have consistent member types

@typescript-eslint
Copy link
Contributor

Thanks for the PR, @brianconnoly!

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. As a thank you, your profile/company logo will be added to our main README which receives thousands of unique visitors per day.

@codecov
Copy link

codecov bot commented Sep 16, 2021

Codecov Report

Merging #3891 (ee959b1) into master (b1df817) will increase coverage by 0.81%.
The diff coverage is 90.47%.

@@            Coverage Diff             @@
##           master    #3891      +/-   ##
==========================================
+ Coverage   92.70%   93.51%   +0.81%     
==========================================
  Files         329      150     -179     
  Lines       11534     8085    -3449     
  Branches     3257     2562     -695     
==========================================
- Hits        10693     7561    -3132     
+ Misses        368      165     -203     
+ Partials      473      359     -114     
Flag Coverage Δ
unittest 93.51% <90.47%> (+0.81%) ⬆️

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

Impacted Files Coverage Δ
packages/eslint-plugin/src/configs/all.ts 100.00% <ø> (ø)
...eslint-plugin/src/rules/prefer-consistent-enums.ts 90.47% <90.47%> (ø)
...rimental-utils/src/ts-eslint-scope/ScopeManager.ts
...es/experimental-utils/src/ts-eslint-scope/Scope.ts
...e-manager/src/definition/FunctionNameDefinition.ts
packages/eslint-plugin-tslint/src/rules/config.ts
packages/scope-manager/src/scope/TSModuleScope.ts
...cope-manager/src/definition/ParameterDefinition.ts
...ackages/scope-manager/src/variable/VariableBase.ts
packages/scope-manager/src/lib/es2017.string.ts
... and 173 more

@bradzacher bradzacher added the enhancement: new plugin rule New rule request for eslint-plugin label Sep 16, 2021
@JoshuaKGoldberg
Copy link
Member

LGTM in general, thanks for sending! Requesting changes on the error message and auto-fixer.

requiresTypeChecking: false,
},
messages: {
nonConsistentEnum: `All enum members of {{ name }} must be same type (string, number, boolean, etc).`,
Copy link
Member

Choose a reason for hiding this comment

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

boolean

I'm under the impression TypeScript enums can't have boolean typed values: only string or number.


if (!memberType) {
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

Indeed, this check isn't covered in tests. Perhaps it'd be good to have a test that includes an invalid enum value such as a boolean or array?

Comment on lines +76 to +79
/**
* If initializers types dont match — suggest change
*/
if (enumType !== memberType) {
Copy link
Member

Choose a reason for hiding this comment

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

Teeny nit: consider making this also return early, to reduce nesting later on? This function is pretty nested as-is.

Suggested change
/**
* If initializers types dont match suggest change
*/
if (enumType !== memberType) {
/**
* If initializers types match don't suggest change
*/
if (enumType === memberType) {
return;
}

(not a blocker IMO)

node: member,
messageId: 'nonConsistentEnum',
data: { name: enumName },
suggest: [
Copy link
Member

Choose a reason for hiding this comment

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

🤔 I'm a little -1 on suggesting such an unsafe change. In theory folks shouldn't rely on enum values but in practice it happens. eslint/eslint#7873

I'll defer to another reviewer's opinion if they feel strongly on it.

create(context) {
const sourceCode = context.getSourceCode();

function TSEnumDeclaration(node: TSESTree.TSEnumDeclaration): void {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: out of curiosity, is there a reason to have this as a standalone function instead of an inline member of the returned object?

return {
  TSEnumDeclaration(node: TSESTree.TSEnumDeclaration): void {
    // ...
  }
}

@JoshuaKGoldberg
Copy link
Member

Btw @brianconnoly we generally ask that PRs address an existing issue, to give folks a chance to discuss ideas & verify they'd be accepted before putting a lot of work into a PR. This one seems reasonable to me as a non-required rule so just maybe a heads up for next time? I'll defer to @bradzacher on whether this is a common enough use case to justify being in typescript-eslint core.

@JoshuaKGoldberg JoshuaKGoldberg added the awaiting response Issues waiting for a reply from the OP or another party label Oct 16, 2021
@bradzacher bradzacher removed the awaiting response Issues waiting for a reply from the OP or another party label Oct 18, 2021
@JoshuaKGoldberg
Copy link
Member

Oh, this actually resolves #2694, I think!

@JoshuaKGoldberg JoshuaKGoldberg added the awaiting response Issues waiting for a reply from the OP or another party label Oct 25, 2021
@osdiab
Copy link

osdiab commented Jan 3, 2022

Should this be merged?

@JoshuaKGoldberg
Copy link
Member

@osdiab I'd requested changes that haven't yet been addressed. @brianconnoly do you still have the time to work on this?

@bradzacher
Copy link
Member

At this point the PR is now 6 months stale. Closing for housekeeping.
This is not a rejection - anyone is free to re-raise the PR to continue this work!

@bradzacher bradzacher closed this Mar 4, 2022
@bradzacher bradzacher added stale PRs or Issues that are at risk of being or have been closed due to inactivity for a prolonged period awaiting response Issues waiting for a reply from the OP or another party and removed awaiting response Issues waiting for a reply from the OP or another party labels Mar 4, 2022
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 25, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
awaiting response Issues waiting for a reply from the OP or another party enhancement: new plugin rule New rule request for eslint-plugin stale PRs or Issues that are at risk of being or have been closed due to inactivity for a prolonged period
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants