Skip to content

Commit 41f0525

Browse files
committed
Offer a codefix for a lot more cases
1 parent 334ca65 commit 41f0525

File tree

7 files changed

+178
-67
lines changed

7 files changed

+178
-67
lines changed

src/compiler/checker.ts

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,7 @@ namespace ts {
634634
isArrayLikeType,
635635
isTypeInvalidDueToUnionDiscriminant,
636636
getExactOptionalUnassignableProperties,
637+
isExactOptionalPropertyMismatch,
637638
getAllPossiblePropertiesOfTypes,
638639
getSuggestedSymbolForNonexistentProperty,
639640
getSuggestionForNonexistentProperty,
@@ -16754,24 +16755,29 @@ namespace ts {
1675416755
let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType);
1675516756
if (!sourcePropType) continue;
1675616757
const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined);
16757-
const targetIsOptional = !!(propName && (getPropertyOfType(target, propName) || unknownSymbol).flags & SymbolFlags.Optional);
16758-
const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional);
16759-
targetPropType = removeMissingType(targetPropType, targetIsOptional);
16760-
sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional);
1676116758
if (!checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) {
1676216759
const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer);
16763-
if (elaborated) {
16764-
reportedError = true;
16765-
}
16766-
else {
16760+
reportedError = true;
16761+
if (!elaborated) {
1676716762
// Issue error on the prop itself, since the prop couldn't elaborate the error
1676816763
const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {};
1676916764
// Use the expression type, if available
1677016765
const specificSource = next ? checkExpressionForMutableLocationWithContextualType(next, sourcePropType) : sourcePropType;
16771-
const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
16772-
if (result && specificSource !== sourcePropType) {
16773-
// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
16774-
checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
16766+
if (exactOptionalPropertyTypes && isExactOptionalPropertyMismatch(specificSource, targetPropType)) {
16767+
const diag = createDiagnosticForNode(prop, Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, typeToString(specificSource), typeToString(targetPropType));
16768+
diagnostics.add(diag);
16769+
resultObj.errors = [diag];
16770+
}
16771+
else {
16772+
const targetIsOptional = !!(propName && (getPropertyOfType(target, propName) || unknownSymbol).flags & SymbolFlags.Optional);
16773+
const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional);
16774+
targetPropType = removeMissingType(targetPropType, targetIsOptional);
16775+
sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional);
16776+
const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
16777+
if (result && specificSource !== sourcePropType) {
16778+
// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
16779+
checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
16780+
}
1677516781
}
1677616782
if (resultObj.errors) {
1677716783
const reportedDiag = resultObj.errors[resultObj.errors.length - 1];
@@ -16799,7 +16805,6 @@ namespace ts {
1679916805
}
1680016806
}
1680116807
}
16802-
reportedError = true;
1680316808
}
1680416809
}
1680516810
}
@@ -19593,13 +19598,14 @@ namespace ts {
1959319598
}
1959419599

1959519600
function getExactOptionalUnassignableProperties(source: Type, target: Type) {
19596-
return checker.getPropertiesOfType(target).filter(targetProp => {
19597-
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
19598-
return sourceProp && targetProp.valueDeclaration
19599-
&& maybeTypeOfKind(getTypeOfSymbol(sourceProp), TypeFlags.Undefined)
19600-
&& hasQuestionToken(targetProp.valueDeclaration)
19601-
&& containsMissingType(getTypeOfSymbol(targetProp));
19602-
});
19601+
return getPropertiesOfType(target)
19602+
.filter(targetProp => isExactOptionalPropertyMismatch(getTypeOfPropertyOfType(source, targetProp.escapedName), getTypeOfSymbol(targetProp)));
19603+
}
19604+
19605+
function isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined) {
19606+
return !!source && !!target
19607+
&& !(getObjectFlags(source) & ObjectFlags.Tuple) && !(getObjectFlags(target) & ObjectFlags.Tuple)
19608+
&& maybeTypeOfKind(source, TypeFlags.Undefined) && !!containsMissingType(target);
1960319609
}
1960419610

1960519611
function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) {
@@ -32463,8 +32469,16 @@ namespace ts {
3246332469
Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access,
3246432470
Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access)
3246532471
&& (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) {
32472+
32473+
let headMessage: DiagnosticMessage | undefined;
32474+
if (exactOptionalPropertyTypes && isPropertyAccessExpression(left) && maybeTypeOfKind(valueType, TypeFlags.Undefined)) {
32475+
const target = getTypeOfPropertyOfType(getTypeOfExpression(left.expression), left.name.escapedText);
32476+
if (isExactOptionalPropertyMismatch(valueType, target)) {
32477+
headMessage = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target;
32478+
}
32479+
}
3246632480
// to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported
32467-
checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right);
32481+
checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right, headMessage);
3246832482
}
3246932483
}
3247032484
}

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,6 +1882,10 @@
18821882
"category": "Error",
18831883
"code": 2410
18841884
},
1885+
"Type '{0}' is not assignable to type '{1}' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the type of the target.": {
1886+
"category": "Error",
1887+
"code": 2412
1888+
},
18851889
"Property '{0}' of type '{1}' is not assignable to '{2}' index type '{3}'.": {
18861890
"category": "Error",
18871891
"code": 2411

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4306,6 +4306,7 @@ namespace ts {
43064306
*/
43074307
/* @internal */ isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean;
43084308
/* @internal */ getExactOptionalUnassignableProperties(source: Type, target: Type): Symbol[];
4309+
/* @internal */ isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined): boolean;
43094310
/**
43104311
* For a union, will include a property if it's defined in *any* of the member types.
43114312
* So for `{ a } | { b }`, this will include both `a` and `b`.

src/services/codefixes/addOptionalPropertyUndefined.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ namespace ts.codefix {
33
const addOptionalPropertyUndefined = "addOptionalPropertyUndefined";
44

55
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,
67
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,
7-
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
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,
89
];
910

1011
registerCodeFix({
@@ -48,12 +49,27 @@ namespace ts.codefix {
4849
}
4950
const { source: sourceNode, target: targetNode } = sourceTarget;
5051
const target = checker.getTypeAtLocation(targetNode);
52+
const source = checker.getTypeAtLocation(sourceNode);
5153
if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/\.d\.ts$/))) {
5254
return [];
5355
}
54-
return checker.getExactOptionalUnassignableProperties(checker.getTypeAtLocation(sourceNode), target);
56+
const targetPropertyType = getTargetPropertyType(checker, targetNode);
57+
if (targetPropertyType && checker.isExactOptionalPropertyMismatch(source, targetPropertyType)) {
58+
const s = checker.getSymbolAtLocation((targetNode as PropertyAccessExpression).name);
59+
return s ? [s] : [];
60+
}
61+
return checker.getExactOptionalUnassignableProperties(source, target);
5562
}
5663

64+
function getTargetPropertyType(checker: TypeChecker, targetNode: Node) {
65+
if (isPropertySignature(targetNode)) {
66+
return checker.getTypeAtLocation(targetNode.name);
67+
}
68+
else if (isPropertyAccessExpression(targetNode)) {
69+
return checker.getTypeOfPropertyOfType(checker.getTypeAtLocation(targetNode.expression), targetNode.name.text);
70+
}
71+
return undefined;
72+
}
5773
/**
5874
* Get the part of the incorrect assignment that is useful for type-checking
5975
* eg

0 commit comments

Comments
 (0)