diff --git a/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts b/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts index 97e7813c6a3a..ac7000f11acb 100644 --- a/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts +++ b/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts @@ -1,18 +1,26 @@ +import * as tsutils from 'ts-api-utils'; import type * as ts from 'typescript'; export function specifierNameMatches( type: ts.Type, - name: string[] | string, + names: string[] | string, ): boolean { - if (typeof name === 'string') { - name = [name]; + if (typeof names === 'string') { + names = [names]; } - if (name.some(item => item === type.intrinsicName)) { + + const symbol = type.aliasSymbol ?? type.getSymbol(); + const candidateNames = symbol + ? [symbol.escapedName as string, type.intrinsicName] + : [type.intrinsicName]; + + if (names.some(item => candidateNames.includes(item))) { return true; } - const symbol = type.aliasSymbol ?? type.getSymbol(); - if (symbol === undefined) { - return false; + + if (tsutils.isIntersectionType(type)) { + return type.types.some(subType => specifierNameMatches(subType, names)); } - return name.some(item => (item as ts.__String) === symbol.escapedName); + + return false; } diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index fa85b93d0cdb..e2bc32acc2e5 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -369,6 +369,42 @@ describe('TypeOrValueSpecifier', () => { ], ])('matches a matching package specifier: %s', runTestPositive); + it.each<[string, TypeOrValueSpecifier]>([ + [ + ` + type Other = { __otherBrand: true }; + type SafePromise = Promise & { __safeBrand: string }; + type JoinedPromise = SafePromise & {}; + `, + { from: 'file', name: ['Other'] }, + ], + // The SafePromise alias acts as an actual alias ("cut-and-paste"). I.e.: + // type JoinedPromise = Promise & { __safeBrand: string }; + [ + ` + type SafePromise = Promise & { __safeBrand: string }; + type JoinedPromise = SafePromise & {}; + `, + { from: 'file', name: ['SafePromise'] }, + ], + ])( + "doesn't match a mismatched type specifier for an intersection type: %s", + runTestNegative, + ); + + it.each<[string, TypeOrValueSpecifier]>([ + [ + ` + type SafePromise = Promise & { __safeBrand: string }; + type JoinedPromise = SafePromise & {}; + `, + { from: 'file', name: ['JoinedPromise'] }, + ], + ])( + 'matches a matching type specifier for an intersection type: %s', + runTestPositive, + ); + it("does not match a `declare global` with the 'global' package name", () => { runTestNegative( `