Skip to content

Commit d54ad4b

Browse files
author
Andy
authored
Add refactoring to use default import (#19659)
* Add refactoring to use default import * Add localizable description
1 parent cc2a2a7 commit d54ad4b

File tree

5 files changed

+134
-0
lines changed

5 files changed

+134
-0
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3793,5 +3793,9 @@
37933793
"Infer parameter types from usage.": {
37943794
"category": "Message",
37953795
"code": 95012
3796+
},
3797+
"Convert to default import": {
3798+
"category": "Message",
3799+
"code": 95013
37963800
}
37973801
}

src/harness/fourslash.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,10 @@ namespace FourSlash {
470470

471471
public select(startMarker: string, endMarker: string) {
472472
const start = this.getMarkerByName(startMarker), end = this.getMarkerByName(endMarker);
473+
ts.Debug.assert(start.fileName === end.fileName);
474+
if (this.activeFile.fileName !== start.fileName) {
475+
this.openFile(start.fileName);
476+
}
473477
this.goToPosition(start.position);
474478
this.selectionEnd = end.position;
475479
}

src/services/refactors/refactors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/// <reference path="convertFunctionToEs6Class.ts" />
33
/// <reference path="extractSymbol.ts" />
44
/// <reference path="installTypesForPackage.ts" />
5+
/// <reference path="useDefaultImport.ts" />
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/* @internal */
2+
namespace ts.refactor.installTypesForPackage {
3+
const actionName = "Convert to default import";
4+
5+
const useDefaultImport: Refactor = {
6+
name: actionName,
7+
description: getLocaleSpecificMessage(Diagnostics.Convert_to_default_import),
8+
getEditsForAction,
9+
getAvailableActions,
10+
};
11+
12+
registerRefactor(useDefaultImport);
13+
14+
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
15+
const { file, startPosition, program } = context;
16+
17+
if (!program.getCompilerOptions().allowSyntheticDefaultImports) {
18+
return undefined;
19+
}
20+
21+
const importInfo = getConvertibleImportAtPosition(file, startPosition);
22+
if (!importInfo) {
23+
return undefined;
24+
}
25+
26+
const module = ts.getResolvedModule(file, importInfo.moduleSpecifier.text);
27+
const resolvedFile = program.getSourceFile(module.resolvedFileName);
28+
if (!(resolvedFile.externalModuleIndicator && isExportAssignment(resolvedFile.externalModuleIndicator) && resolvedFile.externalModuleIndicator.isExportEquals)) {
29+
return undefined;
30+
}
31+
32+
return [
33+
{
34+
name: useDefaultImport.name,
35+
description: useDefaultImport.description,
36+
actions: [
37+
{
38+
description: useDefaultImport.description,
39+
name: actionName,
40+
},
41+
],
42+
},
43+
];
44+
}
45+
46+
function getEditsForAction(context: RefactorContext, _actionName: string): RefactorEditInfo | undefined {
47+
const { file, startPosition } = context;
48+
Debug.assertEqual(actionName, _actionName);
49+
const importInfo = getConvertibleImportAtPosition(file, startPosition);
50+
if (!importInfo) {
51+
return undefined;
52+
}
53+
const { importStatement, name, moduleSpecifier } = importInfo;
54+
const newImportClause = createImportClause(name, /*namedBindings*/ undefined);
55+
const newImportStatement = ts.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, newImportClause, moduleSpecifier);
56+
return {
57+
edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, importStatement, newImportStatement)),
58+
renameFilename: undefined,
59+
renameLocation: undefined,
60+
};
61+
}
62+
63+
function getConvertibleImportAtPosition(
64+
file: SourceFile,
65+
startPosition: number,
66+
): { importStatement: AnyImportSyntax, name: Identifier, moduleSpecifier: StringLiteral } | undefined {
67+
let node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false);
68+
while (true) {
69+
switch (node.kind) {
70+
case SyntaxKind.ImportEqualsDeclaration:
71+
const eq = node as ImportEqualsDeclaration;
72+
const { moduleReference } = eq;
73+
return moduleReference.kind === SyntaxKind.ExternalModuleReference && isStringLiteral(moduleReference.expression)
74+
? { importStatement: eq, name: eq.name, moduleSpecifier: moduleReference.expression }
75+
: undefined;
76+
case SyntaxKind.ImportDeclaration:
77+
const d = node as ImportDeclaration;
78+
const { importClause } = d;
79+
return !importClause.name && importClause.namedBindings.kind === SyntaxKind.NamespaceImport && isStringLiteral(d.moduleSpecifier)
80+
? { importStatement: d, name: importClause.namedBindings.name, moduleSpecifier: d.moduleSpecifier }
81+
: undefined;
82+
// For known child node kinds of convertible imports, try again with parent node.
83+
case SyntaxKind.NamespaceImport:
84+
case SyntaxKind.ExternalModuleReference:
85+
case SyntaxKind.ImportKeyword:
86+
case SyntaxKind.Identifier:
87+
case SyntaxKind.StringLiteral:
88+
case SyntaxKind.AsteriskToken:
89+
break;
90+
default:
91+
return undefined;
92+
}
93+
node = node.parent;
94+
}
95+
}
96+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowSyntheticDefaultImports: true
4+
5+
// @Filename: /a.d.ts
6+
////declare const x: number;
7+
////export = x;
8+
9+
// @Filename: /b.ts
10+
/////*b0*/import * as a from "./a";/*b1*/
11+
12+
// @Filename: /c.ts
13+
/////*c0*/import a = require("./a");/*c1*/
14+
15+
goTo.select("b0", "b1");
16+
edit.applyRefactor({
17+
refactorName: "Convert to default import",
18+
actionName: "Convert to default import",
19+
actionDescription: "Convert to default import",
20+
newContent: 'import a from "./a";',
21+
});
22+
23+
goTo.select("c0", "c1");
24+
edit.applyRefactor({
25+
refactorName: "Convert to default import",
26+
actionName: "Convert to default import",
27+
actionDescription: "Convert to default import",
28+
newContent: 'import a from "./a";',
29+
});

0 commit comments

Comments
 (0)