Skip to content

Commit 9f9376f

Browse files
author
Andy Hanson
committed
Convert use-default-import refactor to a codefix
1 parent 70818ae commit 9f9376f

12 files changed

+118
-141
lines changed

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -3826,6 +3826,10 @@
38263826
"category": "Suggestion",
38273827
"code": 80002
38283828
},
3829+
"Import may be converted to a default import.": {
3830+
"category": "Suggestion",
3831+
"code": 80003
3832+
},
38293833

38303834
"Add missing 'super()' call": {
38313835
"category": "Message",

src/services/codefixes/convertToEs6Module.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -495,9 +495,13 @@ namespace ts.codefix {
495495
: makeImport(/*name*/ undefined, [makeImportSpecifier(propertyName, localName)], moduleSpecifier);
496496
}
497497

498-
function makeImport(name: Identifier | undefined, namedImports: ReadonlyArray<ImportSpecifier>, moduleSpecifier: string): ImportDeclaration {
498+
function makeImport(name: Identifier | undefined, namedImports: ReadonlyArray<ImportSpecifier> | undefined, moduleSpecifier: string): ImportDeclaration {
499+
return makeImportDeclaration(name, namedImports, createLiteral(moduleSpecifier));
500+
}
501+
502+
export function makeImportDeclaration(name: Identifier, namedImports: ReadonlyArray<ImportSpecifier> | undefined, moduleSpecifier: Expression) {
499503
const importClause = (name || namedImports) && createImportClause(name, namedImports && createNamedImports(namedImports));
500-
return createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, importClause, createLiteral(moduleSpecifier));
504+
return createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, importClause, moduleSpecifier);
501505
}
502506

503507
function makeImportSpecifier(propertyName: string | undefined, name: string): ImportSpecifier {

src/services/codefixes/fixInvalidImportSyntax.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,7 @@ namespace ts.codefix {
2626
const variations: CodeAction[] = [];
2727

2828
// import Bluebird from "bluebird";
29-
variations.push(createAction(context, sourceFile, node, createImportDeclaration(
30-
/*decorators*/ undefined,
31-
/*modifiers*/ undefined,
32-
createImportClause(namespace.name, /*namedBindings*/ undefined),
33-
node.moduleSpecifier
34-
)));
29+
variations.push(createAction(context, sourceFile, node, makeImportDeclaration(namespace.name, /*namedImports*/ undefined, node.moduleSpecifier)));
3530

3631
if (getEmitModuleKind(opts) === ModuleKind.CommonJS) {
3732
// import Bluebird = require("bluebird");

src/services/codefixes/fixes.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@
2020
/// <reference path='inferFromUsage.ts' />
2121
/// <reference path="fixInvalidImportSyntax.ts" />
2222
/// <reference path="fixStrictClassInitialization.ts" />
23+
/// <reference path="useDefaultImport.ts" />
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixId = "useDefaultImport";
4+
const errorCodes = [Diagnostics.Import_may_be_converted_to_a_default_import.code];
5+
registerCodeFix({
6+
errorCodes,
7+
getCodeActions(context) {
8+
const { sourceFile, span: { start } } = context;
9+
const info = getInfo(sourceFile, start);
10+
if (!info) return undefined;
11+
const description = getLocaleSpecificMessage(Diagnostics.Convert_to_default_import);
12+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, info));
13+
return [{ description, changes, fixId }];
14+
},
15+
fixIds: [fixId],
16+
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
17+
const info = getInfo(diag.file!, diag.start!);
18+
if (info) doChange(changes, diag.file!, info);
19+
}),
20+
});
21+
22+
interface Info {
23+
readonly importNode: AnyImportSyntax;
24+
readonly name: Identifier;
25+
readonly moduleSpecifier: Expression;
26+
}
27+
function getInfo(sourceFile: SourceFile, pos: number): Info | undefined {
28+
const name = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
29+
if (!isIdentifier(name)) return undefined; // bad input
30+
const { parent } = name;
31+
if (isImportEqualsDeclaration(parent) && isExternalModuleReference(parent.moduleReference)) {
32+
return { importNode: parent, name, moduleSpecifier: parent.moduleReference.expression };
33+
}
34+
else if (isNamespaceImport(parent)) {
35+
const importNode = parent.parent.parent;
36+
return { importNode, name, moduleSpecifier: importNode.moduleSpecifier };
37+
}
38+
}
39+
40+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, info: Info): void {
41+
changes.replaceNode(sourceFile, info.importNode, makeImportDeclaration(info.name, /*namedImports*/ undefined, info.moduleSpecifier));
42+
}
43+
}

