Skip to content

Commit 715f235

Browse files
committed
More cleanup
1 parent a728852 commit 715f235

File tree

2 files changed

+70
-59
lines changed

2 files changed

+70
-59
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17658,7 +17658,7 @@ namespace ts {
1765817658
message = Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated;
1765917659
}
1766017660
else if (exactOptionalPropertyTypes && getExactOptionalUnassignableProperties(source, target).length) {
17661-
message = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties
17661+
message = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties;
1766217662
}
1766317663
else {
1766417664
message = Diagnostics.Type_0_is_not_assignable_to_type_1;
@@ -17667,7 +17667,7 @@ namespace ts {
1766717667
else if (message === Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1
1766817668
&& exactOptionalPropertyTypes
1766917669
&& getExactOptionalUnassignableProperties(source, target).length) {
17670-
message = 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
17670+
message = 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;
1767117671
}
1767217672

1767317673
reportError(message, generalizedSourceType, targetType);
@@ -19586,11 +19586,12 @@ namespace ts {
1958619586

1958719587
function getExactOptionalUnassignableProperties(source: Type, target: Type) {
1958819588
return checker.getPropertiesOfType(target).filter(targetProp => {
19589-
const sourceProp = getPropertyOfType(source, targetProp.escapedName)
19589+
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
1959019590
return sourceProp && targetProp.valueDeclaration
1959119591
&& maybeTypeOfKind(getTypeOfSymbol(sourceProp), TypeFlags.Undefined)
1959219592
&& hasQuestionToken(targetProp.valueDeclaration)
19593-
&& containsMissingType(getTypeOfSymbol(targetProp)) })
19593+
&& containsMissingType(getTypeOfSymbol(targetProp));
19594+
});
1959419595
}
1959519596

1959619597
function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) {

src/services/codefixes/addOptionalPropertyUndefined.ts

Lines changed: 65 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,11 @@ namespace ts.codefix {
1111
errorCodes,
1212
getCodeActions(context) {
1313
const typeChecker = context.program.getTypeChecker();
14-
const info = getInfo(context.sourceFile, context.span.start, typeChecker);
15-
if (!info.length) {
14+
const toAdd = getPropertiesToAdd(context.sourceFile, context.span.start, typeChecker);
15+
if (!toAdd.length) {
1616
return undefined;
1717
}
18-
// if method, it has to be rewritten to property
19-
// skip any and unions with any
20-
// add to existing unions
21-
// parenthesise conditional types and arrows (the printer should take care of that, but it needs a test)
22-
// test with destructuring, I've no idea what to do there
23-
const changes = textChanges.ChangeTracker.with(context, t => addUndefinedToOptionalProperty(t, info));
18+
const changes = textChanges.ChangeTracker.with(context, t => addUndefinedToOptionalProperty(t, toAdd));
2419
return [createCodeFixAction(addOptionalPropertyUndefined, changes, Diagnostics.Add_undefined_to_optional_property_type, addOptionalPropertyUndefined, Diagnostics.Add_undefined_to_all_optional_properties)];
2520
},
2621
fixIds: [addOptionalPropertyUndefined],
@@ -30,81 +25,96 @@ namespace ts.codefix {
3025
const seen = new Map<string, true>();
3126
return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => {
3227
eachDiagnostic(context, errorCodes, diag => {
33-
const info = getInfo(diag.file, diag.start, checker);
34-
if (!info.length) {
28+
const toAdd = getPropertiesToAdd(diag.file, diag.start, checker);
29+
if (!toAdd.length) {
3530
return;
3631
}
37-
for (const add of info) {
38-
if (addToSeen(seen, add.id + "")) {
39-
addUndefinedToOptionalProperty(changes, info);
32+
let untouched = true;
33+
for (const add of toAdd) {
34+
if (!addToSeen(seen, add.id + "")) {
35+
untouched = false;
4036
}
4137
}
38+
if (untouched) {addUndefinedToOptionalProperty(changes, toAdd);}
4239
});
4340
}));
4441
},
4542
});
4643

47-
// The target of the incorrect assignment
48-
// eg
49-
// this.definite = 1; -OR- definite = source
50-
// ^^^^ ^^^^^^^^
51-
// TODO: More examples here
52-
function getTarget(file: SourceFile, pos: number): MemberName | PropertyAccessExpression | undefined {
53-
const start = getTokenAtPosition(file, pos)
44+
function getPropertiesToAdd(file: SourceFile, pos: number, checker: TypeChecker): Symbol[] {
45+
const sourceTarget = getSourceTarget(getErrorNode(file, pos), checker);
46+
if (!sourceTarget) {
47+
return [];
48+
}
49+
const { source: sourceNode, target: targetNode } = sourceTarget;
50+
const target = checker.getTypeAtLocation(targetNode);
51+
if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/(node_modules|^lib\.)/))) {
52+
return [];
53+
}
54+
return checker.getExactOptionalUnassignableProperties(checker.getTypeAtLocation(sourceNode), target);
55+
}
56+
57+
/**
58+
* Get the part of the incorrect assignment that is useful for type-checking
59+
* eg
60+
* this.definite = 1; ---> `this.definite`
61+
* ^^^^
62+
* definite = source ----> `definite`
63+
* ^^^^^^^^
64+
*/
65+
function getErrorNode(file: SourceFile, pos: number): MemberName | PropertyAccessExpression | undefined {
66+
const start = getTokenAtPosition(file, pos);
5467
return isPropertyAccessExpression(start.parent) && start.parent.expression === start ? start.parent
5568
: isIdentifier(start) || isPrivateIdentifier(start) ? start
5669
: undefined;
5770
}
5871

