Skip to content

Commit a234d00

Browse files
author
Andy
committed
moveToNewFile: Update namespace imports (#24612)
1 parent a56fe76 commit a234d00

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed

src/services/refactors/moveToNewFile.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ namespace ts.refactor {
153153
if (sourceFile === oldFile) continue;
154154
for (const statement of sourceFile.statements) {
155155
forEachImportInStatement(statement, importNode => {
156+
if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return;
157+
156158
const shouldMove = (name: Identifier): boolean => {
157159
const symbol = isBindingElement(name.parent)
158160
? getPropertySymbolFromBindingElement(checker, name.parent as BindingElement & { name: Identifier })
@@ -163,11 +165,76 @@ namespace ts.refactor {
163165
const newModuleSpecifier = combinePaths(getDirectoryPath(moduleSpecifierFromImport(importNode).text), newModuleName);
164166
const newImportDeclaration = filterImport(importNode, createLiteral(newModuleSpecifier), shouldMove);
165167
if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration);
168+
169+
const ns = getNamespaceLikeImport(importNode);
170+
if (ns) updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode);
166171
});
167172
}
168173
}
169174
}
170175

176+
function getNamespaceLikeImport(node: SupportedImport): Identifier | undefined {
177+
switch (node.kind) {
178+
case SyntaxKind.ImportDeclaration:
179+
return node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ?
180+
node.importClause.namedBindings.name : undefined;
181+
case SyntaxKind.ImportEqualsDeclaration:
182+
return node.name;
183+
case SyntaxKind.VariableDeclaration:
184+
return tryCast(node.name, isIdentifier);
185+
default:
186+
return Debug.assertNever(node);
187+
}
188+
}
189+
190+
function updateNamespaceLikeImport(
191+
changes: textChanges.ChangeTracker,
192+
sourceFile: SourceFile,
193+
checker: TypeChecker,
194+
movedSymbols: ReadonlySymbolSet,
195+
newModuleName: string,
196+
newModuleSpecifier: string,
197+
oldImportId: Identifier,
198+
oldImportNode: SupportedImport,
199+
): void {
200+
const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleName, ScriptTarget.ESNext);
201+
let needUniqueName = false;
202+
const toChange: Identifier[] = [];
203+
FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => {
204+
if (!isPropertyAccessExpression(ref.parent)) return;
205+
needUniqueName = needUniqueName || !!checker.resolveName(preferredNewNamespaceName, ref, SymbolFlags.All, /*excludeGlobals*/ true);
206+
if (movedSymbols.has(checker.getSymbolAtLocation(ref.parent.name)!)) {
207+
toChange.push(ref);
208+
}
209+
});
210+
211+
if (toChange.length) {
212+
const newNamespaceName = needUniqueName ? getUniqueName(preferredNewNamespaceName, sourceFile) : preferredNewNamespaceName;
213+
for (const ref of toChange) {
214+
changes.replaceNode(sourceFile, ref, createIdentifier(newNamespaceName));
215+
}
216+
changes.insertNodeAfter(sourceFile, oldImportNode, updateNamespaceLikeImportNode(oldImportNode, newModuleName, newModuleSpecifier));
217+
}
218+
}
219+
220+
function updateNamespaceLikeImportNode(node: SupportedImport, newNamespaceName: string, newModuleSpecifier: string): Node {
221+
const newNamespaceId = createIdentifier(newNamespaceName);
222+
const newModuleString = createLiteral(newModuleSpecifier);
223+
switch (node.kind) {
224+
case SyntaxKind.ImportDeclaration:
225+
return createImportDeclaration(
226+
/*decorators*/ undefined, /*modifiers*/ undefined,
227+
createImportClause(/*name*/ undefined, createNamespaceImport(newNamespaceId)),
228+
newModuleString);
229+
case SyntaxKind.ImportEqualsDeclaration:
230+
return createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, newNamespaceId, createExternalModuleReference(newModuleString));
231+
case SyntaxKind.VariableDeclaration:
232+
return createVariableDeclaration(newNamespaceId, /*type*/ undefined, createRequireCall(newModuleString));
233+
default:
234+
return Debug.assertNever(node);
235+
}
236+
}
237+
171238
function moduleSpecifierFromImport(i: SupportedImport): StringLiteralLike {
172239
return (i.kind === SyntaxKind.ImportDeclaration ? i.moduleSpecifier
173240
: i.kind === SyntaxKind.ImportEqualsDeclaration ? i.moduleReference.expression
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowJs: true
4+
5+
// @Filename: /a.ts
6+
////[|export const x = 0;|]
7+
////export const y = 0;
8+
9+
// @Filename: /b.ts
10+
////import * as a from "./a";
11+
////a.x;
12+
////a.y;
13+
14+
// @Filename: /c.ts
15+
////import a = require("./a");
16+
////a.x;
17+
////a.y;
18+
19+
// @Filename: /d.js
20+
////const a = require("./a");
21+
////a.x;
22+
////a.y;
23+
24+
verify.moveToNewFile({
25+
newFileContents: {
26+
"/a.ts":
27+
`export const y = 0;`,
28+
29+
"/x.ts":
30+
`export const x = 0;`,
31+
32+
"/b.ts":
33+
`import * as a from "./a";
34+
import * as x from "./x";
35+
x.x;
36+
a.y;`,
37+
38+
"/c.ts":
39+
`import a = require("./a");
40+
import x = require("./x");
41+
x.x;
42+
a.y;`,
43+
44+
"/d.js":
45+
`const a = require("./a"), x = require("./x");
46+
x.x;
47+
a.y;`,
48+
},
49+
});

0 commit comments

Comments
 (0)