Skip to content

Commit b0afbd6

Browse files
Cherry-pick #52565 to release-5.0 (#53055)
Co-authored-by: Oleksandr T <[email protected]>
1 parent 3ede924 commit b0afbd6

File tree

6 files changed

+78
-47
lines changed

6 files changed

+78
-47
lines changed

Diff for: src/services/codefixes/convertToTypeOnlyImport.ts

+47-33
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,81 @@
11
import {
2-
CodeFixContextBase,
32
Diagnostics,
43
factory,
4+
getSynthesizedDeepClone,
5+
getSynthesizedDeepClones,
56
getTokenAtPosition,
7+
ImportClause,
68
ImportDeclaration,
9+
ImportSpecifier,
710
isImportDeclaration,
11+
isImportSpecifier,
812
SourceFile,
913
textChanges,
10-
TextSpan,
11-
tryCast,
1214
} from "../_namespaces/ts";
1315
import {
1416
codeFixAll,
1517
createCodeFixAction,
1618
registerCodeFix,
1719
} from "../_namespaces/ts.codefix";
1820

19-
const errorCodes = [Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_importsNotUsedAsValues_is_set_to_error.code];
21+
const errorCodes = [
22+
Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_importsNotUsedAsValues_is_set_to_error.code,
23+
Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled.code,
24+
];
2025
const fixId = "convertToTypeOnlyImport";
26+
2127
registerCodeFix({
2228
errorCodes,
2329
getCodeActions: function getCodeActionsToConvertToTypeOnlyImport(context) {
24-
const changes = textChanges.ChangeTracker.with(context, t => {
25-
const importDeclaration = getImportDeclarationForDiagnosticSpan(context.span, context.sourceFile);
26-
fixSingleImportDeclaration(t, importDeclaration, context);
27-
});
28-
if (changes.length) {
30+
const declaration = getDeclaration(context.sourceFile, context.span.start);
31+
if (declaration) {
32+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, declaration));
2933
return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_type_only_import, fixId, Diagnostics.Convert_all_imports_not_used_as_a_value_to_type_only_imports)];
3034
}
35+
return undefined;
3136
},
3237
fixIds: [fixId],
3338
getAllCodeActions: function getAllCodeActionsToConvertToTypeOnlyImport(context) {
3439
return codeFixAll(context, errorCodes, (changes, diag) => {
35-
const importDeclaration = getImportDeclarationForDiagnosticSpan(diag, context.sourceFile);
36-
fixSingleImportDeclaration(changes, importDeclaration, context);
40+
const declaration = getDeclaration(diag.file, diag.start);
41+
if (declaration) {
42+
doChange(changes, diag.file, declaration);
43+
}
3744
});
3845
}
3946
});
4047

