Skip to content

Commit 950c356

Browse files
committed
add support for type literal and improve test
1 parent 0924b49 commit 950c356

6 files changed

+92
-29
lines changed

src/services/codefixes/convertToMappedObjectType.ts

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,74 +4,84 @@ namespace ts.codefix {
44
const fixId = fixIdAddMissingTypeof;
55
const errorCodes = [Diagnostics.An_index_signature_parameter_type_cannot_be_a_union_type_Consider_using_a_mapped_object_type_instead.code];
66

7+
type FixableDeclaration = InterfaceDeclaration | TypeAliasDeclaration;
8+
79
interface Info {
810
indexSignature: IndexSignatureDeclaration;
9-
container: InterfaceDeclaration;
11+
container: FixableDeclaration;
12+
otherMembers: ReadonlyArray<TypeElement>;
13+
parameterName: Identifier;
14+
parameterType: TypeNode;
1015
}
1116

1217
registerCodeFix({
1318
errorCodes,
1419
getCodeActions: context => {
1520
const { sourceFile, span } = context;
16-
const info = getConvertibleSignatureAtPosition(sourceFile, span.start);
21+
const info = getFixableSignatureAtPosition(sourceFile, span.start);
1722
if (!info) return;
18-
const { indexSignature, container } = info;
23+
const { indexSignature, container, otherMembers, parameterName, parameterType } = info;
1924

20-
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, indexSignature, container));
21-
return [createCodeFixAction(fixId, changes, [Diagnostics.Convert_0_to_mapped_object_type, ""], fixId, [Diagnostics.Convert_0_to_mapped_object_type, ""])];
25+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, indexSignature, container, otherMembers, parameterName, parameterType));
26+
return [createCodeFixAction(fixId, changes, [Diagnostics.Convert_0_to_mapped_object_type, idText(container.name)], fixId, [Diagnostics.Convert_0_to_mapped_object_type, idText(container.name)])];
2227
},
2328
fixIds: [fixId],
2429
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
25-
const info = getConvertibleSignatureAtPosition(diag.file, diag.start);
30+
const info = getFixableSignatureAtPosition(diag.file, diag.start);
2631
if (!info) return;
27-
const { indexSignature, container } = info;
32+
const { indexSignature, container, otherMembers, parameterName, parameterType } = info;
2833

29-
doChange(changes, context.sourceFile, indexSignature, container);
34+
doChange(changes, context.sourceFile, indexSignature, container, otherMembers, parameterName, parameterType);
3035
})
3136
});
3237

38+
function isFixableDeclaration(node: Node): node is FixableDeclaration {
39+
return isInterfaceDeclaration(node) || (node.parent && isTypeLiteralNode(node) && isTypeAliasDeclaration(node.parent));
40+
}
41+
3342
function isIndexSignatureParameterName(node: Node): node is Identifier {
3443
return node && node.parent && node.parent.parent && node.parent.parent.parent &&
35-
isIdentifier(node) && isParameter(node.parent) && isIndexSignatureDeclaration(node.parent.parent) && isInterfaceDeclaration(node.parent.parent.parent);
44+
isIdentifier(node) && isParameter(node.parent) && isIndexSignatureDeclaration(node.parent.parent) && isFixableDeclaration(node.parent.parent.parent);
3645
}
3746

