diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index f2418485d521..1d63860408f6 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -102,6 +102,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Bans specific types from being used | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforces consistent usage of type assertions | :heavy_check_mark: | | | | [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Consistent with type definition either `interface` or `type` | | :wrench: | | +| [`@typescript-eslint/enum-const-style`](./docs/rules/enum-const-style.md) | Enforce const enum style | | | | | [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | | | [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods | | | | | [`@typescript-eslint/explicit-module-boundary-types`](./docs/rules/explicit-module-boundary-types.md) | Require explicit return and argument types on exported functions' and classes' public class methods | | | | diff --git a/packages/eslint-plugin/docs/rules/enum-const-style.md b/packages/eslint-plugin/docs/rules/enum-const-style.md new file mode 100644 index 000000000000..6db4c987594e --- /dev/null +++ b/packages/eslint-plugin/docs/rules/enum-const-style.md @@ -0,0 +1,61 @@ +# Enforce const enum style (`enum-const-style`) + +This rule enforces consistent usage of `const enum`. + +## Rule Details + +This rule aims to standardize usage of const enums. + +## Options + +### Default config: `never` + +```JSON +{ + "enum-const-style": ["error", "never"] +} +``` + +Examples of **incorrect** code for this rule with `never` config: + +```ts +enum Foo { + ONE, + TWO, +} +``` + +Examples of **correct** code for this rule with `never` config: + +```ts +const enum Foo { + ONE, + TWO, +} +``` + +### `always` config + +Only const enums are allowed + +Examples of **incorrect** code for this rule with `always` config: + +```ts +enum const Foo { + ONE, + TWO, +} +``` + +Examples of **correct** code for this rule with `always` config: + +```ts +const Foo { + ONE, + TWO, +} +``` + +## When Not To Use It + +If you don't want to regulate usage of `const enum`s. diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index 91380db19320..6e367b8d58be 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -12,6 +12,7 @@ "@typescript-eslint/consistent-type-definitions": "error", "default-param-last": "off", "@typescript-eslint/default-param-last": "error", + "@typescript-eslint/enum-const-style": "error", "@typescript-eslint/explicit-function-return-type": "error", "@typescript-eslint/explicit-member-accessibility": "error", "@typescript-eslint/explicit-module-boundary-types": "error", diff --git a/packages/eslint-plugin/src/rules/enum-const-style.ts b/packages/eslint-plugin/src/rules/enum-const-style.ts new file mode 100644 index 000000000000..1f77971f7419 --- /dev/null +++ b/packages/eslint-plugin/src/rules/enum-const-style.ts @@ -0,0 +1,44 @@ +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import * as util from '../util'; + +type Messages = 'noNonConstEnums' | 'noConstEnums'; +type Options = ['always' | 'never']; + +export default util.createRule({ + name: 'enum-const-style', + meta: { + type: 'problem', + docs: { + description: 'Enforce const enum style', + category: 'Stylistic Issues', + recommended: false, + }, + messages: { + noNonConstEnums: 'enums are forbidden. Use const enums', + noConstEnums: 'const enums are forbidden. Use enum', + }, + schema: [ + { + enum: ['always', 'never'], + }, + ], + }, + defaultOptions: ['never'], + create(context, [options]) { + return { + TSEnumDeclaration(node: TSESTree.TSEnumDeclaration): void { + if (options === 'always' && !node.const) { + context.report({ + node, + messageId: 'noNonConstEnums', + }); + } else if (options === 'never' && node.const) { + context.report({ + node, + messageId: 'noConstEnums', + }); + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index ac12f27e8ba3..cb553aa0da7c 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -9,6 +9,7 @@ import classNameCasing from './class-name-casing'; import consistentTypeAssertions from './consistent-type-assertions'; import consistentTypeDefinitions from './consistent-type-definitions'; import defaultParamLast from './default-param-last'; +import enumConstStyle from './enum-const-style'; import explicitFunctionReturnType from './explicit-function-return-type'; import explicitMemberAccessibility from './explicit-member-accessibility'; import explicitModuleBoundaryTypes from './explicit-module-boundary-types'; @@ -92,6 +93,7 @@ export default { 'consistent-type-assertions': consistentTypeAssertions, 'consistent-type-definitions': consistentTypeDefinitions, 'default-param-last': defaultParamLast, + 'enum-const-style': enumConstStyle, 'explicit-function-return-type': explicitFunctionReturnType, 'explicit-member-accessibility': explicitMemberAccessibility, 'explicit-module-boundary-types': explicitModuleBoundaryTypes, diff --git a/packages/eslint-plugin/tests/rules/enum-const-style.test.ts b/packages/eslint-plugin/tests/rules/enum-const-style.test.ts new file mode 100644 index 000000000000..b7c587a09799 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/enum-const-style.test.ts @@ -0,0 +1,81 @@ +import rule from '../../src/rules/enum-const-style'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('enum-style', rule, { + valid: [ + 'enum Foo {}', + 'enum Foo { FOO }', + 'enum Foo { FOO = 1, BAR = 2 }', + 'enum Foo { FOO = "FOO", BAR = "BAR" }', + { + code: 'const enum Foo {}', + options: ['always'], + }, + { + code: 'const enum Foo { FOO }', + options: ['always'], + }, + { + code: 'const enum Foo { FOO = 1, BAR = 2 }', + options: ['always'], + }, + { + code: 'const enum Foo { FOO = "FOO", BAR = "BAR" }', + options: ['always'], + }, + { + code: 'enum Foo {}', + options: ['never'], + }, + { + code: 'enum Foo { FOO }', + options: ['never'], + }, + { + code: 'enum Foo { FOO = 1, BAR = 2 }', + options: ['never'], + }, + { + code: 'enum Foo { FOO = "FOO", BAR = "BAR" }', + options: ['never'], + }, + ], + invalid: [ + { + code: 'const enum Foo {}', + errors: [ + { + messageId: 'noConstEnums', + line: 1, + column: 1, + }, + ], + }, + { + code: 'const enum Foo {}', + errors: [ + { + messageId: 'noConstEnums', + line: 1, + column: 1, + }, + ], + options: ['never'], + }, + { + code: 'enum Foo {}', + errors: [ + { + messageId: 'noNonConstEnums', + line: 1, + column: 1, + }, + ], + options: ['always'], + }, + ], +});