forked from microsoft/TypeScript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconvertImport.ts
More file actions
130 lines (115 loc) · 7.68 KB
/
convertImport.ts
File metadata and controls
130 lines (115 loc) · 7.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* @internal */
namespace ts.refactor {
const refactorName = "Convert import";
const actionNameNamespaceToNamed = "Convert namespace import to named imports";
const actionNameNamedToNamespace = "Convert named imports to namespace import";
registerRefactor(refactorName, {
getAvailableActions(context): readonly ApplicableRefactorInfo[] {
const i = getImportToConvert(context);
if (!i) return emptyArray;
const description = i.kind === SyntaxKind.NamespaceImport ? Diagnostics.Convert_namespace_import_to_named_imports.message : Diagnostics.Convert_named_imports_to_namespace_import.message;
const actionName = i.kind === SyntaxKind.NamespaceImport ? actionNameNamespaceToNamed : actionNameNamedToNamespace;
return [{ name: refactorName, description, actions: [{ name: actionName, description }] }];
},
getEditsForAction(context, actionName): RefactorEditInfo {
Debug.assert(actionName === actionNameNamespaceToNamed || actionName === actionNameNamedToNamespace, "Unexpected action name");
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, t, Debug.assertDefined(getImportToConvert(context), "Context must provide an import to convert")));
return { edits, renameFilename: undefined, renameLocation: undefined };
}
});
// Can convert imports of the form `import * as m from "m";` or `import d, { x, y } from "m";`.
function getImportToConvert(context: RefactorContext): NamedImportBindings | undefined {
const { file } = context;
const span = getRefactorContextSpan(context);
const token = getTokenAtPosition(file, span.start);
const importDecl = getParentNodeInSpan(token, file, span);
if (!importDecl || !isImportDeclaration(importDecl)) return undefined;
const { importClause } = importDecl;
return importClause && importClause.namedBindings;
}
function doChange(sourceFile: SourceFile, program: Program, changes: textChanges.ChangeTracker, toConvert: NamedImportBindings): void {
const checker = program.getTypeChecker();
if (toConvert.kind === SyntaxKind.NamespaceImport) {
doChangeNamespaceToNamed(sourceFile, checker, changes, toConvert, getAllowSyntheticDefaultImports(program.getCompilerOptions()));
}
else {
doChangeNamedToNamespace(sourceFile, checker, changes, toConvert);
}
}
function doChangeNamespaceToNamed(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, toConvert: NamespaceImport, allowSyntheticDefaultImports: boolean): void {
let usedAsNamespaceOrDefault = false;
const nodesToReplace: PropertyAccessExpression[] = [];
const conflictingNames = createMap<true>();
FindAllReferences.Core.eachSymbolReferenceInFile(toConvert.name, checker, sourceFile, id => {
if (!isPropertyAccessExpression(id.parent)) {
usedAsNamespaceOrDefault = true;
}
else {
const parent = cast(id.parent, isPropertyAccessExpression);
const exportName = parent.name.text;
if (checker.resolveName(exportName, id, SymbolFlags.All, /*excludeGlobals*/ true)) {
conflictingNames.set(exportName, true);
}
Debug.assert(parent.expression === id, "Parent expression should match id");
nodesToReplace.push(parent);
}
});
// We may need to change `mod.x` to `_x` to avoid a name conflict.
const exportNameToImportName = createMap<string>();
for (const propertyAccess of nodesToReplace) {
const exportName = propertyAccess.name.text;
let importName = exportNameToImportName.get(exportName);
if (importName === undefined) {
exportNameToImportName.set(exportName, importName = conflictingNames.has(exportName) ? getUniqueName(exportName, sourceFile) : exportName);
}
changes.replaceNode(sourceFile, propertyAccess, createIdentifier(importName));
}
const importSpecifiers: ImportSpecifier[] = [];
exportNameToImportName.forEach((name, propertyName) => {
importSpecifiers.push(createImportSpecifier(name === propertyName ? undefined : createIdentifier(propertyName), createIdentifier(name)));
});
const importDecl = toConvert.parent.parent;
if (usedAsNamespaceOrDefault && !allowSyntheticDefaultImports) {
// Need to leave the namespace import alone
changes.insertNodeAfter(sourceFile, importDecl, updateImport(importDecl, /*defaultImportName*/ undefined, importSpecifiers));
}
else {
changes.replaceNode(sourceFile, importDecl, updateImport(importDecl, usedAsNamespaceOrDefault ? createIdentifier(toConvert.name.text) : undefined, importSpecifiers));
}
}
function doChangeNamedToNamespace(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, toConvert: NamedImports): void {
const importDecl = toConvert.parent.parent;
const { moduleSpecifier } = importDecl;
const preferredName = moduleSpecifier && isStringLiteral(moduleSpecifier) ? codefix.moduleSpecifierToValidIdentifier(moduleSpecifier.text, ScriptTarget.ESNext) : "module";
const namespaceNameConflicts = toConvert.elements.some(element =>
FindAllReferences.Core.eachSymbolReferenceInFile(element.name, checker, sourceFile, id =>
!!checker.resolveName(preferredName, id, SymbolFlags.All, /*excludeGlobals*/ true)) || false);
const namespaceImportName = namespaceNameConflicts ? getUniqueName(preferredName, sourceFile) : preferredName;
const neededNamedImports: ImportSpecifier[] = [];
for (const element of toConvert.elements) {
const propertyName = (element.propertyName || element.name).text;
FindAllReferences.Core.eachSymbolReferenceInFile(element.name, checker, sourceFile, id => {
const access = createPropertyAccess(createIdentifier(namespaceImportName), propertyName);
if (isShorthandPropertyAssignment(id.parent)) {
changes.replaceNode(sourceFile, id.parent, createPropertyAssignment(id.text, access));
}
else if (isExportSpecifier(id.parent) && !id.parent.propertyName) {
if (!neededNamedImports.some(n => n.name === element.name)) {
neededNamedImports.push(createImportSpecifier(element.propertyName && createIdentifier(element.propertyName.text), createIdentifier(element.name.text)));
}
}
else {
changes.replaceNode(sourceFile, id, access);
}
});
}
changes.replaceNode(sourceFile, toConvert, createNamespaceImport(createIdentifier(namespaceImportName)));
if (neededNamedImports.length) {
changes.insertNodeAfter(sourceFile, toConvert.parent.parent, updateImport(importDecl, /*defaultImportName*/ undefined, neededNamedImports));
}
}
function updateImport(old: ImportDeclaration, defaultImportName: Identifier | undefined, elements: readonly ImportSpecifier[] | undefined): ImportDeclaration {
return createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined,
createImportClause(defaultImportName, elements && elements.length ? createNamedImports(elements) : undefined), old.moduleSpecifier);
}
}