-
Notifications
You must be signed in to change notification settings - Fork 12.8k
/
Copy pathconvertToMappedObjectType.ts
92 lines (84 loc) · 3.8 KB
/
convertToMappedObjectType.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import {
codeFixAll,
createCodeFixAction,
registerCodeFix,
} from "../_namespaces/ts.codefix.js";
import {
cast,
Diagnostics,
emptyArray,
factory,
first,
getAllSuperTypeNodes,
getTokenAtPosition,
hasEffectiveReadonlyModifier,
idText,
IndexSignatureDeclaration,
InterfaceDeclaration,
isIdentifier,
isIndexSignatureDeclaration,
isInterfaceDeclaration,
isTypeAliasDeclaration,
SourceFile,
SyntaxKind,
textChanges,
tryCast,
TypeAliasDeclaration,
TypeLiteralNode,
TypeNode,
} from "../_namespaces/ts.js";
const fixId = "fixConvertToMappedObjectType";
const errorCodes = [Diagnostics.An_index_signature_parameter_type_cannot_be_a_literal_type_or_generic_type_Consider_using_a_mapped_object_type_instead.code];
type FixableDeclaration = InterfaceDeclaration | TypeAliasDeclaration;
registerCodeFix({
errorCodes,
getCodeActions: function getCodeActionsToConvertToMappedTypeObject(context) {
const { sourceFile, span } = context;
const info = getInfo(sourceFile, span.start);
if (!info) return undefined;
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, info));
const name = idText(info.container.name);
return [createCodeFixAction(fixId, changes, [Diagnostics.Convert_0_to_mapped_object_type, name], fixId, [Diagnostics.Convert_0_to_mapped_object_type, name])];
},
fixIds: [fixId],
getAllCodeActions: context =>
codeFixAll(context, errorCodes, (changes, diag) => {
const info = getInfo(diag.file, diag.start);
if (info) doChange(changes, diag.file, info);
}),
});
interface Info {
readonly indexSignature: IndexSignatureDeclaration;
readonly container: FixableDeclaration;
}
function getInfo(sourceFile: SourceFile, pos: number): Info | undefined {
const token = getTokenAtPosition(sourceFile, pos);
const indexSignature = tryCast(token.parent.parent, isIndexSignatureDeclaration);
if (!indexSignature) return undefined;
const container = isInterfaceDeclaration(indexSignature.parent) ? indexSignature.parent : tryCast(indexSignature.parent.parent, isTypeAliasDeclaration);
if (!container) return undefined;
return { indexSignature, container };
}
function createTypeAliasFromInterface(declaration: FixableDeclaration, type: TypeNode): TypeAliasDeclaration {
return factory.createTypeAliasDeclaration(declaration.modifiers, declaration.name, declaration.typeParameters, type);
}
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { indexSignature, container }: Info): void {
const members = isInterfaceDeclaration(container) ? container.members : (container.type as TypeLiteralNode).members;
const otherMembers = members.filter(member => !isIndexSignatureDeclaration(member));
const parameter = first(indexSignature.parameters);
const mappedTypeParameter = factory.createTypeParameterDeclaration(/*modifiers*/ undefined, cast(parameter.name, isIdentifier), parameter.type);
const mappedIntersectionType = factory.createMappedTypeNode(
hasEffectiveReadonlyModifier(indexSignature) ? factory.createModifier(SyntaxKind.ReadonlyKeyword) : undefined,
mappedTypeParameter,
/*nameType*/ undefined,
indexSignature.questionToken,
indexSignature.type,
/*members*/ undefined,
);
const intersectionType = factory.createIntersectionTypeNode([
...getAllSuperTypeNodes(container),
mappedIntersectionType,
...(otherMembers.length ? [factory.createTypeLiteralNode(otherMembers)] : emptyArray),
]);
changes.replaceNode(sourceFile, container, createTypeAliasFromInterface(container, intersectionType));
}