38-
function getConvertibleSignatureAtPosition(sourceFile: SourceFile, pos: number): Info | undefined {
47+
function getFixableSignatureAtPosition(sourceFile: SourceFile, pos: number): Info | undefined {
3948
const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
4049
if (!isIndexSignatureParameterName(token)) return undefined;
4150

4251
const indexSignature = <IndexSignatureDeclaration>token.parent.parent;
43-
const container = <InterfaceDeclaration>indexSignature.parent;
52+
const container: FixableDeclaration = isInterfaceDeclaration(indexSignature.parent) ? indexSignature.parent : <TypeAliasDeclaration>indexSignature.parent.parent;
53+
const members = isInterfaceDeclaration(container) ? container.members : (<TypeLiteralNode>container.type).members;
54+
const otherMembers = filter(members, member => !isIndexSignatureDeclaration(member));
55+
const parameter = first(indexSignature.parameters);
4456

4557
return {
4658
indexSignature,
47-
container
59+
container,
60+
otherMembers,
61+
parameterName: <Identifier>parameter.name,
62+
parameterType: parameter.type
4863
};
4964
}
5065

51-
function createTypeAliasFromInterface(indexSignature: IndexSignatureDeclaration, declaration: InterfaceDeclaration) {
52-
const otherMembersType = createTypeLiteralNode(filter(declaration.members, member => !isIndexSignatureDeclaration(member)));
53-
const parameter = first(indexSignature.parameters);
54-
const mappedObjectType = createMappedTypeNode(
55-
hasReadonlyModifier(indexSignature) ? createModifier(SyntaxKind.ReadonlyKeyword) : undefined,
56-
createTypeParameterDeclaration(
57-
<Identifier>parameter.name,
58-
parameter.type
59-
),
60-
indexSignature.questionToken,
61-
indexSignature.type
62-
);
66+
function createTypeAliasFromInterface(indexSignature: IndexSignatureDeclaration, declaration: FixableDeclaration, otherMembers: ReadonlyArray<TypeElement>, parameterName: Identifier, parameterType: TypeNode) {
67+
const mappedIntersectionType: TypeNode[] = [
68+
createMappedTypeNode(
69+
hasReadonlyModifier(indexSignature) ? createModifier(SyntaxKind.ReadonlyKeyword) : undefined,
70+
createTypeParameterDeclaration(parameterName, parameterType),
71+
indexSignature.questionToken,
72+
indexSignature.type)
73+
];
6374

6475
return createTypeAliasDeclaration(
6576
declaration.decorators,
6677
declaration.modifiers,
6778
declaration.name,
6879
declaration.typeParameters,
69-
createIntersectionTypeNode([mappedObjectType, otherMembersType])
80+
createIntersectionTypeNode(append(mappedIntersectionType, otherMembers.length ? createTypeLiteralNode(otherMembers) : undefined))
7081
);
7182
}
7283

73-
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, indexSignature: IndexSignatureDeclaration, declaration: InterfaceDeclaration) {
74-
const newTypeDeclaration = createTypeAliasFromInterface(indexSignature, declaration);
75-
changes.replaceNode(sourceFile, declaration, newTypeDeclaration);
84+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, indexSignature: IndexSignatureDeclaration, declaration: FixableDeclaration, otherMembers: ReadonlyArray<TypeElement>, parameterName: Identifier, parameterType: TypeNode) {
85+
changes.replaceNode(sourceFile, declaration, createTypeAliasFromInterface(indexSignature, declaration, otherMembers, parameterName, parameterType));
7686
}
7787
}

tests/cases/fourslash/codeFixConvertToMappedObjectType.ts renamed to tests/cases/fourslash/codeFixConvertToMappedObjectType1.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//// }
88

99
verify.codeFix({
10-
description: `Convert '' to mapped object type`,
10+
description: `Convert 'SomeType' to mapped object type`,
1111
newFileContent: `type K = "foo" | "bar";
1212
type SomeType = {
1313
[prop in K]: any;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// type K = "foo" | "bar";
4+
//// type SomeType = {
5+
//// a: string;
6+
//// [prop: K]: any;
7+
//// }
8+
9+
verify.codeFix({
10+
description: `Convert 'SomeType' to mapped object type`,
11+
newFileContent: `type K = "foo" | "bar";
12+
type SomeType = {
13+
[prop in K]: any;
14+
} & {
15+
a: string;
16+
};`
17+
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// type K = "foo" | "bar";
4+
//// type SomeType = {
5+
//// [prop: K]: any;
6+
//// }
7+
8+
verify.codeFix({
9+
description: `Convert 'SomeType' to mapped object type`,
10+
newFileContent: `type K = "foo" | "bar";
11+
type SomeType = {
12+
[prop in K]: any;
13+
};`
14+
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// type K = "foo" | "bar";
4+
//// interface SomeType {
5+
//// [prop: K]: any;
6+
//// }
7+
8+
verify.codeFix({
9+
description: `Convert 'SomeType' to mapped object type`,
10+
newFileContent: `type K = "foo" | "bar";
11+
type SomeType = {
12+
[prop in K]: any;
13+
};`
14+
})
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// type K = "foo" | "bar";
4+
//// class SomeType {
5+
//// [prop: K]: any;
6+
//// }
7+
8+
verify.not.codeFixAvailable()

0 commit comments

Comments
 (0)