From 31b48560991a27f5f0fa3ae56101b3e40353bbca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Mon, 27 Mar 2023 22:54:18 +0200 Subject: [PATCH 1/8] test(type-utils): using last node instead of first for TypeOrValueSpecifier test --- packages/type-utils/tests/TypeOrValueSpecifier.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index d768911528a3..200ea0a33f01 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -139,7 +139,8 @@ describe('TypeOrValueSpecifier', () => { .program!.getTypeChecker() .getTypeAtLocation( services.esTreeNodeToTSNodeMap.get( - (ast.body[0] as TSESTree.TSTypeAliasDeclaration).id, + (ast.body[ast.body.length - 1] as TSESTree.TSTypeAliasDeclaration) + .id, ), ); expect(typeMatchesSpecifier(type, specifier, services.program!)).toBe( From 0da275e5c1f0927b1e44bcdc4115a0114640a2ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Mon, 27 Mar 2023 23:02:20 +0200 Subject: [PATCH 2/8] test(type-utils): added TypeOrValueSPecifier tests for package specifiers --- .../tests/TypeOrValueSpecifier.test.ts | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index 200ea0a33f01..02eb1db59d40 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -233,6 +233,76 @@ describe('TypeOrValueSpecifier', () => { ['type Test = RegExp;', { from: 'lib', name: ['BigInt', 'Date'] }], ])("doesn't match a mismatched lib specifier: %s", runTestNegative); + it.each<[string, TypeOrValueSpecifier]>([ + [ + 'import type {Node} from "typescript"; type Test = Node;', + { from: 'package', name: 'Node', package: 'typescript' }, + ], + [ + 'import type {Node} from "typescript"; type Test = Node;', + { from: 'package', name: ['Node', 'Symbol'], package: 'typescript' }, + ], + [ + 'import {Node} from "typescript"; type Test = Node;', + { from: 'package', name: 'Node', package: 'typescript' }, + ], + [ + 'import {Node} from "typescript"; type Test = Node;', + { from: 'package', name: ['Node', 'Symbol'], package: 'typescript' }, + ], + [ + 'import * as ts from "typescript"; type Test = ts.Node;', + { from: 'package', name: 'Node', package: 'typescript' }, + ], + [ + 'import * as ts from "typescript"; type Test = ts.Node;', + { from: 'package', name: ['Node', 'Symbol'], package: 'typescript' }, + ], + [ + 'import type * as ts from "typescript"; type Test = ts.Node;', + { from: 'package', name: 'Node', package: 'typescript' }, + ], + [ + 'import type * as ts from "typescript"; type Test = ts.Node;', + { from: 'package', name: ['Node', 'Symbol'], package: 'typescript' }, + ], + [ + 'import type {Node as TsNode} from "typescript"; type Test = TsNode;', + { from: 'package', name: 'Node', package: 'typescript' }, + ], + [ + 'import type {Node as TsNode} from "typescript"; type Test = TsNode;', + { from: 'package', name: ['Node', 'Symbol'], package: 'typescript' }, + ], + ])('matches a matching package specifier: %s', runTestPositive); + + it.each<[string, TypeOrValueSpecifier]>([ + [ + 'import type {Node} from "typescript"; type Test = Node;', + { from: 'package', name: 'Symbol', package: 'typescript' }, + ], + [ + 'import type {Node} from "typescript"; type Test = Node;', + { from: 'package', name: ['Symbol', 'Checker'], package: 'typescript' }, + ], + [ + 'import type {Node} from "typescript"; type Test = Node;', + { from: 'package', name: 'Node', package: 'other-package' }, + ], + [ + 'import type {Node} from "typescript"; type Test = Node;', + { from: 'package', name: ['Node', 'Symbol'], package: 'other-package' }, + ], + [ + 'interface Node {prop: string}; type Test = Node;', + { from: 'package', name: 'Node', package: 'typescript' }, + ], + [ + 'import type {Node as TsNode} from "typescript"; type Test = TsNode;', + { from: 'package', name: 'TsNode', package: 'typescript' }, + ], + ])("doesn't match a mismatched lib specifier: %s", runTestNegative); + it.each<[string, TypeOrValueSpecifier]>([ [ 'interface Foo {prop: string}; type Test = Foo;', From d68d428905c221f50b5d6d79307627829dc18ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Mon, 27 Mar 2023 23:13:56 +0200 Subject: [PATCH 3/8] test(type-utils): added TypeOrValueSPecifier tests for DT package --- packages/type-utils/tests/TypeOrValueSpecifier.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index 02eb1db59d40..7fcf92008f44 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -274,6 +274,11 @@ describe('TypeOrValueSpecifier', () => { 'import type {Node as TsNode} from "typescript"; type Test = TsNode;', { from: 'package', name: ['Node', 'Symbol'], package: 'typescript' }, ], + // The following type is available from the @types/semver package. + [ + 'import {SemVer} from "semver"; type Test = SemVer;', + { from: 'package', name: 'SemVer', package: 'semver' }, + ], ])('matches a matching package specifier: %s', runTestPositive); it.each<[string, TypeOrValueSpecifier]>([ From cffd1b228f97b29f2004bb375c8642a0de838291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Mon, 27 Mar 2023 23:21:28 +0200 Subject: [PATCH 4/8] test(type-utils): added a failing TypeOrValueSpecifier test for an org-scoped DT package --- packages/type-utils/tests/TypeOrValueSpecifier.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index 7fcf92008f44..ceff3085d2f4 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -279,6 +279,15 @@ describe('TypeOrValueSpecifier', () => { 'import {SemVer} from "semver"; type Test = SemVer;', { from: 'package', name: 'SemVer', package: 'semver' }, ], + // The following type is available from the org-scoped @types/babel__code-frame package. + [ + 'import {BabelCodeFrameOptions} from "@babel/code-frame"; type Test = BabelCodeFrameOptions;', + { + from: 'package', + name: 'BabelCodeFrameOptions', + package: '@babel/code-frame', + }, + ], ])('matches a matching package specifier: %s', runTestPositive); it.each<[string, TypeOrValueSpecifier]>([ From a29d60d270c04a064ec994a7bf0372003c195574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Mon, 27 Mar 2023 23:40:24 +0200 Subject: [PATCH 5/8] fix(type-utils): fixed TypeOrValueSpecifier not accounting for scoped DT packages --- .../type-utils/src/TypeOrValueSpecifier.ts | 24 +++++++++++++------ .../tests/TypeOrValueSpecifier.test.ts | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index 3ba6b02868d9..0918b57f24a0 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -146,6 +146,22 @@ function typeDeclaredInFile( ); } +function typeDeclaredInPackage( + packageName: string, + declarationFiles: ts.SourceFile[], +): boolean { + const typesPackageName = + '@types/' + + (packageName[0] === '@' + ? packageName.substring(1).replaceAll('/', '__') + : packageName); + return declarationFiles.some( + declaration => + declaration.fileName.includes(`node_modules/${packageName}/`) || + declaration.fileName.includes(`node_modules/${typesPackageName}/`), + ); +} + export function typeMatchesSpecifier( type: ts.Type, specifier: TypeOrValueSpecifier, @@ -170,12 +186,6 @@ export function typeMatchesSpecifier( program.isSourceFileDefaultLibrary(declaration), ); case 'package': - return declarationFiles.some( - declaration => - declaration.fileName.includes(`node_modules/${specifier.package}/`) || - declaration.fileName.includes( - `node_modules/@types/${specifier.package}/`, - ), - ); + return typeDeclaredInPackage(specifier.package, declarationFiles); } } diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index ceff3085d2f4..c64ce7e6bac3 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -279,7 +279,7 @@ describe('TypeOrValueSpecifier', () => { 'import {SemVer} from "semver"; type Test = SemVer;', { from: 'package', name: 'SemVer', package: 'semver' }, ], - // The following type is available from the org-scoped @types/babel__code-frame package. + // The following type is available from the scoped @types/babel__code-frame package. [ 'import {BabelCodeFrameOptions} from "@babel/code-frame"; type Test = BabelCodeFrameOptions;', { From bbd46624aa4369d2d302bbe05f35f395fcfeb616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Mon, 27 Mar 2023 23:54:41 +0200 Subject: [PATCH 6/8] fix(type-utils): fixed using String.prototype.replaceAll where it actually wasn't needed --- packages/type-utils/src/TypeOrValueSpecifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index 0918b57f24a0..f59267a7f01c 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -153,7 +153,7 @@ function typeDeclaredInPackage( const typesPackageName = '@types/' + (packageName[0] === '@' - ? packageName.substring(1).replaceAll('/', '__') + ? packageName.substring(1).replace('/', '__') : packageName); return declarationFiles.some( declaration => From d0b5e15f4492a829b2d893c14e245073e5c8650b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Mon, 17 Apr 2023 13:39:05 +0200 Subject: [PATCH 7/8] chore(type-utils) refactored scoped package name handling --- packages/type-utils/src/TypeOrValueSpecifier.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index f59267a7f01c..e54686d768cf 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -150,11 +150,9 @@ function typeDeclaredInPackage( packageName: string, declarationFiles: ts.SourceFile[], ): boolean { + // Handle scoped packages - if the name starts with @, remove it and replace / with __ const typesPackageName = - '@types/' + - (packageName[0] === '@' - ? packageName.substring(1).replace('/', '__') - : packageName); + '@types/' + packageName.replace(/^@([^/]+)\//, '$1__'); return declarationFiles.some( declaration => declaration.fileName.includes(`node_modules/${packageName}/`) || From e97628f55f2d7424a5451b3764d382a2d2e90f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Mon, 17 Apr 2023 13:42:51 +0200 Subject: [PATCH 8/8] chore(type-utils) refactored scoped package matching to do only one pass over the package name --- packages/type-utils/src/TypeOrValueSpecifier.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index e54686d768cf..34e18ac5f5ce 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -153,10 +153,11 @@ function typeDeclaredInPackage( // Handle scoped packages - if the name starts with @, remove it and replace / with __ const typesPackageName = '@types/' + packageName.replace(/^@([^/]+)\//, '$1__'); - return declarationFiles.some( - declaration => - declaration.fileName.includes(`node_modules/${packageName}/`) || - declaration.fileName.includes(`node_modules/${typesPackageName}/`), + const matcher = new RegExp( + `node_modules/(?:${packageName}|${typesPackageName})/`, + ); + return declarationFiles.some(declaration => + matcher.test(declaration.fileName), ); }