diff --git a/packages/eslint-plugin/docs/rules/no-type-alias.md b/packages/eslint-plugin/docs/rules/no-type-alias.md index 5b720b2f331..a3b61f1da44 100644 --- a/packages/eslint-plugin/docs/rules/no-type-alias.md +++ b/packages/eslint-plugin/docs/rules/no-type-alias.md @@ -89,6 +89,7 @@ or more of the following you may pass an object with the options set as follows: - `allowLiterals` set to `"always"` will allow you to use type aliases with literal objects (Defaults to `"never"`) - `allowMappedTypes` set to `"always"` will allow you to use type aliases as mapping tools (Defaults to `"never"`) - `allowTupleTypes` set to `"always"` will allow you to use type aliases with tuples (Defaults to `"never"`) +- `allowGenerics` set to `"always"` will allow you to use type aliases with generics (Defaults to `"never"`) ### `allowAliases` @@ -555,6 +556,28 @@ type Foo = [number] & [number, number]; type Foo = [string] | [number]; ``` +### `allowGenerics` + +This applies to generic types, including TypeScript provided global utility types (`type Foo = Record`). + +The setting accepts the following options: + +- `"always"` or `"never"` to active or deactivate the feature. + +Examples of **correct** code for the `{ "allowGenerics": "always" }` options: + +```ts +type Foo = Bar; + +type Foo = Record; + +type Foo = Readonly; + +type Foo = Partial; + +type Foo = Omit; +``` + ## When Not To Use It When you can't express some shape with an interface or you need to use a union, tuple type, diff --git a/packages/eslint-plugin/src/rules/no-type-alias.ts b/packages/eslint-plugin/src/rules/no-type-alias.ts index 3e93a995e64..6cd149dd77c 100644 --- a/packages/eslint-plugin/src/rules/no-type-alias.ts +++ b/packages/eslint-plugin/src/rules/no-type-alias.ts @@ -27,6 +27,7 @@ type Options = [ allowLiterals?: Values; allowMappedTypes?: Values; allowTupleTypes?: Values; + allowGenerics?: 'always' | 'never'; }, ]; type MessageIds = 'noTypeAlias' | 'noCompositionAlias'; @@ -79,6 +80,9 @@ export default util.createRule({ allowTupleTypes: { enum: enumValues, }, + allowGenerics: { + enum: ['always', 'never'], + }, }, additionalProperties: false, }, @@ -93,6 +97,7 @@ export default util.createRule({ allowLiterals: 'never', allowMappedTypes: 'never', allowTupleTypes: 'never', + allowGenerics: 'never', }, ], create( @@ -106,6 +111,7 @@ export default util.createRule({ allowLiterals, allowMappedTypes, allowTupleTypes, + allowGenerics, }, ], ) { @@ -203,6 +209,13 @@ export default util.createRule({ return false; }; + const isValidGeneric = (type: TypeWithLabel): boolean => { + return ( + type.node.type === AST_NODE_TYPES.TSTypeReference && + type.node.typeParameters !== undefined + ); + }; + const checkAndReport = ( optionValue: Values, isTopLevel: boolean, @@ -260,6 +273,10 @@ export default util.createRule({ } else if (isValidTupleType(type)) { // tuple types checkAndReport(allowTupleTypes!, isTopLevel, type, 'Tuple Types'); + } else if (isValidGeneric(type)) { + if (allowGenerics === 'never') { + reportError(type.node, type.compositionType, isTopLevel, 'Generics'); + } } else if ( // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum type.node.type.endsWith('Keyword') || diff --git a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts index 1f44675a851..06d06f7672e 100644 --- a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts +++ b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts @@ -481,6 +481,10 @@ type KeyNames = keyof typeof SCALARS; code: 'type Foo = new (bar: number) => string | null;', options: [{ allowConstructors: 'always' }], }, + { + code: 'type Foo = Record;', + options: [{ allowGenerics: 'always' }], + }, ], invalid: [ { @@ -3329,5 +3333,18 @@ type Foo = { }, ], }, + { + code: 'type Foo = Record;', + errors: [ + { + messageId: 'noTypeAlias', + data: { + alias: 'generics', + }, + line: 1, + column: 12, + }, + ], + }, ], });