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