Skip to content

Commit 6aec7f4

Browse files
authored
feat(25770): add Quick Fix to convert LiteralType to MappedType (#40226)
1 parent 6101fbc commit 6aec7f4

6 files changed

+118
-0
lines changed

src/compiler/diagnosticMessages.json

+8
Original file line numberDiff line numberDiff line change
@@ -5335,6 +5335,10 @@
53355335
"category": "Message",
53365336
"code": 95002
53375337
},
5338+
"Convert '{0}' to '{1} in {0}'": {
5339+
"category": "Message",
5340+
"code": 95003
5341+
},
53385342
"Extract to {0} in {1}": {
53395343
"category": "Message",
53405344
"code": 95004
@@ -5403,6 +5407,10 @@
54035407
"category": "Message",
54045408
"code": 95020
54055409
},
5410+
"Convert all type literals to mapped type": {
5411+
"category": "Message",
5412+
"code": 95021
5413+
},
54065414
"Add all missing members": {
54075415
"category": "Message",
54085416
"code": 95022
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixId = "convertLiteralTypeToMappedType";
4+
const errorCodes = [Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Did_you_mean_to_use_1_in_0.code];
5+
6+
registerCodeFix({
7+
errorCodes,
8+
getCodeActions: context => {
9+
const { sourceFile, span } = context;
10+
const info = getInfo(sourceFile, span.start);
11+
if (!info) {
12+
return undefined;
13+
}
14+
const { name, constraint } = info;
15+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, info));
16+
return [createCodeFixAction(fixId, changes, [Diagnostics.Convert_0_to_1_in_0, constraint, name], fixId, Diagnostics.Convert_all_type_literals_to_mapped_type)];
17+
},
18+
fixIds: [fixId],
19+
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
20+
const info = getInfo(diag.file, diag.start);
21+
if (info) {
22+
doChange(changes, diag.file, info);
23+
}
24+
})
25+
});
26+
27+
interface Info {
28+
container: TypeLiteralNode,
29+
typeNode: TypeNode | undefined;
30+
constraint: string;
31+
name: string;
32+
}
33+
34+
function getInfo(sourceFile: SourceFile, pos: number): Info | undefined {
35+
const token = getTokenAtPosition(sourceFile, pos);
36+
if (isIdentifier(token)) {
37+
const propertySignature = cast(token.parent.parent, isPropertySignature);
38+
const propertyName = token.getText(sourceFile);
39+
return {
40+
container: cast(propertySignature.parent, isTypeLiteralNode),
41+
typeNode: propertySignature.type,
42+
constraint: propertyName,
43+
name: propertyName === "K" ? "P" : "K",
44+
};
45+
}
46+
return undefined;
47+
}
48+
49+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { container, typeNode, constraint, name }: Info): void {
50+
changes.replaceNode(sourceFile, container, factory.createMappedTypeNode(/*readonlyToken*/ undefined,
51+
factory.createTypeParameterDeclaration(name, factory.createTypeReferenceNode(constraint)), /*questionToken*/ undefined, typeNode));
52+
}
53+
}

src/services/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"codefixes/correctQualifiedNameToIndexedAccessType.ts",
6262
"codefixes/convertToTypeOnlyExport.ts",
6363
"codefixes/convertToTypeOnlyImport.ts",
64+
"codefixes/convertLiteralTypeToMappedType.ts",
6465
"codefixes/fixClassIncorrectlyImplementsInterface.ts",
6566
"codefixes/importFixes.ts",
6667
"codefixes/fixImplicitThis.ts",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////type K = number | string;
4+
////type T = {
5+
//// [K]: number;
6+
////}
7+
8+
verify.codeFix({
9+
description: [ts.Diagnostics.Convert_0_to_1_in_0.message, "K", "P"],
10+
index: 0,
11+
newFileContent:
12+
`type K = number | string;
13+
type T = {
14+
[P in K]: number;
15+
}`
16+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////type Keys = number | string;
4+
////type T = {
5+
//// [Keys]: number;
6+
////}
7+
8+
verify.codeFix({
9+
description: [ts.Diagnostics.Convert_0_to_1_in_0.message, "Keys", "K"],
10+
index: 0,
11+
newFileContent:
12+
`type Keys = number | string;
13+
type T = {
14+
[K in Keys]: number;
15+
}`
16+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////type K1 = number | string;
4+
////type T1 = {
5+
//// [K1]: number;
6+
////}
7+
////type K2 = number | string;
8+
////type T2 = {
9+
//// [K2]: number;
10+
////}
11+
12+
verify.codeFixAll({
13+
fixAllDescription: ts.Diagnostics.Convert_all_type_literals_to_mapped_type.message,
14+
fixId: 'convertLiteralTypeToMappedType',
15+
newFileContent:
16+
`type K1 = number | string;
17+
type T1 = {
18+
[K in K1]: number;
19+
}
20+
type K2 = number | string;
21+
type T2 = {
22+
[K in K2]: number;
23+
}`
24+
});

0 commit comments

Comments
 (0)