Skip to content

Commit 0b6c925

Browse files
authored
Fix import tracker for dynamic import (#41473)
* chore: migrate findAllRefs_importType_js to baseline * fix: reference for dynamic import * fix: find all reference for typeof import() * fix: test * refactor: addIndirectUser * refactor: isExported * refactor: isExported * resolve review
1 parent 9f9eed4 commit 0b6c925

13 files changed

+788
-46
lines changed

src/services/importTracker.ts

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ namespace ts.FindAllReferences {
8484

8585
switch (direct.kind) {
8686
case SyntaxKind.CallExpression:
87+
if (isImportCall(direct)) {
88+
handleImportCall(direct);
89+
break;
90+
}
8791
if (!isAvailableThroughGlobal) {
8892
const parent = direct.parent;
8993
if (exportKind === ExportKind.ExportEquals && parent.kind === SyntaxKind.VariableDeclaration) {
@@ -121,7 +125,7 @@ namespace ts.FindAllReferences {
121125
}
122126
else if (direct.exportClause.kind === SyntaxKind.NamespaceExport) {
123127
// `export * as foo from "foo"` add to indirect uses
124-
addIndirectUsers(getSourceFileLikeForImportDeclaration(direct));
128+
addIndirectUser(getSourceFileLikeForImportDeclaration(direct), /** addTransitiveDependencies */ true);
125129
}
126130
else {
127131
// This is `export { foo } from "foo"` and creates an alias symbol, so recursive search will get handle re-exports.
@@ -130,6 +134,10 @@ namespace ts.FindAllReferences {
130134
break;
131135

132136
case SyntaxKind.ImportType:
137+
// Only check for typeof import('xyz')
138+
if (direct.isTypeOf && !direct.qualifier && isExported(direct)) {
139+
addIndirectUser(direct.getSourceFile(), /** addTransitiveDependencies */ true);
140+
}
133141
directImports.push(direct);
134142
break;
135143

@@ -140,6 +148,18 @@ namespace ts.FindAllReferences {
140148
}
141149
}
142150

151+
function handleImportCall(importCall: ImportCall) {
152+
const top = findAncestor(importCall, isAmbientModuleDeclaration) || importCall.getSourceFile();
153+
addIndirectUser(top, /** addTransitiveDependencies */ !!isExported(importCall, /** stopAtAmbientModule */ true));
154+
}
155+
156+
function isExported(node: Node, stopAtAmbientModule = false) {
157+
return findAncestor(node, node => {
158+
if (stopAtAmbientModule && isAmbientModuleDeclaration(node)) return "quit";
159+
return some(node.modifiers, mod => mod.kind === SyntaxKind.ExportKeyword);
160+
});
161+
}
162+
143163
function handleNamespaceImport(importDeclaration: ImportEqualsDeclaration | ImportDeclaration, name: Identifier, isReExport: boolean, alreadyAddedDirect: boolean): void {
144164
if (exportKind === ExportKind.ExportEquals) {
145165
// This is a direct import, not import-as-namespace.
@@ -149,36 +169,30 @@ namespace ts.FindAllReferences {
149169
const sourceFileLike = getSourceFileLikeForImportDeclaration(importDeclaration);
150170
Debug.assert(sourceFileLike.kind === SyntaxKind.SourceFile || sourceFileLike.kind === SyntaxKind.ModuleDeclaration);
151171
if (isReExport || findNamespaceReExports(sourceFileLike, name, checker)) {
152-
addIndirectUsers(sourceFileLike);
172+
addIndirectUser(sourceFileLike, /** addTransitiveDependencies */ true);
153173
}
154174
else {
155175
addIndirectUser(sourceFileLike);
156176
}
157177
}
158178
}
159179

160-
function addIndirectUser(sourceFileLike: SourceFileLike): boolean {
180+
/** Adds a module and all of its transitive dependencies as possible indirect users. */
181+
function addIndirectUser(sourceFileLike: SourceFileLike, addTransitiveDependencies = false): void {
161182
Debug.assert(!isAvailableThroughGlobal);
162183
const isNew = markSeenIndirectUser(sourceFileLike);
163-
if (isNew) {
164-
indirectUserDeclarations!.push(sourceFileLike); // TODO: GH#18217
165-
}
166-
return isNew;
167-
}
168-
169-
/** Adds a module and all of its transitive dependencies as possible indirect users. */
170-
function addIndirectUsers(sourceFileLike: SourceFileLike): void {
171-
if (!addIndirectUser(sourceFileLike)) {
172-
return;
173-
}
184+
if (!isNew) return;
185+
indirectUserDeclarations!.push(sourceFileLike); // TODO: GH#18217
174186

187+
if (!addTransitiveDependencies) return;
175188
const moduleSymbol = checker.getMergedSymbol(sourceFileLike.symbol);
189+
if (!moduleSymbol) return;
176190
Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module));
177191
const directImports = getDirectImports(moduleSymbol);
178192
if (directImports) {
179193
for (const directImport of directImports) {
180194
if (!isImportTypeNode(directImport)) {
181-
addIndirectUsers(getSourceFileLikeForImportDeclaration(directImport));
195+
addIndirectUser(getSourceFileLikeForImportDeclaration(directImport), /** addTransitiveDependencies */ true);
182196
}
183197
}
184198
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// === /app.ts ===
2+
// /*FIND ALL REFS*/export function [|hello|]() {};
3+
4+
// === /indirect-use.ts ===
5+
// import("./re-export").then(mod => mod.services.app.[|hello|]());
6+
7+
// === /direct-use.ts ===
8+
// async function main() {
9+
// const mod = await import("./app")
10+
// mod.[|hello|]();
11+
// }
12+
13+
[
14+
{
15+
"definition": {
16+
"containerKind": "",
17+
"containerName": "",
18+
"fileName": "/app.ts",
19+
"kind": "function",
20+
"name": "function hello(): void",
21+
"textSpan": {
22+
"start": 16,
23+
"length": 5
24+
},
25+
"displayParts": [
26+
{
27+
"text": "function",
28+
"kind": "keyword"
29+
},
30+
{
31+
"text": " ",
32+
"kind": "space"
33+
},
34+
{
35+
"text": "hello",
36+
"kind": "functionName"
37+
},
38+
{
39+
"text": "(",
40+
"kind": "punctuation"
41+
},
42+
{
43+
"text": ")",
44+
"kind": "punctuation"
45+
},
46+
{
47+
"text": ":",
48+
"kind": "punctuation"
49+
},
50+
{
51+
"text": " ",
52+
"kind": "space"
53+
},
54+
{
55+
"text": "void",
56+
"kind": "keyword"
57+
}
58+
],
59+
"contextSpan": {
60+
"start": 0,
61+
"length": 26
62+
}
63+
},
64+
"references": [
65+
{
66+
"textSpan": {
67+
"start": 16,
68+
"length": 5
69+
},
70+
"fileName": "/app.ts",
71+
"contextSpan": {
72+
"start": 0,
73+
"length": 26
74+
},
75+
"isWriteAccess": true,
76+
"isDefinition": true
77+
},
78+
{
79+
"textSpan": {
80+
"start": 51,
81+
"length": 5
82+
},
83+
"fileName": "/indirect-use.ts",
84+
"isWriteAccess": false,
85+
"isDefinition": false
86+
},
87+
{
88+
"textSpan": {
89+
"start": 70,
90+
"length": 5
91+
},
92+
"fileName": "/direct-use.ts",
93+
"isWriteAccess": false,
94+
"isDefinition": false
95+
}
96+
]
97+
}
98+
]
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// === /app.ts ===
2+
// /*FIND ALL REFS*/export function [|hello|]() {};
3+
4+
// === /indirect-use.ts ===
5+
// import type { app } from "./re-export";
6+
// declare const app: app
7+
// app.[|hello|]();
8+
9+
[
10+
{
11+
"definition": {
12+
"containerKind": "",
13+
"containerName": "",
14+
"fileName": "/app.ts",
15+
"kind": "function",
16+
"name": "function hello(): void",
17+
"textSpan": {
18+
"start": 16,
19+
"length": 5
20+
},
21+
"displayParts": [
22+
{
23+
"text": "function",
24+
"kind": "keyword"
25+
},
26+
{
27+
"text": " ",
28+
"kind": "space"
29+
},
30+
{
31+
"text": "hello",
32+
"kind": "functionName"
33+
},
34+
{
35+
"text": "(",
36+
"kind": "punctuation"
37+
},
38+
{
39+
"text": ")",
40+
"kind": "punctuation"
41+
},
42+
{
43+
"text": ":",
44+
"kind": "punctuation"
45+
},
46+
{
47+
"text": " ",
48+
"kind": "space"
49+
},
50+
{
51+
"text": "void",
52+
"kind": "keyword"
53+
}
54+
],
55+
"contextSpan": {
56+
"start": 0,
57+
"length": 26
58+
}
59+
},
60+
"references": [
61+
{
62+
"textSpan": {
63+
"start": 16,
64+
"length": 5
65+
},
66+
"fileName": "/app.ts",
67+
"contextSpan": {
68+
"start": 0,
69+
"length": 26
70+
},
71+
"isWriteAccess": true,
72+
"isDefinition": true
73+
},
74+
{
75+
"textSpan": {
76+
"start": 67,
77+
"length": 5
78+
},
79+
"fileName": "/indirect-use.ts",
80+
"isWriteAccess": false,
81+
"isDefinition": false
82+
}
83+
]
84+
}
85+
]

0 commit comments

Comments
 (0)