Skip to content

Commit

Permalink
fix(type-utils): fixed TypeOrValueSpecifier not accounting for scoped…
Browse files Browse the repository at this point in the history
… DT packages (#6780)

* test(type-utils): using last node instead of first for TypeOrValueSpecifier test

* test(type-utils): added TypeOrValueSPecifier tests for package specifiers

* test(type-utils): added TypeOrValueSPecifier tests for DT package

* test(type-utils): added a failing TypeOrValueSpecifier test for an org-scoped DT package

* fix(type-utils): fixed TypeOrValueSpecifier not accounting for scoped DT packages

* fix(type-utils): fixed using String.prototype.replaceAll where it actually wasn't needed

* chore(type-utils) refactored scoped package name handling

* chore(type-utils) refactored scoped package matching to do only one pass over the package name
  • Loading branch information
marekdedic committed Apr 17, 2023
1 parent d1a4b90 commit 3350940
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 8 deletions.
23 changes: 16 additions & 7 deletions packages/type-utils/src/TypeOrValueSpecifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,21 @@ function typeDeclaredInFile(
);
}

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.replace(/^@([^/]+)\//, '$1__');
const matcher = new RegExp(
`node_modules/(?:${packageName}|${typesPackageName})/`,
);
return declarationFiles.some(declaration =>
matcher.test(declaration.fileName),
);
}

export function typeMatchesSpecifier(
type: ts.Type,
specifier: TypeOrValueSpecifier,
Expand All @@ -170,12 +185,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);
}
}
87 changes: 86 additions & 1 deletion packages/type-utils/tests/TypeOrValueSpecifier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -246,6 +247,90 @@ 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' },
],
// The following type is available from the @types/semver package.
[
'import {SemVer} from "semver"; type Test = SemVer;',
{ from: 'package', name: 'SemVer', package: 'semver' },
],
// The following type is available from the 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]>([
[
'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;',
Expand Down

0 comments on commit 3350940

Please sign in to comment.