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