• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    const fixId = "fixConvertToMappedObjectType";
4    const errorCodes = [Diagnostics.An_index_signature_parameter_type_cannot_be_a_literal_type_or_generic_type_Consider_using_a_mapped_object_type_instead.code];
5
6    type FixableDeclaration = InterfaceDeclaration | TypeAliasDeclaration;
7
8    registerCodeFix({
9        errorCodes,
10        getCodeActions: function getCodeActionsToConvertToMappedTypeObject(context) {
11            const { sourceFile, span } = context;
12            const info = getInfo(sourceFile, span.start);
13            if (!info) return undefined;
14            const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, info));
15            const name = idText(info.container.name);
16            return [createCodeFixAction(fixId, changes, [Diagnostics.Convert_0_to_mapped_object_type, name], fixId, [Diagnostics.Convert_0_to_mapped_object_type, name])];
17        },
18        fixIds: [fixId],
19        getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
20            const info = getInfo(diag.file, diag.start);
21            if (info) doChange(changes, diag.file, info);
22        })
23    });
24
25    interface Info { readonly indexSignature: IndexSignatureDeclaration; readonly container: FixableDeclaration; }
26    function getInfo(sourceFile: SourceFile, pos: number): Info | undefined {
27        const token = getTokenAtPosition(sourceFile, pos);
28        const indexSignature = tryCast(token.parent.parent, isIndexSignatureDeclaration);
29        if (!indexSignature) return undefined;
30
31        const container = isInterfaceDeclaration(indexSignature.parent) ? indexSignature.parent : tryCast(indexSignature.parent.parent, isTypeAliasDeclaration);
32        if (!container) return undefined;
33
34        return { indexSignature, container };
35    }
36
37    function createTypeAliasFromInterface(declaration: FixableDeclaration, type: TypeNode): TypeAliasDeclaration {
38        return factory.createTypeAliasDeclaration(declaration.modifiers, declaration.name, declaration.typeParameters, type);
39    }
40
41    function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { indexSignature, container }: Info): void {
42        const members = isInterfaceDeclaration(container) ? container.members : (container.type as TypeLiteralNode).members;
43        const otherMembers = members.filter(member => !isIndexSignatureDeclaration(member));
44        const parameter = first(indexSignature.parameters);
45        const mappedTypeParameter = factory.createTypeParameterDeclaration(/*modifiers*/ undefined, cast(parameter.name, isIdentifier), parameter.type);
46        const mappedIntersectionType = factory.createMappedTypeNode(
47            hasEffectiveReadonlyModifier(indexSignature) ? factory.createModifier(SyntaxKind.ReadonlyKeyword) : undefined,
48            mappedTypeParameter,
49            /*nameType*/ undefined,
50            indexSignature.questionToken,
51            indexSignature.type,
52            /*members*/ undefined);
53        const intersectionType = factory.createIntersectionTypeNode([
54            ...getAllSuperTypeNodes(container),
55            mappedIntersectionType,
56            ...(otherMembers.length ? [factory.createTypeLiteralNode(otherMembers)] : emptyArray),
57        ]);
58        changes.replaceNode(sourceFile, container, createTypeAliasFromInterface(container, intersectionType));
59    }
60}
61