Skip to content

Commit bc502c8

Browse files
committed
fix(38081): allow transforming object binding to named imports
1 parent 1d1c167 commit bc502c8

7 files changed

+97
-19
lines changed

src/services/codefixes/requireInTs.ts

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,68 @@ namespace ts.codefix {
55
registerCodeFix({
66
errorCodes,
77
getCodeActions(context) {
8-
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, context.span.start, context.program));
8+
const info = getInfo(context.sourceFile, context.program, context.span.start);
9+
if (!info) {
10+
return undefined;
11+
}
12+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, info));
913
return [createCodeFixAction(fixId, changes, Diagnostics.Convert_require_to_import, fixId, Diagnostics.Convert_all_require_to_import)];
1014
},
1115
fixIds: [fixId],
12-
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => doChange(changes, diag.file, diag.start, context.program)),
16+
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
17+
const info = getInfo(diag.file, context.program, diag.start);
18+
if (info) {
19+
doChange(changes, context.sourceFile, info);
20+
}
21+
}),
1322
});
1423

15-
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, program: Program) {
16-
const { statement, name, required } = getInfo(sourceFile, pos);
17-
changes.replaceNode(sourceFile, statement, getAllowSyntheticDefaultImports(program.getCompilerOptions())
18-
? createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClause(name, /*namedBindings*/ undefined), required)
19-
: createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, name, createExternalModuleReference(required)));
24+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, info: Info) {
25+
const { allowSyntheticDefaults, defaultImportName, namedImports, statement, required } = info;
26+
changes.replaceNode(sourceFile, statement, defaultImportName && !allowSyntheticDefaults
27+
? createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, defaultImportName, createExternalModuleReference(required))
28+
: createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClause(defaultImportName, namedImports), required));
2029
}
2130

22-
interface Info { readonly statement: VariableStatement; readonly name: Identifier; readonly required: StringLiteralLike; }
23-
function getInfo(sourceFile: SourceFile, pos: number): Info {
31+
interface Info {
32+
readonly allowSyntheticDefaults: boolean;
33+
readonly defaultImportName: Identifier | undefined;
34+
readonly namedImports: NamedImports | undefined;
35+
readonly statement: VariableStatement;
36+
readonly required: StringLiteralLike;
37+
}
38+
39+
function getInfo(sourceFile: SourceFile, program: Program, pos: number): Info | undefined {
2440
const { parent } = getTokenAtPosition(sourceFile, pos);
25-
if (!isRequireCall(parent, /*checkArgumentIsStringLiteralLike*/ true)) throw Debug.failBadSyntaxKind(parent);
41+
if (!isRequireCall(parent, /*checkArgumentIsStringLiteralLike*/ true)) {
42+
throw Debug.failBadSyntaxKind(parent);
43+
}
44+
2645
const decl = cast(parent.parent, isVariableDeclaration);
27-
return { statement: cast(decl.parent.parent, isVariableStatement), name: cast(decl.name, isIdentifier), required: parent.arguments[0] };
46+
const defaultImportName = tryCast(decl.name, isIdentifier);
47+
const namedImports = isObjectBindingPattern(decl.name) ? tryCreateNamedImportsFromObjectBindingPattern(decl.name) : undefined;
48+
if (defaultImportName || namedImports) {
49+
return {
50+
allowSyntheticDefaults: getAllowSyntheticDefaultImports(program.getCompilerOptions()),
51+
defaultImportName,
52+
namedImports,
53+
statement: cast(decl.parent.parent, isVariableStatement),
54+
required: first(parent.arguments)
55+
};
56+
}
57+
}
58+
59+
function tryCreateNamedImportsFromObjectBindingPattern(node: ObjectBindingPattern): NamedImports | undefined {
60+
const importSpecifiers: ImportSpecifier[] = [];
61+
for (const element of node.elements) {
62+
if (!isIdentifier(element.name) || element.initializer) {
63+
return undefined;
64+
}
65+
importSpecifiers.push(createImportSpecifier(tryCast(element.propertyName, isIdentifier), element.name));
66+
}
67+
68+
if (importSpecifiers.length) {
69+
return createNamedImports(importSpecifiers);
70+
}
2871
}
2972
}

tests/cases/fourslash/codeFixRequireInTs.ts renamed to tests/cases/fourslash/codeFixRequireInTs1.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ verify.getSuggestionDiagnostics([{
99
}]);
1010

1111
verify.codeFix({
12-
description: "Convert 'require' to 'import'",
13-
newFileContent:
14-
`import a = require("a");`,
12+
description: ts.Diagnostics.Convert_require_to_import.message,
13+
newFileContent: 'import a = require("a");',
1514
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: /a.ts
4+
////const { a, b, c } = [|require("a")|];
5+
6+
verify.codeFix({
7+
description: ts.Diagnostics.Convert_require_to_import.message,
8+
newFileContent: 'import { a, b, c } from "a";',
9+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: /a.ts
4+
////const { a, b: { c } } = [|require("a")|];
5+
6+
verify.not.codeFixAvailable();

tests/cases/fourslash/codeFixRequireInTs_all.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
// @Filename: /a.ts
44
////const a = [|require("a")|];
55
////const b = [|require("b")|];
6+
////const { c } = [|require("c")|];
7+
////const { d } = [|require("d")|];
68

79
verify.codeFixAll({
810
fixId: "requireInTs",
9-
fixAllDescription: "Convert all 'require' to 'import'",
11+
fixAllDescription: ts.Diagnostics.Convert_all_require_to_import.message,
1012
newFileContent:
1113
`import a = require("a");
12-
import b = require("b");`,
14+
import b = require("b");
15+
import { c } from "c";
16+
import { d } from "d";`,
1317
});
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
/// <reference path='fourslash.ts' />
22

33
// @allowSyntheticDefaultImports: true
4-
54
// @Filename: /a.ts
65
////const a = [|require("a")|];
76

87
verify.codeFix({
9-
description: "Convert 'require' to 'import'",
10-
newFileContent: `import a from "a";`,
8+
description: ts.Diagnostics.Convert_require_to_import.message,
9+
newFileContent: 'import a from "a";',
1110
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowSyntheticDefaultImports: true
4+
// @Filename: /a.ts
5+
////const a = [|require("a")|];
6+
////const b = [|require("b")|];
7+
////const { c } = [|require("c")|];
8+
////const { d } = [|require("d")|];
9+
10+
verify.codeFixAll({
11+
fixId: "requireInTs",
12+
fixAllDescription: ts.Diagnostics.Convert_all_require_to_import.message,
13+
newFileContent:
14+
`import a from "a";
15+
import b from "b";
16+
import { c } from "c";
17+
import { d } from "d";`,
18+
});

0 commit comments

Comments
 (0)