• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    const addOptionalPropertyUndefined = "addOptionalPropertyUndefined";
4
5    const errorCodes = [
6        Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target.code,
7        Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties.code,
8        Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties.code,
9    ];
10
11    registerCodeFix({
12        errorCodes,
13        getCodeActions(context) {
14            const typeChecker = context.program.getTypeChecker();
15            const toAdd = getPropertiesToAdd(context.sourceFile, context.span, typeChecker);
16            if (!toAdd.length) {
17                return undefined;
18            }
19            const changes = textChanges.ChangeTracker.with(context, t => addUndefinedToOptionalProperty(t, toAdd));
20            return [createCodeFixActionWithoutFixAll(addOptionalPropertyUndefined, changes, Diagnostics.Add_undefined_to_optional_property_type)];
21        },
22        fixIds: [addOptionalPropertyUndefined],
23    });
24
25    function getPropertiesToAdd(file: SourceFile, span: TextSpan, checker: TypeChecker): Symbol[] {
26        const sourceTarget = getSourceTarget(getFixableErrorSpanExpression(file, span), checker);
27        if (!sourceTarget) {
28            return emptyArray;
29        }
30        const { source: sourceNode, target: targetNode } = sourceTarget;
31        const target = shouldUseParentTypeOfProperty(sourceNode, targetNode, checker)
32            ? checker.getTypeAtLocation(targetNode.expression)
33            : checker.getTypeAtLocation(targetNode);
34        if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/\.d\.ts$/))) {
35            return emptyArray;
36        }
37        return checker.getExactOptionalProperties(target);
38    }
39
40    function shouldUseParentTypeOfProperty(sourceNode: Node, targetNode: Node, checker: TypeChecker): targetNode is PropertyAccessExpression {
41        return isPropertyAccessExpression(targetNode)
42            && !!checker.getExactOptionalProperties(checker.getTypeAtLocation(targetNode.expression)).length
43            && checker.getTypeAtLocation(sourceNode) === checker.getUndefinedType();
44    }
45
46    /**
47     * Find the source and target of the incorrect assignment.
48     * The call is recursive for property assignments.
49     */
50    function getSourceTarget(errorNode: Node | undefined, checker: TypeChecker): { source: Node, target: Node } | undefined {
51        if (!errorNode) {
52            return undefined;
53        }
54        else if (isBinaryExpression(errorNode.parent) && errorNode.parent.operatorToken.kind === SyntaxKind.EqualsToken) {
55            return { source: errorNode.parent.right, target: errorNode.parent.left };
56        }
57        else if (isVariableDeclaration(errorNode.parent) && errorNode.parent.initializer) {
58            return { source: errorNode.parent.initializer, target: errorNode.parent.name };
59        }
60        else if (isCallExpression(errorNode.parent)) {
61            const n = checker.getSymbolAtLocation(errorNode.parent.expression);
62            if (!n?.valueDeclaration || !isFunctionLikeKind(n.valueDeclaration.kind)) return undefined;
63            if (!isExpression(errorNode)) return undefined;
64            const i = errorNode.parent.arguments.indexOf(errorNode);
65            if (i === -1) return undefined;
66            const name = (n.valueDeclaration as any as SignatureDeclaration).parameters[i].name;
67            if (isIdentifier(name)) return { source: errorNode, target: name };
68        }
69        else if (isPropertyAssignment(errorNode.parent) && isIdentifier(errorNode.parent.name) ||
70            isShorthandPropertyAssignment(errorNode.parent)) {
71            const parentTarget = getSourceTarget(errorNode.parent.parent, checker);
72            if (!parentTarget) return undefined;
73            const prop = checker.getPropertyOfType(checker.getTypeAtLocation(parentTarget.target), (errorNode.parent.name as Identifier).text);
74            const declaration = prop?.declarations?.[0];
75            if (!declaration) return undefined;
76            return {
77                source: isPropertyAssignment(errorNode.parent) ? errorNode.parent.initializer : errorNode.parent.name,
78                target: declaration
79            };
80        }
81        return undefined;
82    }
83
84    function addUndefinedToOptionalProperty(changes: textChanges.ChangeTracker, toAdd: Symbol[]) {
85        for (const add of toAdd) {
86            const d = add.valueDeclaration;
87            if (d && (isPropertySignature(d) || isPropertyDeclaration(d)) && d.type) {
88                const t = factory.createUnionTypeNode([
89                    ...d.type.kind === SyntaxKind.UnionType ? (d.type as UnionTypeNode).types : [d.type],
90                    factory.createTypeReferenceNode("undefined")
91                ]);
92                changes.replaceNode(d.getSourceFile(), d.type, t);
93            }
94        }
95    }
96}
97