From d04e7454541b762fa3a1e384f8b34c8564eb1c07 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 18 Mar 2020 17:05:49 -0700 Subject: [PATCH] Add code fix for importsNotUsedAsValues error --- .../codefixes/convertToTypeOnlyImport.ts | 52 +++++++++++++++++++ src/services/tsconfig.json | 1 + .../codeFixConvertToTypeOnlyImport1.ts | 32 ++++++++++++ .../codeFixConvertToTypeOnlyImport2.ts | 29 +++++++++++ .../codeFixConvertToTypeOnlyImport3.ts | 41 +++++++++++++++ 5 files changed, 155 insertions(+) create mode 100644 src/services/codefixes/convertToTypeOnlyImport.ts create mode 100644 tests/cases/fourslash/codeFixConvertToTypeOnlyImport1.ts create mode 100644 tests/cases/fourslash/codeFixConvertToTypeOnlyImport2.ts create mode 100644 tests/cases/fourslash/codeFixConvertToTypeOnlyImport3.ts diff --git a/src/services/codefixes/convertToTypeOnlyImport.ts b/src/services/codefixes/convertToTypeOnlyImport.ts new file mode 100644 index 0000000000000..d434e1ffd3a1c --- /dev/null +++ b/src/services/codefixes/convertToTypeOnlyImport.ts @@ -0,0 +1,52 @@ +/* @internal */ +namespace ts.codefix { + const errorCodes = [Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_the_importsNotUsedAsValues_is_set_to_error.code]; + const fixId = "convertToTypeOnlyImport"; + registerCodeFix({ + errorCodes, + getCodeActions: context => { + const changes = textChanges.ChangeTracker.with(context, t => { + const importDeclaration = getImportDeclarationForDiagnosticSpan(context.span, context.sourceFile); + fixSingleImportDeclaration(t, importDeclaration, context); + }); + if (changes.length) { + return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_type_only_import, fixId, Diagnostics.Convert_all_imports_not_used_as_a_value_to_type_only_imports)]; + } + }, + fixIds: [fixId], + getAllCodeActions: context => { + return codeFixAll(context, errorCodes, (changes, diag) => { + const importDeclaration = getImportDeclarationForDiagnosticSpan(diag, context.sourceFile); + fixSingleImportDeclaration(changes, importDeclaration, context); + }); + } + }); + + function getImportDeclarationForDiagnosticSpan(span: TextSpan, sourceFile: SourceFile) { + return tryCast(getTokenAtPosition(sourceFile, span.start).parent, isImportDeclaration); + } + + function fixSingleImportDeclaration(changes: textChanges.ChangeTracker, importDeclaration: ImportDeclaration | undefined, context: CodeFixContextBase) { + if (!importDeclaration?.importClause) { + return; + } + + const { importClause } = importDeclaration; + // `changes.insertModifierBefore` produces a range that might overlap further changes + changes.insertText(context.sourceFile, importDeclaration.getStart() + "import".length, " type"); + + // `import type foo, { Bar }` is not allowed, so move `foo` to new declaration + if (importClause.name && importClause.namedBindings) { + changes.deleteNodeRangeExcludingEnd(context.sourceFile, importClause.name, importDeclaration.importClause.namedBindings); + changes.insertNodeBefore(context.sourceFile, importDeclaration, updateImportDeclaration( + importDeclaration, + /*decorators*/ undefined, + /*modifiers*/ undefined, + createImportClause( + importClause.name, + /*namedBindings*/ undefined, + /*isTypeOnly*/ true), + importDeclaration.moduleSpecifier)); + } + } +} diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index f2dc06e4597a8..9d1b8b44052fd 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -60,6 +60,7 @@ "codefixes/convertToEs6Module.ts", "codefixes/correctQualifiedNameToIndexedAccessType.ts", "codefixes/convertToTypeOnlyExport.ts", + "codefixes/convertToTypeOnlyImport.ts", "codefixes/fixClassIncorrectlyImplementsInterface.ts", "codefixes/importFixes.ts", "codefixes/fixImplicitThis.ts", diff --git a/tests/cases/fourslash/codeFixConvertToTypeOnlyImport1.ts b/tests/cases/fourslash/codeFixConvertToTypeOnlyImport1.ts new file mode 100644 index 0000000000000..cd7fb8b05c399 --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToTypeOnlyImport1.ts @@ -0,0 +1,32 @@ +/// + +// @importsNotUsedAsValues: error + +// @Filename: exports.ts +////export default class A {} +////export class B {} +////export class C {} + +// @Filename: imports.ts +////import { +//// B, +//// C, +////} from './exports'; +//// +////declare const b: B; +////declare const c: C; +////console.log(b, c); + +goTo.file("imports.ts"); +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Convert_to_type_only_import.message, + newFileContent: `import type { + B, + C, +} from './exports'; + +declare const b: B; +declare const c: C; +console.log(b, c);` +}); diff --git a/tests/cases/fourslash/codeFixConvertToTypeOnlyImport2.ts b/tests/cases/fourslash/codeFixConvertToTypeOnlyImport2.ts new file mode 100644 index 0000000000000..9cd7111d05cc7 --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToTypeOnlyImport2.ts @@ -0,0 +1,29 @@ +/// + +// @importsNotUsedAsValues: error + +// @Filename: exports.ts +////export default class A {} +////export class B {} +////export class C {} + +// @Filename: imports.ts +////import A, { B, C } from './exports'; +//// +////declare const a: A; +////declare const b: B; +////declare const c: C; +////console.log(a, b, c); + +goTo.file("imports.ts"); +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Convert_to_type_only_import.message, + newFileContent: `import type A from './exports'; +import type { B, C } from './exports'; + +declare const a: A; +declare const b: B; +declare const c: C; +console.log(a, b, c);` +}); diff --git a/tests/cases/fourslash/codeFixConvertToTypeOnlyImport3.ts b/tests/cases/fourslash/codeFixConvertToTypeOnlyImport3.ts new file mode 100644 index 0000000000000..0544e3035cdf4 --- /dev/null +++ b/tests/cases/fourslash/codeFixConvertToTypeOnlyImport3.ts @@ -0,0 +1,41 @@ +/// + +// @importsNotUsedAsValues: error + +// @Filename: exports1.ts +////export default class A {} +////export class B {} +////export class C {} + +// @Filename: exports2.ts +////export default class D {} +////export class E {} +////export class F {} + +// @Filename: imports.ts +////import A, { B, C } from './exports1'; +////import D, * as others from "./exports2"; +//// +////declare const a: A; +////declare const b: B; +////declare const c: C; +////declare const d: D; +////declare const o: typeof others; +////console.log(a, b, c, d, o); + +goTo.file("imports.ts"); +verify.codeFixAll({ + fixId: "convertToTypeOnlyImport", + fixAllDescription: ts.Diagnostics.Convert_all_imports_not_used_as_a_value_to_type_only_imports.message, + newFileContent: `import type A from './exports1'; +import type { B, C } from './exports1'; +import type D from "./exports2"; +import type * as others from "./exports2"; + +declare const a: A; +declare const b: B; +declare const c: C; +declare const d: D; +declare const o: typeof others; +console.log(a, b, c, d, o);` +});