diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts index 318af6c6d016..294adf554adb 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts @@ -38,6 +38,22 @@ export default util.createRule<[], MessageIds>({ const parserServices = util.getParserServices(context); const checker = parserServices.program.getTypeChecker(); + function getTypeForComparison(type: ts.Type): { + type: ts.Type; + typeArguments: readonly ts.Type[]; + } { + if (util.isTypeReferenceType(type)) { + return { + type: type.target, + typeArguments: util.getTypeArguments(type, checker), + }; + } + return { + type, + typeArguments: [], + }; + } + function checkTSArgsAndParameters( esParameters: TSESTree.TSTypeParameterInstantiation, typeParameters: readonly ts.TypeParameterDeclaration[], @@ -49,19 +65,30 @@ export default util.createRule<[], MessageIds>({ if (!param?.default) { return; } + // TODO: would like checker.areTypesEquivalent. https://github.com/Microsoft/TypeScript/issues/13502 const defaultType = checker.getTypeAtLocation(param.default); const argTsNode = parserServices.esTreeNodeToTSNodeMap.get(arg); const argType = checker.getTypeAtLocation(argTsNode); - if (!argType.aliasSymbol && !defaultType.aliasSymbol) { - if (argType.flags !== defaultType.flags) { + // this check should handle some of the most simple cases of like strings, numbers, etc + if (defaultType !== argType) { + // For more complex types (like aliases to generic object types) - TS won't always create a + // global shared type object for the type - so we need to resort to manually comparing the + // reference type and the passed type arguments. + // Also - in case there are aliases - we need to resolve them before we do checks + const defaultTypeResolved = getTypeForComparison(defaultType); + const argTypeResolved = getTypeForComparison(argType); + if ( + // ensure the resolved type AND all the parameters are the same + defaultTypeResolved.type !== argTypeResolved.type || + defaultTypeResolved.typeArguments.length !== + argTypeResolved.typeArguments.length || + defaultTypeResolved.typeArguments.some( + (t, i) => t !== argTypeResolved.typeArguments[i], + ) + ) { return; } - } else if ( - argType.aliasSymbol !== defaultType.aliasSymbol || - argType.aliasTypeArguments !== defaultType.aliasTypeArguments - ) { - return; } context.report({ diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-arguments.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-arguments.test.ts index dc140aade441..4005f7a54e03 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-arguments.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-arguments.test.ts @@ -132,6 +132,19 @@ import { F } from './missing'; function bar() {} bar>(); `, + ` +type A = T; +type B = A; + `, + ` +type A> = T; +type B = A>; + `, + ` +type A = Map; +type B = T; +type C2 = B>; + `, ], invalid: [ { @@ -317,5 +330,101 @@ declare module 'bar' { } `, }, + { + code: ` +type A> = T; +type B = A>; + `, + errors: [ + { + line: 3, + messageId: 'unnecessaryTypeParameter', + }, + ], + output: ` +type A> = T; +type B = A; + `, + }, + { + code: ` +type A = Map; +type B = T; +type C = B; + `, + errors: [ + { + line: 4, + messageId: 'unnecessaryTypeParameter', + }, + ], + output: ` +type A = Map; +type B = T; +type C = B; + `, + }, + { + code: ` +type A = Map; +type B = T; +type C = B>; + `, + errors: [ + { + line: 4, + messageId: 'unnecessaryTypeParameter', + }, + ], + output: ` +type A = Map; +type B = T; +type C = B; + `, + }, + { + code: ` +type A = Map; +type B = Map; +type C = T; +type D = C; + `, + errors: [ + { + line: 5, + messageId: 'unnecessaryTypeParameter', + }, + ], + output: ` +type A = Map; +type B = Map; +type C = T; +type D = C; + `, + }, + { + code: ` +type A = Map; +type B = A; +type C = Map; +type D = C; +type E = T; +type F = E; + `, + errors: [ + { + line: 7, + messageId: 'unnecessaryTypeParameter', + }, + ], + output: ` +type A = Map; +type B = A; +type C = Map; +type D = C; +type E = T; +type F = E; + `, + }, ], }); diff --git a/packages/type-utils/src/predicates.ts b/packages/type-utils/src/predicates.ts index 16afbb25ea89..c3ece8aa3c7a 100644 --- a/packages/type-utils/src/predicates.ts +++ b/packages/type-utils/src/predicates.ts @@ -54,6 +54,24 @@ export function isTypeUnknownType(type: ts.Type): boolean { return isTypeFlagSet(type, ts.TypeFlags.Unknown); } +// https://github.com/microsoft/TypeScript/blob/42aa18bf442c4df147e30deaf27261a41cbdc617/src/compiler/types.ts#L5157 +const Nullable = ts.TypeFlags.Undefined | ts.TypeFlags.Null; +// https://github.com/microsoft/TypeScript/blob/42aa18bf442c4df147e30deaf27261a41cbdc617/src/compiler/types.ts#L5187 +const ObjectFlagsType = + ts.TypeFlags.Any | + Nullable | + ts.TypeFlags.Never | + ts.TypeFlags.Object | + ts.TypeFlags.Union | + ts.TypeFlags.Intersection; +export function isTypeReferenceType(type: ts.Type): type is ts.TypeReference { + if ((type.flags & ObjectFlagsType) === 0) { + return false; + } + const objectTypeFlags = (type as ts.ObjectType).objectFlags; + return (objectTypeFlags & ts.ObjectFlags.Reference) !== 0; +} + /** * @returns true if the type is `any` */ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy

Alternative Proxy