From bc502c8c3c6ee5d0a30635bb4f8ad8e3e5179253 Mon Sep 17 00:00:00 2001 From: Alexander T Date: Tue, 2 Jun 2020 11:23:56 +0300 Subject: [PATCH] fix(38081): allow transforming object binding to named imports --- src/services/codefixes/requireInTs.ts | 65 +++++++++++++++---- ...xRequireInTs.ts => codeFixRequireInTs1.ts} | 5 +- tests/cases/fourslash/codeFixRequireInTs2.ts | 9 +++ tests/cases/fourslash/codeFixRequireInTs3.ts | 6 ++ .../cases/fourslash/codeFixRequireInTs_all.ts | 8 ++- ...equireInTs_allowSyntheticDefaultImports.ts | 5 +- ...reInTs_allowSyntheticDefaultImports_all.ts | 18 +++++ 7 files changed, 97 insertions(+), 19 deletions(-) rename tests/cases/fourslash/{codeFixRequireInTs.ts => codeFixRequireInTs1.ts} (64%) create mode 100644 tests/cases/fourslash/codeFixRequireInTs2.ts create mode 100644 tests/cases/fourslash/codeFixRequireInTs3.ts create mode 100644 tests/cases/fourslash/codeFixRequireInTs_allowSyntheticDefaultImports_all.ts diff --git a/src/services/codefixes/requireInTs.ts b/src/services/codefixes/requireInTs.ts index ee2a898147..ee53b3edaf 100644 --- a/src/services/codefixes/requireInTs.ts +++ b/src/services/codefixes/requireInTs.ts @@ -5,25 +5,68 @@ namespace ts.codefix { registerCodeFix({ errorCodes, getCodeActions(context) { - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, context.span.start, context.program)); + const info = getInfo(context.sourceFile, context.program, context.span.start); + if (!info) { + return undefined; + } + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, info)); return [createCodeFixAction(fixId, changes, Diagnostics.Convert_require_to_import, fixId, Diagnostics.Convert_all_require_to_import)]; }, fixIds: [fixId], - getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => doChange(changes, diag.file, diag.start, context.program)), + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + const info = getInfo(diag.file, context.program, diag.start); + if (info) { + doChange(changes, context.sourceFile, info); + } + }), }); - function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, program: Program) { - const { statement, name, required } = getInfo(sourceFile, pos); - changes.replaceNode(sourceFile, statement, getAllowSyntheticDefaultImports(program.getCompilerOptions()) - ? createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClause(name, /*namedBindings*/ undefined), required) - : createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, name, createExternalModuleReference(required))); + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, info: Info) { + const { allowSyntheticDefaults, defaultImportName, namedImports, statement, required } = info; + changes.replaceNode(sourceFile, statement, defaultImportName && !allowSyntheticDefaults + ? createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, defaultImportName, createExternalModuleReference(required)) + : createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClause(defaultImportName, namedImports), required)); } - interface Info { readonly statement: VariableStatement; readonly name: Identifier; readonly required: StringLiteralLike; } - function getInfo(sourceFile: SourceFile, pos: number): Info { + interface Info { + readonly allowSyntheticDefaults: boolean; + readonly defaultImportName: Identifier | undefined; + readonly namedImports: NamedImports | undefined; + readonly statement: VariableStatement; + readonly required: StringLiteralLike; + } + + function getInfo(sourceFile: SourceFile, program: Program, pos: number): Info | undefined { const { parent } = getTokenAtPosition(sourceFile, pos); - if (!isRequireCall(parent, /*checkArgumentIsStringLiteralLike*/ true)) throw Debug.failBadSyntaxKind(parent); + if (!isRequireCall(parent, /*checkArgumentIsStringLiteralLike*/ true)) { + throw Debug.failBadSyntaxKind(parent); + } + const decl = cast(parent.parent, isVariableDeclaration); - return { statement: cast(decl.parent.parent, isVariableStatement), name: cast(decl.name, isIdentifier), required: parent.arguments[0] }; + const defaultImportName = tryCast(decl.name, isIdentifier); + const namedImports = isObjectBindingPattern(decl.name) ? tryCreateNamedImportsFromObjectBindingPattern(decl.name) : undefined; + if (defaultImportName || namedImports) { + return { + allowSyntheticDefaults: getAllowSyntheticDefaultImports(program.getCompilerOptions()), + defaultImportName, + namedImports, + statement: cast(decl.parent.parent, isVariableStatement), + required: first(parent.arguments) + }; + } + } + + function tryCreateNamedImportsFromObjectBindingPattern(node: ObjectBindingPattern): NamedImports | undefined { + const importSpecifiers: ImportSpecifier[] = []; + for (const element of node.elements) { + if (!isIdentifier(element.name) || element.initializer) { + return undefined; + } + importSpecifiers.push(createImportSpecifier(tryCast(element.propertyName, isIdentifier), element.name)); + } + + if (importSpecifiers.length) { + return createNamedImports(importSpecifiers); + } } } diff --git a/tests/cases/fourslash/codeFixRequireInTs.ts b/tests/cases/fourslash/codeFixRequireInTs1.ts similarity index 64% rename from tests/cases/fourslash/codeFixRequireInTs.ts rename to tests/cases/fourslash/codeFixRequireInTs1.ts index 345b15f69f..773afe0eb1 100644 --- a/tests/cases/fourslash/codeFixRequireInTs.ts +++ b/tests/cases/fourslash/codeFixRequireInTs1.ts @@ -9,7 +9,6 @@ verify.getSuggestionDiagnostics([{ }]); verify.codeFix({ - description: "Convert 'require' to 'import'", - newFileContent: -`import a = require("a");`, + description: ts.Diagnostics.Convert_require_to_import.message, + newFileContent: 'import a = require("a");', }); diff --git a/tests/cases/fourslash/codeFixRequireInTs2.ts b/tests/cases/fourslash/codeFixRequireInTs2.ts new file mode 100644 index 0000000000..6d6325cdfe --- /dev/null +++ b/tests/cases/fourslash/codeFixRequireInTs2.ts @@ -0,0 +1,9 @@ +/// + +// @Filename: /a.ts +////const { a, b, c } = [|require("a")|]; + +verify.codeFix({ + description: ts.Diagnostics.Convert_require_to_import.message, + newFileContent: 'import { a, b, c } from "a";', +}); diff --git a/tests/cases/fourslash/codeFixRequireInTs3.ts b/tests/cases/fourslash/codeFixRequireInTs3.ts new file mode 100644 index 0000000000..3943f38fc1 --- /dev/null +++ b/tests/cases/fourslash/codeFixRequireInTs3.ts @@ -0,0 +1,6 @@ +/// + +// @Filename: /a.ts +////const { a, b: { c } } = [|require("a")|]; + +verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixRequireInTs_all.ts b/tests/cases/fourslash/codeFixRequireInTs_all.ts index 9f9fe4824a..d37d2455e9 100644 --- a/tests/cases/fourslash/codeFixRequireInTs_all.ts +++ b/tests/cases/fourslash/codeFixRequireInTs_all.ts @@ -3,11 +3,15 @@ // @Filename: /a.ts ////const a = [|require("a")|]; ////const b = [|require("b")|]; +////const { c } = [|require("c")|]; +////const { d } = [|require("d")|]; verify.codeFixAll({ fixId: "requireInTs", - fixAllDescription: "Convert all 'require' to 'import'", + fixAllDescription: ts.Diagnostics.Convert_all_require_to_import.message, newFileContent: `import a = require("a"); -import b = require("b");`, +import b = require("b"); +import { c } from "c"; +import { d } from "d";`, }); diff --git a/tests/cases/fourslash/codeFixRequireInTs_allowSyntheticDefaultImports.ts b/tests/cases/fourslash/codeFixRequireInTs_allowSyntheticDefaultImports.ts index 949387af2e..5fcad968e2 100644 --- a/tests/cases/fourslash/codeFixRequireInTs_allowSyntheticDefaultImports.ts +++ b/tests/cases/fourslash/codeFixRequireInTs_allowSyntheticDefaultImports.ts @@ -1,11 +1,10 @@ /// // @allowSyntheticDefaultImports: true - // @Filename: /a.ts ////const a = [|require("a")|]; verify.codeFix({ - description: "Convert 'require' to 'import'", - newFileContent: `import a from "a";`, + description: ts.Diagnostics.Convert_require_to_import.message, + newFileContent: 'import a from "a";', }); diff --git a/tests/cases/fourslash/codeFixRequireInTs_allowSyntheticDefaultImports_all.ts b/tests/cases/fourslash/codeFixRequireInTs_allowSyntheticDefaultImports_all.ts new file mode 100644 index 0000000000..8e0017eb2a --- /dev/null +++ b/tests/cases/fourslash/codeFixRequireInTs_allowSyntheticDefaultImports_all.ts @@ -0,0 +1,18 @@ +/// + +// @allowSyntheticDefaultImports: true +// @Filename: /a.ts +////const a = [|require("a")|]; +////const b = [|require("b")|]; +////const { c } = [|require("c")|]; +////const { d } = [|require("d")|]; + +verify.codeFixAll({ + fixId: "requireInTs", + fixAllDescription: ts.Diagnostics.Convert_all_require_to_import.message, + newFileContent: +`import a from "a"; +import b from "b"; +import { c } from "c"; +import { d } from "d";`, +});