Skip to content

Commit

Permalink
Convert use-default-import refactor to a codefix
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy Hanson committed Mar 5, 2018
1 parent 70818ae commit 9f9376f
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 141 deletions.
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3826,6 +3826,10 @@
"category": "Suggestion",
"code": 80002
},
"Import may be converted to a default import.": {
"category": "Suggestion",
"code": 80003
},

"Add missing 'super()' call": {
"category": "Message",
Expand Down
8 changes: 6 additions & 2 deletions src/services/codefixes/convertToEs6Module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,13 @@ namespace ts.codefix {
: makeImport(/*name*/ undefined, [makeImportSpecifier(propertyName, localName)], moduleSpecifier);
}

function makeImport(name: Identifier | undefined, namedImports: ReadonlyArray<ImportSpecifier>, moduleSpecifier: string): ImportDeclaration {
function makeImport(name: Identifier | undefined, namedImports: ReadonlyArray<ImportSpecifier> | undefined, moduleSpecifier: string): ImportDeclaration {
return makeImportDeclaration(name, namedImports, createLiteral(moduleSpecifier));
}

export function makeImportDeclaration(name: Identifier, namedImports: ReadonlyArray<ImportSpecifier> | undefined, moduleSpecifier: Expression) {
const importClause = (name || namedImports) && createImportClause(name, namedImports && createNamedImports(namedImports));
return createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, importClause, createLiteral(moduleSpecifier));
return createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, importClause, moduleSpecifier);
}

function makeImportSpecifier(propertyName: string | undefined, name: string): ImportSpecifier {
Expand Down
7 changes: 1 addition & 6 deletions src/services/codefixes/fixInvalidImportSyntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,7 @@ namespace ts.codefix {
const variations: CodeAction[] = [];

// import Bluebird from "bluebird";
variations.push(createAction(context, sourceFile, node, createImportDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createImportClause(namespace.name, /*namedBindings*/ undefined),
node.moduleSpecifier
)));
variations.push(createAction(context, sourceFile, node, makeImportDeclaration(namespace.name, /*namedImports*/ undefined, node.moduleSpecifier)));

if (getEmitModuleKind(opts) === ModuleKind.CommonJS) {
// import Bluebird = require("bluebird");
Expand Down
1 change: 1 addition & 0 deletions src/services/codefixes/fixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
/// <reference path='inferFromUsage.ts' />
/// <reference path="fixInvalidImportSyntax.ts" />
/// <reference path="fixStrictClassInitialization.ts" />
/// <reference path="useDefaultImport.ts" />
43 changes: 43 additions & 0 deletions src/services/codefixes/useDefaultImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* @internal */
namespace ts.codefix {
const fixId = "useDefaultImport";
const errorCodes = [Diagnostics.Import_may_be_converted_to_a_default_import.code];
registerCodeFix({
errorCodes,
getCodeActions(context) {
const { sourceFile, span: { start } } = context;
const info = getInfo(sourceFile, start);
if (!info) return undefined;
const description = getLocaleSpecificMessage(Diagnostics.Convert_to_default_import);
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, info));
return [{ description, changes, fixId }];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
const info = getInfo(diag.file!, diag.start!);
if (info) doChange(changes, diag.file!, info);
}),
});

interface Info {
readonly importNode: AnyImportSyntax;
readonly name: Identifier;
readonly moduleSpecifier: Expression;
}
function getInfo(sourceFile: SourceFile, pos: number): Info | undefined {
const name = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
if (!isIdentifier(name)) return undefined; // bad input
const { parent } = name;
if (isImportEqualsDeclaration(parent) && isExternalModuleReference(parent.moduleReference)) {
return { importNode: parent, name, moduleSpecifier: parent.moduleReference.expression };
}
else if (isNamespaceImport(parent)) {
const importNode = parent.parent.parent;
return { importNode, name, moduleSpecifier: importNode.moduleSpecifier };
}
}

function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, info: Info): void {
changes.replaceNode(sourceFile, info.importNode, makeImportDeclaration(info.name, /*namedImports*/ undefined, info.moduleSpecifier));
}
}
1 change: 0 additions & 1 deletion src/services/refactors/refactors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
/// <reference path="annotateWithTypeFromJSDoc.ts" />
/// <reference path="extractSymbol.ts" />
/// <reference path="useDefaultImport.ts" />
89 changes: 0 additions & 89 deletions src/services/refactors/useDefaultImport.ts

This file was deleted.

24 changes: 24 additions & 0 deletions src/services/suggestionDiagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,30 @@ namespace ts {
check(sourceFile);
}

if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) {
for (const importNode of sourceFile.imports) {
const name = importNameForConvertToDefaultImport(importNode.parent);
if (!name) continue;
const module = getResolvedModule(sourceFile, importNode.text);
const resolvedFile = module && program.getSourceFile(module.resolvedFileName);
if (resolvedFile && resolvedFile.externalModuleIndicator && isExportAssignment(resolvedFile.externalModuleIndicator) && resolvedFile.externalModuleIndicator.isExportEquals) {
diags.push(createDiagnosticForNode(name, Diagnostics.Import_may_be_converted_to_a_default_import));
}
}
}

return diags.concat(checker.getSuggestionDiagnostics(sourceFile));
}

function importNameForConvertToDefaultImport(node: Node): Identifier | undefined {
if (isExternalModuleReference(node)) {
return node.parent.name;
}
if (isImportDeclaration(node)) {
const { importClause, moduleSpecifier } = node;
return importClause && !importClause.name && importClause.namedBindings.kind === SyntaxKind.NamespaceImport && isStringLiteral(moduleSpecifier)
? importClause.namedBindings.name
: undefined;
}
}
}
39 changes: 39 additions & 0 deletions tests/cases/fourslash/codeFixUseDefaultImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/// <reference path='fourslash.ts' />

// @allowSyntheticDefaultImports: true

// @Filename: /a.d.ts
////declare const x: number;
////export = x;

// @Filename: /b.ts
////import * as [|a|] from "./a";

// @Filename: /c.ts
////import [|a|] = require("./a");

// @Filename: /d.ts
////import "./a";

// @Filename: /e.ts
////import * as n from "./non-existant";

for (const file of ["/b.ts", "/c.ts"]) {
goTo.file(file);

verify.getSuggestionDiagnostics([{
message: "Import may be converted to a default import.",
range: test.ranges().find(r => r.fileName === file),
code: 80003,
}]);

verify.codeFix({
description: "Convert to default import",
newFileContent: `import a from "./a";`,
});
}

for (const file of ["/d.ts", "/e.ts"]) {
goTo.file(file);
verify.getSuggestionDiagnostics([]);
}
1 change: 0 additions & 1 deletion tests/cases/fourslash/convertFunctionToEs6Class1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

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

Expand Down
41 changes: 0 additions & 41 deletions tests/cases/fourslash/refactorUseDefaultImport.ts

This file was deleted.

0 comments on commit 9f9376f

Please sign in to comment.