41-
function getImportDeclarationForDiagnosticSpan(span: TextSpan, sourceFile: SourceFile) {
42-
return tryCast(getTokenAtPosition(sourceFile, span.start).parent, isImportDeclaration);
48+
function getDeclaration(sourceFile: SourceFile, pos: number) {
49+
const { parent } = getTokenAtPosition(sourceFile, pos);
50+
return isImportSpecifier(parent) || isImportDeclaration(parent) && parent.importClause ? parent : undefined;
4351
}
4452

45-
function fixSingleImportDeclaration(changes: textChanges.ChangeTracker, importDeclaration: ImportDeclaration | undefined, context: CodeFixContextBase) {
46-
if (!importDeclaration?.importClause) {
47-
return;
53+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: ImportDeclaration | ImportSpecifier) {
54+
if (isImportSpecifier(declaration)) {
55+
changes.replaceNode(sourceFile, declaration, factory.updateImportSpecifier(declaration, /*isTypeOnly*/ true, declaration.propertyName, declaration.name));
4856
}
49-
50-
const { importClause } = importDeclaration;
51-
// `changes.insertModifierBefore` produces a range that might overlap further changes
52-
changes.insertText(context.sourceFile, importDeclaration.getStart() + "import".length, " type");
53-
54-
// `import type foo, { Bar }` is not allowed, so move `foo` to new declaration
55-
if (importClause.name && importClause.namedBindings) {
56-
changes.deleteNodeRangeExcludingEnd(context.sourceFile, importClause.name, importDeclaration.importClause.namedBindings);
57-
changes.insertNodeBefore(context.sourceFile, importDeclaration, factory.updateImportDeclaration(
58-
importDeclaration,
59-
/*modifiers*/ undefined,
60-
factory.createImportClause(
61-
/*isTypeOnly*/ true,
62-
importClause.name,
63-
/*namedBindings*/ undefined),
64-
importDeclaration.moduleSpecifier,
65-
/*assertClause*/ undefined));
57+
else {
58+
const importClause = declaration.importClause as ImportClause;
59+
if (importClause.name && importClause.namedBindings) {
60+
changes.replaceNodeWithNodes(sourceFile, declaration, [
61+
factory.createImportDeclaration(
62+
getSynthesizedDeepClones(declaration.modifiers, /*includeTrivia*/ true),
63+
factory.createImportClause(/*isTypeOnly*/ true, getSynthesizedDeepClone(importClause.name, /*includeTrivia*/ true), /*namedBindings*/ undefined),
64+
getSynthesizedDeepClone(declaration.moduleSpecifier, /*includeTrivia*/ true),
65+
getSynthesizedDeepClone(declaration.assertClause, /*includeTrivia*/ true),
66+
),
67+
factory.createImportDeclaration(
68+
getSynthesizedDeepClones(declaration.modifiers, /*includeTrivia*/ true),
69+
factory.createImportClause(/*isTypeOnly*/ true, /*name*/ undefined, getSynthesizedDeepClone(importClause.namedBindings, /*includeTrivia*/ true)),
70+
getSynthesizedDeepClone(declaration.moduleSpecifier, /*includeTrivia*/ true),
71+
getSynthesizedDeepClone(declaration.assertClause, /*includeTrivia*/ true),
72+
),
73+
]);
74+
}
75+
else {
76+
const importDeclaration = factory.updateImportDeclaration(declaration, declaration.modifiers,
77+
factory.updateImportClause(importClause, /*isTypeOnly*/ true, importClause.name, importClause.namedBindings), declaration.moduleSpecifier, declaration.assertClause);
78+
changes.replaceNode(sourceFile, declaration, importDeclaration);
79+
}
6680
}
6781
}

Diff for: tests/baselines/reference/tsserver/plugins/getSupportedCodeFixes-can-be-proxied.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ Info 32 [00:01:13.000] response:
309309
"2713",
310310
"1205",
311311
"1371",
312+
"1484",
312313
"2690",
313314
"2420",
314315
"2720",
@@ -720,7 +721,6 @@ Info 32 [00:01:13.000] response:
720721
"1477",
721722
"1478",
722723
"1479",
723-
"1484",
724724
"1485",
725725
"1486",
726726
"2200",

Diff for: tests/cases/fourslash/codeFixConvertToTypeOnlyImport1.ts

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

1010
// @Filename: imports.ts
1111
////import {
12-
//// B,
13-
//// C,
12+
//// B,
13+
//// C,
1414
////} from './exports';
1515
////
1616
////declare const b: B;
@@ -19,11 +19,11 @@
1919

2020
goTo.file("imports.ts");
2121
verify.codeFix({
22-
index: 0,
23-
description: ts.Diagnostics.Convert_to_type_only_import.message,
24-
newFileContent: `import type {
25-
B,
26-
C,
22+
index: 0,
23+
description: ts.Diagnostics.Convert_to_type_only_import.message,
24+
newFileContent: `import type {
25+
B,
26+
C,
2727
} from './exports';
2828
2929
declare const b: B;

Diff for: tests/cases/fourslash/codeFixConvertToTypeOnlyImport2.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717

1818
goTo.file("imports.ts");
1919
verify.codeFix({
20-
index: 0,
21-
description: ts.Diagnostics.Convert_to_type_only_import.message,
22-
newFileContent: `import type A from './exports';
20+
index: 0,
21+
description: ts.Diagnostics.Convert_to_type_only_import.message,
22+
newFileContent: `import type A from './exports';
2323
import type { B, C } from './exports';
2424
2525
declare const a: A;

Diff for: tests/cases/fourslash/codeFixConvertToTypeOnlyImport3.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525

2626
goTo.file("imports.ts");
2727
verify.codeFixAll({
28-
fixId: "convertToTypeOnlyImport",
29-
fixAllDescription: ts.Diagnostics.Convert_all_imports_not_used_as_a_value_to_type_only_imports.message,
30-
newFileContent: `import type A from './exports1';
28+
fixId: "convertToTypeOnlyImport",
29+
fixAllDescription: ts.Diagnostics.Convert_all_imports_not_used_as_a_value_to_type_only_imports.message,
30+
newFileContent: `import type A from './exports1';
3131
import type { B, C } from './exports1';
3232
import type D from "./exports2";
3333
import type * as others from "./exports2";
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @module: esnext
4+
// @verbatimModuleSyntax: true
5+
// @filename: /b.ts
6+
////export interface I {}
7+
////export const foo = {};
8+
9+
// @filename: /a.ts
10+
////import { I, foo } from "./b";
11+
12+
goTo.file("/a.ts");
13+
verify.codeFix({
14+
index: 0,
15+
description: ts.Diagnostics.Convert_to_type_only_import.message,
16+
newFileContent: `import { type I, foo } from "./b";`
17+
});

0 commit comments

Comments
 (0)