src/services/refactors/refactors.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
/// <reference path="annotateWithTypeFromJSDoc.ts" />
22
/// <reference path="extractSymbol.ts" />
3-
/// <reference path="useDefaultImport.ts" />

src/services/refactors/useDefaultImport.ts

-89
This file was deleted.

src/services/suggestionDiagnostics.ts

+24
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ namespace ts {
2525
check(sourceFile);
2626
}
2727

28+
if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) {
29+
for (const importNode of sourceFile.imports) {
30+
const name = importNameForConvertToDefaultImport(importNode.parent);
31+
if (!name) continue;
32+
const module = getResolvedModule(sourceFile, importNode.text);
33+
const resolvedFile = module && program.getSourceFile(module.resolvedFileName);
34+
if (resolvedFile && resolvedFile.externalModuleIndicator && isExportAssignment(resolvedFile.externalModuleIndicator) && resolvedFile.externalModuleIndicator.isExportEquals) {
35+
diags.push(createDiagnosticForNode(name, Diagnostics.Import_may_be_converted_to_a_default_import));
36+
}
37+
}
38+
}
39+
2840
return diags.concat(checker.getSuggestionDiagnostics(sourceFile));
2941
}
42+
43+
function importNameForConvertToDefaultImport(node: Node): Identifier | undefined {
44+
if (isExternalModuleReference(node)) {
45+
return node.parent.name;
46+
}
47+
if (isImportDeclaration(node)) {
48+
const { importClause, moduleSpecifier } = node;
49+
return importClause && !importClause.name && importClause.namedBindings.kind === SyntaxKind.NamespaceImport && isStringLiteral(moduleSpecifier)
50+
? importClause.namedBindings.name
51+
: undefined;
52+
}
53+
}
3054
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
////import * as [|a|] from "./a";
11+
12+
// @Filename: /c.ts
13+
////import [|a|] = require("./a");
14+
15+
// @Filename: /d.ts
16+
////import "./a";
17+
18+
// @Filename: /e.ts
19+
////import * as n from "./non-existant";
20+
21+
for (const file of ["/b.ts", "/c.ts"]) {
22+
goTo.file(file);
23+
24+
verify.getSuggestionDiagnostics([{
25+
message: "Import may be converted to a default import.",
26+
range: test.ranges().find(r => r.fileName === file),
27+
code: 80003,
28+
}]);
29+
30+
verify.codeFix({
31+
description: "Convert to default import",
32+
newFileContent: `import a from "./a";`,
33+
});
34+
}
35+
36+
for (const file of ["/d.ts", "/e.ts"]) {
37+
goTo.file(file);
38+
verify.getSuggestionDiagnostics([]);
39+
}

tests/cases/fourslash/convertFunctionToEs6Class1.ts

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
verify.getSuggestionDiagnostics([{
1515
message: "This constructor function may be converted to a class declaration.",
16-
category: "suggestion",
1716
code: 80002,
1817
}]);
1918

tests/cases/fourslash/refactorConvertToEs6Module_export_named.ts

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
verify.getSuggestionDiagnostics([{
1111
message: "File is a CommonJS module; it may be converted to an ES6 module.",
12-
category: "suggestion",
1312
code: 80001,
1413
}]);
1514

tests/cases/fourslash/refactorUseDefaultImport.ts

-41
This file was deleted.

0 commit comments

Comments
 (0)