Skip to content

Commit

Permalink
feat(eslint-plugin): add prefer-enum-initializers rule (#2326)
Browse files Browse the repository at this point in the history
  • Loading branch information
bobstrange committed Aug 2, 2020
1 parent 3ef6bd5 commit 4f38ea3
Show file tree
Hide file tree
Showing 6 changed files with 388 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Expand Up @@ -147,6 +147,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int
| [`@typescript-eslint/no-unused-vars-experimental`](./docs/rules/no-unused-vars-experimental.md) | Disallow unused variables and arguments | | | |
| [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements | :heavy_check_mark: | | |
| [`@typescript-eslint/prefer-as-const`](./docs/rules/prefer-as-const.md) | Prefer usage of `as const` over literal type | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/prefer-enum-initializers`](./docs/rules/prefer-enum-initializers.md) | Prefer initializing each enums member value | | | |
| [`@typescript-eslint/prefer-for-of`](./docs/rules/prefer-for-of.md) | Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated | | | |
| [`@typescript-eslint/prefer-function-type`](./docs/rules/prefer-function-type.md) | Use function types instead of interfaces with call signatures | | :wrench: | |
| [`@typescript-eslint/prefer-includes`](./docs/rules/prefer-includes.md) | Enforce `includes` method over `indexOf` method | | :wrench: | :thought_balloon: |
Expand Down
70 changes: 70 additions & 0 deletions packages/eslint-plugin/docs/rules/prefer-enum-initializers.md
@@ -0,0 +1,70 @@
# Prefer initializing each enums member value (`prefer-enum-initializers`)

This rule recommends having each `enum`s member value explicitly initialized.

`enum`s are a practical way to organize semantically related constant values. However, by implicitly defining values, `enum`s can lead to unexpected bugs if it's modified without paying attention to the order of its items.

## Rule Details

`enum`s infers sequential numbers automatically when initializers are omitted:

```ts
enum Status {
Open, // infer 0
Closed, // infer 1
}
```

If a new member is added to the top of `Status`, both `Open` and `Closed` would have its values altered:

```ts
enum Status {
Pending, // infer 0
Open, // infer 1
Closed, // infer 2
}
```

Examples of **incorrect** code for this rule:

```ts
enum Status {
Open = 1,
Close,
}

enum Direction {
Up,
Down,
}

enum Color {
Red,
Green = 'Green'
Blue = 'Blue',
}
```

Examples of **correct** code for this rule:

```ts
enum Status {
Open = 'Open',
Close = 'Close',
}

enum Direction {
Up = 1,
Down = 2,
}

enum Color {
Red = 'Red',
Green = 'Green',
Blue = 'Blue',
}
```

## When Not To Use It

If you don't care about `enum`s having implicit values you can safely disable this rule.
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/all.ts
Expand Up @@ -97,6 +97,7 @@ export = {
'@typescript-eslint/no-useless-constructor': 'error',
'@typescript-eslint/no-var-requires': 'error',
'@typescript-eslint/prefer-as-const': 'error',
'@typescript-eslint/prefer-enum-initializers': 'error',
'@typescript-eslint/prefer-for-of': 'error',
'@typescript-eslint/prefer-function-type': 'error',
'@typescript-eslint/prefer-includes': 'error',
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Expand Up @@ -70,6 +70,7 @@ import noUseBeforeDefine from './no-use-before-define';
import noUselessConstructor from './no-useless-constructor';
import noVarRequires from './no-var-requires';
import preferAsConst from './prefer-as-const';
import preferEnumInitializers from './prefer-enum-initializers';
import preferForOf from './prefer-for-of';
import preferFunctionType from './prefer-function-type';
import preferIncludes from './prefer-includes';
Expand Down Expand Up @@ -169,6 +170,7 @@ export default {
'no-useless-constructor': noUselessConstructor,
'no-var-requires': noVarRequires,
'prefer-as-const': preferAsConst,
'prefer-enum-initializers': preferEnumInitializers,
'prefer-for-of': preferForOf,
'prefer-function-type': preferFunctionType,
'prefer-includes': preferIncludes,
Expand Down
73 changes: 73 additions & 0 deletions packages/eslint-plugin/src/rules/prefer-enum-initializers.ts
@@ -0,0 +1,73 @@
import { TSESTree } from '@typescript-eslint/experimental-utils';
import * as util from '../util';
import { TSESLint } from '@typescript-eslint/experimental-utils';

type MessageIds = 'defineInitializer' | 'defineInitializerSuggestion';

export default util.createRule<[], MessageIds>({
name: 'prefer-enum-initializers',
meta: {
type: 'suggestion',
docs: {
description: 'Prefer initializing each enums member value',
category: 'Best Practices',
recommended: false,
suggestion: true,
},
messages: {
defineInitializer:
"The value of the member '{{ name }}' should be explicitly defined",
defineInitializerSuggestion:
'Can be fixed to {{ name }} = {{ suggested }}',
},
schema: [],
},
defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode();

function TSEnumDeclaration(node: TSESTree.TSEnumDeclaration): void {
const { members } = node;

members.forEach((member, index) => {
if (member.initializer == null) {
const name = sourceCode.getText(member);
context.report({
node: member,
messageId: 'defineInitializer',
data: {
name,
},
suggest: [
{
messageId: 'defineInitializerSuggestion',
data: { name, suggested: index },
fix: (fixer): TSESLint.RuleFix => {
return fixer.replaceText(member, `${name} = ${index}`);
},
},
{
messageId: 'defineInitializerSuggestion',
data: { name, suggested: index + 1 },
fix: (fixer): TSESLint.RuleFix => {
return fixer.replaceText(member, `${name} = ${index + 1}`);
},
},
{
messageId: 'defineInitializerSuggestion',
data: { name, suggested: `'${name}'` },
fix: (fixer): TSESLint.RuleFix => {
return fixer.replaceText(member, `${name} = '${name}'`);
},
},
],
});
}
});
}

return {
TSEnumDeclaration,
};
},
});

0 comments on commit 4f38ea3

Please sign in to comment.