59-
function getSourceTarget(target: Node | undefined, checker: TypeChecker): { source: Node, target: Node } | undefined {
60-
if (!target) return undefined
61-
if (isBinaryExpression(target.parent) && target.parent.operatorToken.kind === SyntaxKind.EqualsToken) {
62-
return { source: target.parent.right, target: target.parent.left }
72+
/**
73+
* Find the source and target of the incorrect assignment.
74+
* The call is recursive for property assignments.
75+
*/
76+
function getSourceTarget(errorNode: Node | undefined, checker: TypeChecker): { source: Node, target: Node } | undefined {
77+
if (!errorNode) {
78+
return undefined;
6379
}
64-
else if (isVariableDeclaration(target.parent) && target.parent.initializer) {
65-
return { source: target.parent.initializer, target: target.parent.name }
80+
else if (isBinaryExpression(errorNode.parent) && errorNode.parent.operatorToken.kind === SyntaxKind.EqualsToken) {
81+
return { source: errorNode.parent.right, target: errorNode.parent.left };
6682
}
67-
else if (isCallExpression(target.parent)) {
68-
const n = checker.getSymbolAtLocation(target.parent.expression)
69-
if (!n?.valueDeclaration) return undefined
70-
if (!isExpression(target)) return undefined;
71-
const i = target.parent.arguments.indexOf(target)
72-
const name = (n.valueDeclaration as any as SignatureDeclaration).parameters[i].name
73-
if (isIdentifier(name)) return { source: target, target: name }
83+
else if (isVariableDeclaration(errorNode.parent) && errorNode.parent.initializer) {
84+
return { source: errorNode.parent.initializer, target: errorNode.parent.name };
7485
}
75-
else if (isPropertyAssignment(target.parent) && isIdentifier(target.parent.name) ||
76-
isShorthandPropertyAssignment(target.parent)) {
77-
const parentTarget = getSourceTarget(target.parent.parent, checker)
78-
if (!parentTarget) return undefined
79-
const prop = checker.getPropertyOfType(checker.getTypeAtLocation(parentTarget.target), (target.parent.name as Identifier).text)
80-
const declaration = prop?.declarations?.[0]
81-
if (!declaration) return undefined
86+
else if (isCallExpression(errorNode.parent)) {
87+
const n = checker.getSymbolAtLocation(errorNode.parent.expression);
88+
if (!n?.valueDeclaration) return undefined;
89+
if (!isExpression(errorNode)) return undefined;
90+
const i = errorNode.parent.arguments.indexOf(errorNode);
91+
const name = (n.valueDeclaration as any as SignatureDeclaration).parameters[i].name;
92+
if (isIdentifier(name)) return { source: errorNode, target: name };
93+
}
94+
else if (isPropertyAssignment(errorNode.parent) && isIdentifier(errorNode.parent.name) ||
95+
isShorthandPropertyAssignment(errorNode.parent)) {
96+
const parentTarget = getSourceTarget(errorNode.parent.parent, checker);
97+
if (!parentTarget) return undefined;
98+
const prop = checker.getPropertyOfType(checker.getTypeAtLocation(parentTarget.target), (errorNode.parent.name as Identifier).text);
99+
const declaration = prop?.declarations?.[0];
100+
if (!declaration) return undefined;
82101
return {
83-
source: isPropertyAssignment(target.parent) ? target.parent.initializer : target.parent.name,
102+
source: isPropertyAssignment(errorNode.parent) ? errorNode.parent.initializer : errorNode.parent.name,
84103
target: declaration
85-
}
104+
};
86105
}
87-
return undefined
88-
}
89-
90-
function getInfo(file: SourceFile, pos: number, checker: TypeChecker): Symbol[] {
91-
const sourceTarget = getSourceTarget(getTarget(file, pos), checker)
92-
if (!sourceTarget) return []
93-
const { source: sourceNode, target: targetNode } = sourceTarget
94-
const target = checker.getTypeAtLocation(targetNode)
95-
if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/(node_modules|^lib\.)/))) return [];
96-
return checker.getExactOptionalUnassignableProperties(checker.getTypeAtLocation(sourceNode), target)
106+
return undefined;
97107
}
98108

99109
function addUndefinedToOptionalProperty(changes: textChanges.ChangeTracker, toAdd: Symbol[]) {
100110
for (const add of toAdd) {
101-
const d = add.valueDeclaration
111+
const d = add.valueDeclaration;
102112
if (d && (isPropertySignature(d) || isPropertyDeclaration(d)) && d.type) {
103113
const t = factory.createUnionTypeNode([
104114
...d.type.kind === SyntaxKind.UnionType ? (d.type as UnionTypeNode).types : [d.type],
105115
factory.createTypeReferenceNode("undefined")
106-
])
107-
changes.replaceNode(d.getSourceFile(), d.type, t)
116+
]);
117+
changes.replaceNode(d.getSourceFile(), d.type, t);
108118
}
109119
}
110120
}

0 commit comments

Comments
 (0)