diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 28428f4f31fcb..6855002c129c4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -635,6 +635,7 @@ namespace ts { isTupleType, isArrayLikeType, isTypeInvalidDueToUnionDiscriminant, + getExactOptionalProperties, getAllPossiblePropertiesOfTypes, getSuggestedSymbolForNonexistentProperty, getSuggestionForNonexistentProperty, @@ -16768,24 +16769,29 @@ namespace ts { let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); if (!sourcePropType) continue; const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined); - const targetIsOptional = !!(propName && (getPropertyOfType(target, propName) || unknownSymbol).flags & SymbolFlags.Optional); - const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional); - targetPropType = removeMissingType(targetPropType, targetIsOptional); - sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional); if (!checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); - if (elaborated) { - reportedError = true; - } - else { + reportedError = true; + if (!elaborated) { // Issue error on the prop itself, since the prop couldn't elaborate the error const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; // Use the expression type, if available const specificSource = next ? checkExpressionForMutableLocationWithContextualType(next, sourcePropType) : sourcePropType; - const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); - if (result && specificSource !== sourcePropType) { - // If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType - checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); + if (exactOptionalPropertyTypes && isExactOptionalPropertyMismatch(specificSource, targetPropType)) { + 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)); + diagnostics.add(diag); + resultObj.errors = [diag]; + } + else { + const targetIsOptional = !!(propName && (getPropertyOfType(target, propName) || unknownSymbol).flags & SymbolFlags.Optional); + const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional); + targetPropType = removeMissingType(targetPropType, targetIsOptional); + sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional); + const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); + if (result && specificSource !== sourcePropType) { + // If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType + checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); + } } if (resultObj.errors) { const reportedDiag = resultObj.errors[resultObj.errors.length - 1]; @@ -16813,7 +16819,6 @@ namespace ts { } } } - reportedError = true; } } } @@ -17679,10 +17684,18 @@ namespace ts { else if (sourceType === targetType) { message = Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated; } + else if (exactOptionalPropertyTypes && getExactOptionalUnassignableProperties(source, target).length) { + 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; + } else { message = Diagnostics.Type_0_is_not_assignable_to_type_1; } } + else if (message === Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1 + && exactOptionalPropertyTypes + && getExactOptionalUnassignableProperties(source, target).length) { + 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; + } reportError(message, generalizedSourceType, targetType); } @@ -19598,6 +19611,20 @@ namespace ts { return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral); } + function getExactOptionalUnassignableProperties(source: Type, target: Type) { + if (isTupleType(source) && isTupleType(target)) return emptyArray; + return getPropertiesOfType(target) + .filter(targetProp => isExactOptionalPropertyMismatch(getTypeOfPropertyOfType(source, targetProp.escapedName), getTypeOfSymbol(targetProp))); + } + + function isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined) { + return !!source && !!target && maybeTypeOfKind(source, TypeFlags.Undefined) && !!containsMissingType(target); + } + + function getExactOptionalProperties(type: Type) { + return getPropertiesOfType(type).filter(targetProp => containsMissingType(getTypeOfSymbol(targetProp))); + } + function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { return findMatchingDiscriminantType(source, target, isRelatedTo, /*skipPartial*/ true) || findMatchingTypeReferenceOrTypeAliasReference(source, target) || @@ -32479,8 +32506,16 @@ namespace ts { Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access, Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access) && (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) { + + let headMessage: DiagnosticMessage | undefined; + if (exactOptionalPropertyTypes && isPropertyAccessExpression(left) && maybeTypeOfKind(valueType, TypeFlags.Undefined)) { + const target = getTypeOfPropertyOfType(getTypeOfExpression(left.expression), left.name.escapedText); + if (isExactOptionalPropertyMismatch(valueType, target)) { + headMessage = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target; + } + } // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported - checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right); + checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right, headMessage); } } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 64b6c87d12c5c..91780f5309c8d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1738,6 +1738,10 @@ "category": "Error", "code": 2374 }, + "Type '{0}' is not assignable to type '{1}' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.": { + "category": "Error", + "code": 2375 + }, "A 'super' call must be the first statement in the constructor when a class contains initialized properties, parameter properties, or private identifiers.": { "category": "Error", "code": 2376 @@ -1750,6 +1754,10 @@ "category": "Error", "code": 2378 }, + "Argument of type '{0}' is not assignable to parameter of type '{1}' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.": { + "category": "Error", + "code": 2379 + }, "The return type of a 'get' accessor must be assignable to its 'set' accessor type": { "category": "Error", "code": 2380 @@ -1874,6 +1882,10 @@ "category": "Error", "code": 2410 }, + "Type '{0}' is not assignable to type '{1}' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target.": { + "category": "Error", + "code": 2412 + }, "Property '{0}' of type '{1}' is not assignable to '{2}' index type '{3}'.": { "category": "Error", "code": 2411 @@ -7118,6 +7130,10 @@ "category": "Message", "code": 95168 }, + "Add 'undefined' to optional property type": { + "category": "Message", + "code": 95169 + }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4d13e803fb9b4..6194f41a20d22 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4308,6 +4308,7 @@ namespace ts { * e.g. it specifies `kind: "a"` and obj has `kind: "b"`. */ /* @internal */ isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean; + /* @internal */ getExactOptionalProperties(type: Type): Symbol[]; /** * For a union, will include a property if it's defined in *any* of the member types. * So for `{ a } | { b }`, this will include both `a` and `b`. diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index af75c48f0bae0..322b00a81cfb7 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -641,7 +641,8 @@ namespace FourSlash { if (errors.length) { this.printErrorLog(/*expectErrors*/ false, errors); const error = errors[0]; - this.raiseError(`Found an error: ${this.formatPosition(error.file!, error.start!)}: ${error.messageText}`); + const message = typeof error.messageText === "string" ? error.messageText : error.messageText.messageText; + this.raiseError(`Found an error: ${this.formatPosition(error.file!, error.start!)}: ${message}`); } }); } diff --git a/src/services/codefixes/addMissingAwait.ts b/src/services/codefixes/addMissingAwait.ts index e3a15d8864144..01f7ece1a0388 100644 --- a/src/services/codefixes/addMissingAwait.ts +++ b/src/services/codefixes/addMissingAwait.ts @@ -32,7 +32,7 @@ namespace ts.codefix { errorCodes, getCodeActions: context => { const { sourceFile, errorCode, span, cancellationToken, program } = context; - const expression = getFixableErrorSpanExpression(sourceFile, errorCode, span, cancellationToken, program); + const expression = getAwaitErrorSpanExpression(sourceFile, errorCode, span, cancellationToken, program); if (!expression) { return; } @@ -48,7 +48,7 @@ namespace ts.codefix { const checker = context.program.getTypeChecker(); const fixedDeclarations = new Set(); return codeFixAll(context, errorCodes, (t, diagnostic) => { - const expression = getFixableErrorSpanExpression(sourceFile, diagnostic.code, diagnostic, cancellationToken, program); + const expression = getAwaitErrorSpanExpression(sourceFile, diagnostic.code, diagnostic, cancellationToken, program); if (!expression) { return; } @@ -59,6 +59,13 @@ namespace ts.codefix { }, }); + function getAwaitErrorSpanExpression(sourceFile: SourceFile, errorCode: number, span: TextSpan, cancellationToken: CancellationToken, program: Program) { + const expression = getFixableErrorSpanExpression(sourceFile, span); + return expression + && isMissingAwaitError(sourceFile, errorCode, span, cancellationToken, program) + && isInsideAwaitableBody(expression) ? expression : undefined; + } + function getDeclarationSiteFix(context: CodeFixContext | CodeFixAllContext, expression: Expression, errorCode: number, checker: TypeChecker, trackChanges: ContextualTrackChangesFunction, fixedDeclarations?: Set) { const { sourceFile, program, cancellationToken } = context; const awaitableInitializers = findAwaitableInitializers(expression, sourceFile, cancellationToken, program, checker); @@ -95,23 +102,6 @@ namespace ts.codefix { some(relatedInformation, related => related.code === Diagnostics.Did_you_forget_to_use_await.code)); } - function getFixableErrorSpanExpression(sourceFile: SourceFile, errorCode: number, span: TextSpan, cancellationToken: CancellationToken, program: Program): Expression | undefined { - const token = getTokenAtPosition(sourceFile, span.start); - // Checker has already done work to determine that await might be possible, and has attached - // related info to the node, so start by finding the expression that exactly matches up - // with the diagnostic range. - const expression = findAncestor(token, node => { - if (node.getStart(sourceFile) < span.start || node.getEnd() > textSpanEnd(span)) { - return "quit"; - } - return isExpression(node) && textSpansEqual(span, createTextSpanFromNode(node, sourceFile)); - }) as Expression | undefined; - - return expression - && isMissingAwaitError(sourceFile, errorCode, span, cancellationToken, program) - && isInsideAwaitableBody(expression) ? expression : undefined; - } - interface AwaitableInitializer { expression: Expression; declarationSymbol: Symbol; diff --git a/src/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts new file mode 100644 index 0000000000000..13b80ed8a11e1 --- /dev/null +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -0,0 +1,96 @@ +/* @internal */ +namespace ts.codefix { + const addOptionalPropertyUndefined = "addOptionalPropertyUndefined"; + + const errorCodes = [ + Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target.code, + 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, + 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, + ]; + + registerCodeFix({ + errorCodes, + getCodeActions(context) { + const typeChecker = context.program.getTypeChecker(); + const toAdd = getPropertiesToAdd(context.sourceFile, context.span, typeChecker); + if (!toAdd.length) { + return undefined; + } + const changes = textChanges.ChangeTracker.with(context, t => addUndefinedToOptionalProperty(t, toAdd)); + return [createCodeFixActionWithoutFixAll(addOptionalPropertyUndefined, changes, Diagnostics.Add_undefined_to_optional_property_type)]; + }, + fixIds: [addOptionalPropertyUndefined], + }); + + function getPropertiesToAdd(file: SourceFile, span: TextSpan, checker: TypeChecker): Symbol[] { + const sourceTarget = getSourceTarget(getFixableErrorSpanExpression(file, span), checker); + if (!sourceTarget) { + return emptyArray; + } + const { source: sourceNode, target: targetNode } = sourceTarget; + const target = shouldUseParentTypeOfProperty(sourceNode, targetNode, checker) + ? checker.getTypeAtLocation(targetNode.expression) + : checker.getTypeAtLocation(targetNode); + if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/\.d\.ts$/))) { + return emptyArray; + } + return checker.getExactOptionalProperties(target); + } + + function shouldUseParentTypeOfProperty(sourceNode: Node, targetNode: Node, checker: TypeChecker): targetNode is PropertyAccessExpression { + return isPropertyAccessExpression(targetNode) + && !!checker.getExactOptionalProperties(checker.getTypeAtLocation(targetNode.expression)).length + && checker.getTypeAtLocation(sourceNode) === checker.getUndefinedType(); + } + + /** + * Find the source and target of the incorrect assignment. + * The call is recursive for property assignments. + */ + function getSourceTarget(errorNode: Node | undefined, checker: TypeChecker): { source: Node, target: Node } | undefined { + if (!errorNode) { + return undefined; + } + else if (isBinaryExpression(errorNode.parent) && errorNode.parent.operatorToken.kind === SyntaxKind.EqualsToken) { + return { source: errorNode.parent.right, target: errorNode.parent.left }; + } + else if (isVariableDeclaration(errorNode.parent) && errorNode.parent.initializer) { + return { source: errorNode.parent.initializer, target: errorNode.parent.name }; + } + else if (isCallExpression(errorNode.parent)) { + const n = checker.getSymbolAtLocation(errorNode.parent.expression); + if (!n?.valueDeclaration || !isFunctionLikeKind(n.valueDeclaration.kind)) return undefined; + if (!isExpression(errorNode)) return undefined; + const i = errorNode.parent.arguments.indexOf(errorNode); + if (i === -1) return undefined; + const name = (n.valueDeclaration as any as SignatureDeclaration).parameters[i].name; + if (isIdentifier(name)) return { source: errorNode, target: name }; + } + else if (isPropertyAssignment(errorNode.parent) && isIdentifier(errorNode.parent.name) || + isShorthandPropertyAssignment(errorNode.parent)) { + const parentTarget = getSourceTarget(errorNode.parent.parent, checker); + if (!parentTarget) return undefined; + const prop = checker.getPropertyOfType(checker.getTypeAtLocation(parentTarget.target), (errorNode.parent.name as Identifier).text); + const declaration = prop?.declarations?.[0]; + if (!declaration) return undefined; + return { + source: isPropertyAssignment(errorNode.parent) ? errorNode.parent.initializer : errorNode.parent.name, + target: declaration + }; + } + return undefined; + } + + function addUndefinedToOptionalProperty(changes: textChanges.ChangeTracker, toAdd: Symbol[]) { + for (const add of toAdd) { + const d = add.valueDeclaration; + if (d && (isPropertySignature(d) || isPropertyDeclaration(d)) && d.type) { + const t = factory.createUnionTypeNode([ + ...d.type.kind === SyntaxKind.UnionType ? (d.type as UnionTypeNode).types : [d.type], + factory.createTypeReferenceNode("undefined") + ]); + changes.replaceNode(d.getSourceFile(), d.type, t); + } + } + } +} diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index f1aa438fefb57..1e0934804edda 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -57,6 +57,7 @@ "codefixes/addMissingDeclareProperty.ts", "codefixes/addMissingInvocationForDecorator.ts", "codefixes/addNameToNamelessParameter.ts", + "codefixes/addOptionalPropertyUndefined.ts", "codefixes/annotateWithTypeFromJSDoc.ts", "codefixes/convertFunctionToEs6Class.ts", "codefixes/convertToAsyncFunction.ts", diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 3d503bff65912..2142286b8c67a 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -3085,6 +3085,22 @@ namespace ts { return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition); } + /* @internal */ + export function getFixableErrorSpanExpression(sourceFile: SourceFile, span: TextSpan): Expression | undefined { + const token = getTokenAtPosition(sourceFile, span.start); + // Checker has already done work to determine that await might be possible, and has attached + // related info to the node, so start by finding the expression that exactly matches up + // with the diagnostic range. + const expression = findAncestor(token, node => { + if (node.getStart(sourceFile) < span.start || node.getEnd() > textSpanEnd(span)) { + return "quit"; + } + return isExpression(node) && textSpansEqual(span, createTextSpanFromNode(node, sourceFile)); + }) as Expression | undefined; + + return expression; + } + /** * If the provided value is an array, the mapping function is applied to each element; otherwise, the mapping function is applied * to the provided value itself. diff --git a/tests/baselines/reference/strictOptionalProperties1.errors.txt b/tests/baselines/reference/strictOptionalProperties1.errors.txt index d0401220ec0db..68c7da1f9e449 100644 --- a/tests/baselines/reference/strictOptionalProperties1.errors.txt +++ b/tests/baselines/reference/strictOptionalProperties1.errors.txt @@ -1,9 +1,9 @@ -tests/cases/compiler/strictOptionalProperties1.ts(6,5): error TS2322: Type 'undefined' is not assignable to type 'string'. -tests/cases/compiler/strictOptionalProperties1.ts(12,5): error TS2322: Type 'string | undefined' is not assignable to type 'string'. +tests/cases/compiler/strictOptionalProperties1.ts(6,5): error TS2412: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. +tests/cases/compiler/strictOptionalProperties1.ts(12,5): error TS2412: Type 'string | undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. Type 'undefined' is not assignable to type 'string'. -tests/cases/compiler/strictOptionalProperties1.ts(20,9): error TS2322: Type 'undefined' is not assignable to type 'string'. -tests/cases/compiler/strictOptionalProperties1.ts(28,9): error TS2322: Type 'undefined' is not assignable to type 'string'. -tests/cases/compiler/strictOptionalProperties1.ts(53,5): error TS2322: Type 'undefined' is not assignable to type 'string'. +tests/cases/compiler/strictOptionalProperties1.ts(20,9): error TS2412: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. +tests/cases/compiler/strictOptionalProperties1.ts(28,9): error TS2412: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. +tests/cases/compiler/strictOptionalProperties1.ts(53,5): error TS2412: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. tests/cases/compiler/strictOptionalProperties1.ts(60,5): error TS2322: Type 'undefined' is not assignable to type 'string'. tests/cases/compiler/strictOptionalProperties1.ts(64,5): error TS2322: Type '[number, string?, string?]' is not assignable to type '[number, string?]'. Target allows only 2 element(s) but source may have more. @@ -11,24 +11,43 @@ tests/cases/compiler/strictOptionalProperties1.ts(73,5): error TS2322: Type '[nu Target allows only 3 element(s) but source may have more. tests/cases/compiler/strictOptionalProperties1.ts(74,5): error TS2322: Type '[never?, never?, true?]' is not assignable to type '[number, string?, boolean?]'. Source provides no match for required element at position 0 in target. -tests/cases/compiler/strictOptionalProperties1.ts(75,14): error TS2322: Type 'undefined' is not assignable to type 'string'. -tests/cases/compiler/strictOptionalProperties1.ts(99,34): error TS2322: Type 'undefined' is not assignable to type 'string'. -tests/cases/compiler/strictOptionalProperties1.ts(105,45): error TS2322: Type 'undefined' is not assignable to type 'string'. -tests/cases/compiler/strictOptionalProperties1.ts(106,55): error TS2322: Type 'undefined' is not assignable to type 'boolean'. -tests/cases/compiler/strictOptionalProperties1.ts(107,45): error TS2322: Type 'undefined' is not assignable to type 'string'. -tests/cases/compiler/strictOptionalProperties1.ts(107,56): error TS2322: Type 'undefined' is not assignable to type 'boolean'. -tests/cases/compiler/strictOptionalProperties1.ts(111,31): error TS2322: Type 'undefined' is not assignable to type 'number'. +tests/cases/compiler/strictOptionalProperties1.ts(75,5): error TS2322: Type '[number, undefined, true]' is not assignable to type '[number, string?, boolean?]'. + Type at position 1 in source is not compatible with type at position 1 in target. + Type 'undefined' is not assignable to type 'string'. +tests/cases/compiler/strictOptionalProperties1.ts(99,7): error TS2375: Type '{ foo: undefined; bar: string; }' is not assignable to type 'InputProps' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. + Types of property 'foo' are incompatible. + Type 'undefined' is not assignable to type 'string'. +tests/cases/compiler/strictOptionalProperties1.ts(105,7): error TS2322: Type '[number, undefined]' is not assignable to type '[number, string?, boolean?]'. + Type at position 1 in source is not compatible with type at position 1 in target. + Type 'undefined' is not assignable to type 'string'. +tests/cases/compiler/strictOptionalProperties1.ts(106,7): error TS2322: Type '[number, string, undefined]' is not assignable to type '[number, string?, boolean?]'. + Type at position 2 in source is not compatible with type at position 2 in target. + Type 'undefined' is not assignable to type 'boolean'. +tests/cases/compiler/strictOptionalProperties1.ts(107,7): error TS2322: Type '[number, undefined, undefined]' is not assignable to type '[number, string?, boolean?]'. + Type at position 1 in source is not compatible with type at position 1 in target. + Type 'undefined' is not assignable to type 'string'. +tests/cases/compiler/strictOptionalProperties1.ts(111,7): error TS2375: Type '{ foo: undefined; }' is not assignable to type '{ foo?: number; }' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. + Types of property 'foo' are incompatible. + Type 'undefined' is not assignable to type 'number'. tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property 'bar' of type 'string | undefined' is not assignable to 'string' index type 'string'. -tests/cases/compiler/strictOptionalProperties1.ts(192,1): error TS2322: Type '{ a: number; b: string | undefined; }' is not assignable to type '{ [x: string]: string | number; }'. +tests/cases/compiler/strictOptionalProperties1.ts(190,15): error TS2451: Cannot redeclare block-scoped variable 'e'. +tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type 'string | boolean | undefined' is not assignable to type 'string | number | undefined'. + Type 'false' is not assignable to type 'string | number | undefined'. +tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'string | boolean | undefined' is not assignable to type 'string | number' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. + Type 'undefined' is not assignable to type 'string | number'. +tests/cases/compiler/strictOptionalProperties1.ts(195,1): error TS2375: Type '{ name: string; email: undefined; }' is not assignable to type 'U2' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. + Types of property 'email' are incompatible. + Type 'undefined' is not assignable to type 'string | number'. +tests/cases/compiler/strictOptionalProperties1.ts(206,13): error TS2451: Cannot redeclare block-scoped variable 'e'. +tests/cases/compiler/strictOptionalProperties1.ts(210,1): error TS2322: Type '{ a: number; b: string | undefined; }' is not assignable to type '{ [x: string]: string | number; }'. Property 'b' is incompatible with index signature. Type 'string | undefined' is not assignable to type 'string | number'. Type 'undefined' is not assignable to type 'string | number'. -tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type '{ a: number; b?: string | undefined; }' is not assignable to type '{ [x: string]: string | number; }'. - Property 'b' is incompatible with index signature. - Type 'string | undefined' is not assignable to type 'string | number'. +tests/cases/compiler/strictOptionalProperties1.ts(211,1): error TS2322: Type 'string | boolean | undefined' is not assignable to type '{ [x: string]: string | number; }'. + Type 'undefined' is not assignable to type '{ [x: string]: string | number; }'. -==== tests/cases/compiler/strictOptionalProperties1.ts (19 errors) ==== +==== tests/cases/compiler/strictOptionalProperties1.ts (23 errors) ==== function f1(obj: { a?: string, b?: string | undefined }) { let a = obj.a; // string | undefined let b = obj.b; // string | undefined @@ -36,7 +55,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type '{ obj.b = 'hello'; obj.a = undefined; // Error ~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. +!!! error TS2412: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. obj.b = undefined; } @@ -44,8 +63,8 @@ tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type '{ obj = obj; obj.a = obj.a; // Error ~~~~~ -!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'. -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. +!!! error TS2412: Type 'string | undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. +!!! error TS2412: Type 'undefined' is not assignable to type 'string'. obj.b = obj.b; if ('a' in obj) { obj.a; @@ -55,7 +74,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type '{ obj.a; obj.a = obj.a; // Error ~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. +!!! error TS2412: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. } if (obj.hasOwnProperty('a')) { obj.a; @@ -65,7 +84,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type '{ obj.a; obj.a = obj.a; // Error ~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. +!!! error TS2412: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. } if ('b' in obj) { obj.b; @@ -92,7 +111,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type '{ obj.b = 'hello'; obj.a = undefined; // Error ~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. +!!! error TS2412: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. obj.b = undefined; } @@ -126,8 +145,10 @@ tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type '{ !!! error TS2322: Type '[never?, never?, true?]' is not assignable to type '[number, string?, boolean?]'. !!! error TS2322: Source provides no match for required element at position 0 in target. t = [42, undefined, true]; // Error - ~~~~~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + ~ +!!! error TS2322: Type '[number, undefined, true]' is not assignable to type '[number, string?, boolean?]'. +!!! error TS2322: Type at position 1 in source is not compatible with type at position 1 in target. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. } function f6() { @@ -152,32 +173,38 @@ tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type '{ const defaultProps: Pick = { foo: 'foo' }; const inputProps: InputProps = { foo: undefined, bar: 'bar' }; - ~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. -!!! related TS6500 tests/cases/compiler/strictOptionalProperties1.ts:94:5: The expected type comes from property 'foo' which is declared here on type 'InputProps' + ~~~~~~~~~~ +!!! error TS2375: Type '{ foo: undefined; bar: string; }' is not assignable to type 'InputProps' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Types of property 'foo' are incompatible. +!!! error TS2375: Type 'undefined' is not assignable to type 'string'. const completeProps: Props = { ...defaultProps, ...inputProps }; // Example from #13195 const t1: [number, string?, boolean?] = [1]; const t2: [number, string?, boolean?] = [1, undefined]; - ~~~~~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + ~~ +!!! error TS2322: Type '[number, undefined]' is not assignable to type '[number, string?, boolean?]'. +!!! error TS2322: Type at position 1 in source is not compatible with type at position 1 in target. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. const t3: [number, string?, boolean?] = [1, "string", undefined]; - ~~~~~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'. + ~~ +!!! error TS2322: Type '[number, string, undefined]' is not assignable to type '[number, string?, boolean?]'. +!!! error TS2322: Type at position 2 in source is not compatible with type at position 2 in target. +!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'. const t4: [number, string?, boolean?] = [1, undefined, undefined]; - ~~~~~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. - ~~~~~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'. + ~~ +!!! error TS2322: Type '[number, undefined, undefined]' is not assignable to type '[number, string?, boolean?]'. +!!! error TS2322: Type at position 1 in source is not compatible with type at position 1 in target. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. // Example from #13195 const x: { foo?: number } = { foo: undefined }; - ~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'number'. -!!! related TS6500 tests/cases/compiler/strictOptionalProperties1.ts:111:12: The expected type comes from property 'foo' which is declared here on type '{ foo?: number; }' + ~ +!!! error TS2375: Type '{ foo: undefined; }' is not assignable to type '{ foo?: number; }' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Types of property 'foo' are incompatible. +!!! error TS2375: Type 'undefined' is not assignable to type 'number'. const y: { foo: number } = { foo: 123, ...x }; // Index signatures and strict optional properties @@ -250,6 +277,36 @@ tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type '{ declare function bb(input: number): void; + interface U1 { + name: string + email?: string | number | undefined + } + interface U2 { + name: string + email?: string | number + } + declare const e: string | boolean | undefined + ~ +!!! error TS2451: Cannot redeclare block-scoped variable 'e'. + declare const u1: U1 + declare let u2: U2 + u1.email = e // error, but only because boolean isn't in email's type + ~~~~~~~~ +!!! error TS2322: Type 'string | boolean | undefined' is not assignable to type 'string | number | undefined'. +!!! error TS2322: Type 'false' is not assignable to type 'string | number | undefined'. + u2.email = e // error, and suggest adding undefined + ~~~~~~~~ +!!! error TS2412: Type 'string | boolean | undefined' is not assignable to type 'string | number' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. +!!! error TS2412: Type 'undefined' is not assignable to type 'string | number'. + u2 = { + ~~ +!!! error TS2375: Type '{ name: string; email: undefined; }' is not assignable to type 'U2' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Types of property 'email' are incompatible. +!!! error TS2375: Type 'undefined' is not assignable to type 'string | number'. + name: 'hi', + email: undefined + } + // Repro from #44437 declare var a: {[x: string]: number | string } @@ -257,6 +314,8 @@ tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type '{ declare var c: {a: number, b?: string} declare var d: {a: number, b: string | undefined } declare var e: {a: number, b?: string | undefined } + ~ +!!! error TS2451: Cannot redeclare block-scoped variable 'e'. a = b; a = c; @@ -268,7 +327,6 @@ tests/cases/compiler/strictOptionalProperties1.ts(193,1): error TS2322: Type '{ !!! error TS2322: Type 'undefined' is not assignable to type 'string | number'. a = e; // Error ~ -!!! error TS2322: Type '{ a: number; b?: string | undefined; }' is not assignable to type '{ [x: string]: string | number; }'. -!!! error TS2322: Property 'b' is incompatible with index signature. -!!! error TS2322: Type 'string | undefined' is not assignable to type 'string | number'. +!!! error TS2322: Type 'string | boolean | undefined' is not assignable to type '{ [x: string]: string | number; }'. +!!! error TS2322: Type 'undefined' is not assignable to type '{ [x: string]: string | number; }'. \ No newline at end of file diff --git a/tests/baselines/reference/strictOptionalProperties1.js b/tests/baselines/reference/strictOptionalProperties1.js index 0ae9f9087f883..ba3536e97f7f4 100644 --- a/tests/baselines/reference/strictOptionalProperties1.js +++ b/tests/baselines/reference/strictOptionalProperties1.js @@ -180,6 +180,24 @@ function aa(input: Bar): void { declare function bb(input: number): void; +interface U1 { + name: string + email?: string | number | undefined +} +interface U2 { + name: string + email?: string | number +} +declare const e: string | boolean | undefined +declare const u1: U1 +declare let u2: U2 +u1.email = e // error, but only because boolean isn't in email's type +u2.email = e // error, and suggest adding undefined +u2 = { + name: 'hi', + email: undefined +} + // Repro from #44437 declare var a: {[x: string]: number | string } @@ -322,6 +340,12 @@ function aa(input) { var notUndefinedVal = expectNotUndefined(input.bar); bb(notUndefinedVal); } +u1.email = e; // error, but only because boolean isn't in email's type +u2.email = e; // error, and suggest adding undefined +u2 = { + name: 'hi', + email: undefined +}; a = b; a = c; a = d; // Error @@ -399,6 +423,17 @@ interface Bar { } declare function aa(input: Bar): void; declare function bb(input: number): void; +interface U1 { + name: string; + email?: string | number | undefined; +} +interface U2 { + name: string; + email?: string | number; +} +declare const e: string | boolean | undefined; +declare const u1: U1; +declare let u2: U2; declare var a: { [x: string]: number | string; }; diff --git a/tests/baselines/reference/strictOptionalProperties1.symbols b/tests/baselines/reference/strictOptionalProperties1.symbols index bb363740008ff..2cdd1ddce6dc0 100644 --- a/tests/baselines/reference/strictOptionalProperties1.symbols +++ b/tests/baselines/reference/strictOptionalProperties1.symbols @@ -571,45 +571,97 @@ declare function bb(input: number): void; >bb : Symbol(bb, Decl(strictOptionalProperties1.ts, 177, 1)) >input : Symbol(input, Decl(strictOptionalProperties1.ts, 179, 20)) +interface U1 { +>U1 : Symbol(U1, Decl(strictOptionalProperties1.ts, 179, 41)) + + name: string +>name : Symbol(U1.name, Decl(strictOptionalProperties1.ts, 181, 14)) + + email?: string | number | undefined +>email : Symbol(U1.email, Decl(strictOptionalProperties1.ts, 182, 16)) +} +interface U2 { +>U2 : Symbol(U2, Decl(strictOptionalProperties1.ts, 184, 1)) + + name: string +>name : Symbol(U2.name, Decl(strictOptionalProperties1.ts, 185, 14)) + + email?: string | number +>email : Symbol(U2.email, Decl(strictOptionalProperties1.ts, 186, 16)) +} +declare const e: string | boolean | undefined +>e : Symbol(e, Decl(strictOptionalProperties1.ts, 189, 13)) + +declare const u1: U1 +>u1 : Symbol(u1, Decl(strictOptionalProperties1.ts, 190, 13)) +>U1 : Symbol(U1, Decl(strictOptionalProperties1.ts, 179, 41)) + +declare let u2: U2 +>u2 : Symbol(u2, Decl(strictOptionalProperties1.ts, 191, 11)) +>U2 : Symbol(U2, Decl(strictOptionalProperties1.ts, 184, 1)) + +u1.email = e // error, but only because boolean isn't in email's type +>u1.email : Symbol(U1.email, Decl(strictOptionalProperties1.ts, 182, 16)) +>u1 : Symbol(u1, Decl(strictOptionalProperties1.ts, 190, 13)) +>email : Symbol(U1.email, Decl(strictOptionalProperties1.ts, 182, 16)) +>e : Symbol(e, Decl(strictOptionalProperties1.ts, 189, 13)) + +u2.email = e // error, and suggest adding undefined +>u2.email : Symbol(U2.email, Decl(strictOptionalProperties1.ts, 186, 16)) +>u2 : Symbol(u2, Decl(strictOptionalProperties1.ts, 191, 11)) +>email : Symbol(U2.email, Decl(strictOptionalProperties1.ts, 186, 16)) +>e : Symbol(e, Decl(strictOptionalProperties1.ts, 189, 13)) + +u2 = { +>u2 : Symbol(u2, Decl(strictOptionalProperties1.ts, 191, 11)) + + name: 'hi', +>name : Symbol(name, Decl(strictOptionalProperties1.ts, 194, 6)) + + email: undefined +>email : Symbol(email, Decl(strictOptionalProperties1.ts, 195, 15)) +>undefined : Symbol(undefined) +} + // Repro from #44437 declare var a: {[x: string]: number | string } ->a : Symbol(a, Decl(strictOptionalProperties1.ts, 183, 11)) ->x : Symbol(x, Decl(strictOptionalProperties1.ts, 183, 17)) +>a : Symbol(a, Decl(strictOptionalProperties1.ts, 201, 11)) +>x : Symbol(x, Decl(strictOptionalProperties1.ts, 201, 17)) declare var b: {a: number, b: string} ->b : Symbol(b, Decl(strictOptionalProperties1.ts, 184, 11)) ->a : Symbol(a, Decl(strictOptionalProperties1.ts, 184, 16)) ->b : Symbol(b, Decl(strictOptionalProperties1.ts, 184, 26)) +>b : Symbol(b, Decl(strictOptionalProperties1.ts, 202, 11)) +>a : Symbol(a, Decl(strictOptionalProperties1.ts, 202, 16)) +>b : Symbol(b, Decl(strictOptionalProperties1.ts, 202, 26)) declare var c: {a: number, b?: string} ->c : Symbol(c, Decl(strictOptionalProperties1.ts, 185, 11)) ->a : Symbol(a, Decl(strictOptionalProperties1.ts, 185, 16)) ->b : Symbol(b, Decl(strictOptionalProperties1.ts, 185, 26)) +>c : Symbol(c, Decl(strictOptionalProperties1.ts, 203, 11)) +>a : Symbol(a, Decl(strictOptionalProperties1.ts, 203, 16)) +>b : Symbol(b, Decl(strictOptionalProperties1.ts, 203, 26)) declare var d: {a: number, b: string | undefined } ->d : Symbol(d, Decl(strictOptionalProperties1.ts, 186, 11)) ->a : Symbol(a, Decl(strictOptionalProperties1.ts, 186, 16)) ->b : Symbol(b, Decl(strictOptionalProperties1.ts, 186, 26)) +>d : Symbol(d, Decl(strictOptionalProperties1.ts, 204, 11)) +>a : Symbol(a, Decl(strictOptionalProperties1.ts, 204, 16)) +>b : Symbol(b, Decl(strictOptionalProperties1.ts, 204, 26)) declare var e: {a: number, b?: string | undefined } ->e : Symbol(e, Decl(strictOptionalProperties1.ts, 187, 11)) ->a : Symbol(a, Decl(strictOptionalProperties1.ts, 187, 16)) ->b : Symbol(b, Decl(strictOptionalProperties1.ts, 187, 26)) +>e : Symbol(e, Decl(strictOptionalProperties1.ts, 205, 11)) +>a : Symbol(a, Decl(strictOptionalProperties1.ts, 205, 16)) +>b : Symbol(b, Decl(strictOptionalProperties1.ts, 205, 26)) a = b; ->a : Symbol(a, Decl(strictOptionalProperties1.ts, 183, 11)) ->b : Symbol(b, Decl(strictOptionalProperties1.ts, 184, 11)) +>a : Symbol(a, Decl(strictOptionalProperties1.ts, 201, 11)) +>b : Symbol(b, Decl(strictOptionalProperties1.ts, 202, 11)) a = c; ->a : Symbol(a, Decl(strictOptionalProperties1.ts, 183, 11)) ->c : Symbol(c, Decl(strictOptionalProperties1.ts, 185, 11)) +>a : Symbol(a, Decl(strictOptionalProperties1.ts, 201, 11)) +>c : Symbol(c, Decl(strictOptionalProperties1.ts, 203, 11)) a = d; // Error ->a : Symbol(a, Decl(strictOptionalProperties1.ts, 183, 11)) ->d : Symbol(d, Decl(strictOptionalProperties1.ts, 186, 11)) +>a : Symbol(a, Decl(strictOptionalProperties1.ts, 201, 11)) +>d : Symbol(d, Decl(strictOptionalProperties1.ts, 204, 11)) a = e; // Error ->a : Symbol(a, Decl(strictOptionalProperties1.ts, 183, 11)) ->e : Symbol(e, Decl(strictOptionalProperties1.ts, 187, 11)) +>a : Symbol(a, Decl(strictOptionalProperties1.ts, 201, 11)) +>e : Symbol(e, Decl(strictOptionalProperties1.ts, 189, 13)) diff --git a/tests/baselines/reference/strictOptionalProperties1.types b/tests/baselines/reference/strictOptionalProperties1.types index 939bb5d724390..4eb23fd31c8d0 100644 --- a/tests/baselines/reference/strictOptionalProperties1.types +++ b/tests/baselines/reference/strictOptionalProperties1.types @@ -683,6 +683,57 @@ declare function bb(input: number): void; >bb : (input: number) => void >input : number +interface U1 { + name: string +>name : string + + email?: string | number | undefined +>email : string | number | undefined +} +interface U2 { + name: string +>name : string + + email?: string | number +>email : string | number | undefined +} +declare const e: string | boolean | undefined +>e : string | boolean | undefined + +declare const u1: U1 +>u1 : U1 + +declare let u2: U2 +>u2 : U2 + +u1.email = e // error, but only because boolean isn't in email's type +>u1.email = e : string | boolean | undefined +>u1.email : string | number | undefined +>u1 : U1 +>email : string | number | undefined +>e : string | boolean | undefined + +u2.email = e // error, and suggest adding undefined +>u2.email = e : string | boolean | undefined +>u2.email : string | number +>u2 : U2 +>email : string | number +>e : string | boolean | undefined + +u2 = { +>u2 = { name: 'hi', email: undefined} : { name: string; email: undefined; } +>u2 : U2 +>{ name: 'hi', email: undefined} : { name: string; email: undefined; } + + name: 'hi', +>name : string +>'hi' : "hi" + + email: undefined +>email : undefined +>undefined : undefined +} + // Repro from #44437 declare var a: {[x: string]: number | string } @@ -725,7 +776,7 @@ a = d; // Error >d : { a: number; b: string | undefined; } a = e; // Error ->a = e : { a: number; b?: string | undefined; } +>a = e : string | boolean | undefined >a : { [x: string]: string | number; } ->e : { a: number; b?: string | undefined; } +>e : string | boolean | undefined diff --git a/tests/cases/compiler/strictOptionalProperties1.ts b/tests/cases/compiler/strictOptionalProperties1.ts index 323dfa7eac976..2babf2bdb5bf1 100644 --- a/tests/cases/compiler/strictOptionalProperties1.ts +++ b/tests/cases/compiler/strictOptionalProperties1.ts @@ -183,6 +183,24 @@ function aa(input: Bar): void { declare function bb(input: number): void; +interface U1 { + name: string + email?: string | number | undefined +} +interface U2 { + name: string + email?: string | number +} +declare const e: string | boolean | undefined +declare const u1: U1 +declare let u2: U2 +u1.email = e // error, but only because boolean isn't in email's type +u2.email = e // error, and suggest adding undefined +u2 = { + name: 'hi', + email: undefined +} + // Repro from #44437 declare var a: {[x: string]: number | string } diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties1.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties1.ts new file mode 100644 index 0000000000000..993263eeb003e --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties1.ts @@ -0,0 +1,32 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface I { +//// a?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var i: I +//// declare var j: J +//// i/**/ = j +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface I { + a?: number | undefined +} +interface J { + a?: number | undefined +} +declare var i: I +declare var j: J +i = j`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties10.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties10.ts new file mode 100644 index 0000000000000..57065123f1636 --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties10.ts @@ -0,0 +1,32 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface IF { +//// a?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var j: J +//// function fi(if_: IF) { return if_ } +//// fi(j/**/) +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface IF { + a?: number | undefined +} +interface J { + a?: number | undefined +} +declare var j: J +function fi(if_: IF) { return if_ } +fi(j)`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties11.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties11.ts new file mode 100644 index 0000000000000..094c0288ecead --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties11.ts @@ -0,0 +1,36 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface IC { +//// a?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var j: J +//// class C { +//// ic: IC +//// m() { this.ic/**/ = j } +//// } +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface IC { + a?: number | undefined +} +interface J { + a?: number | undefined +} +declare var j: J +class C { + ic: IC + m() { this.ic = j } +}`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties12.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties12.ts new file mode 100644 index 0000000000000..0e81cc8e67e11 --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties12.ts @@ -0,0 +1,38 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface IC2 { +//// a?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var j: J +//// class C { +//// ic2: IC2 +//// } +//// var c = new C() +//// c.ic2/**/ = j +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface IC2 { + a?: number | undefined +} +interface J { + a?: number | undefined +} +declare var j: J +class C { + ic2: IC2 +} +var c = new C() +c.ic2 = j`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties13.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties13.ts new file mode 100644 index 0000000000000..f2209299ff08d --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties13.ts @@ -0,0 +1,36 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface ICP { +//// a?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var j: J +//// class CP { +//// #icp: ICP +//// m() { this.#icp/**/ = j; console.log(this.#icp) } +//// } +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface ICP { + a?: number | undefined +} +interface J { + a?: number | undefined +} +declare var j: J +class CP { + #icp: ICP + m() { this.#icp = j; console.log(this.#icp) } +}`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties14.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties14.ts new file mode 100644 index 0000000000000..73d45770cb30e --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties14.ts @@ -0,0 +1,32 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface ID { +//// a?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var j: J +//// declare var id: ID +//// ({ id/**/ } = { id: j }) +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface ID { + a?: number | undefined +} +interface J { + a?: number | undefined +} +declare var j: J +declare var id: ID +({ id } = { id: j })`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties15.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties15.ts new file mode 100644 index 0000000000000..ada582dfcf9fd --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties15.ts @@ -0,0 +1,34 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface More { +//// a?: number +//// b?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var j: J +//// declare var more: More +//// more/**/ = j +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface More { + a?: number | undefined + b?: number | undefined +} +interface J { + a?: number | undefined +} +declare var j: J +declare var more: More +more = j`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties16.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties16.ts new file mode 100644 index 0000000000000..4ab2c6549fb3a --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties16.ts @@ -0,0 +1,30 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface Assignment { +//// a?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var j: J +//// var assignment/**/: Assignment = j +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface Assignment { + a?: number | undefined +} +interface J { + a?: number | undefined +} +declare var j: J +var assignment: Assignment = j`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties17.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties17.ts new file mode 100644 index 0000000000000..7d45bff83ffef --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties17.ts @@ -0,0 +1,30 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface PropertyAssignment { +//// a?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var j: J +//// var opa/**/: { pa: PropertyAssignment } = { pa: j } +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface PropertyAssignment { + a?: number | undefined +} +interface J { + a?: number | undefined +} +declare var j: J +var opa: { pa: PropertyAssignment } = { pa: j }`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties18.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties18.ts new file mode 100644 index 0000000000000..fe7f9f87b5b0b --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties18.ts @@ -0,0 +1,30 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface ShorthandPropertyAssignment { +//// a?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var j: J +//// var ospa/**/: { j: ShorthandPropertyAssignment } = { j } +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface ShorthandPropertyAssignment { + a?: number | undefined +} +interface J { + a?: number | undefined +} +declare var j: J +var ospa: { j: ShorthandPropertyAssignment } = { j }`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties19.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties19.ts new file mode 100644 index 0000000000000..39771c61e2cf9 --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties19.ts @@ -0,0 +1,32 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface FPA { +//// a?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var j: J +//// declare function fpa(fpa: { fpa: FPA }): void +//// fpa({ fpa: j }/**/) +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface FPA { + a?: number | undefined +} +interface J { + a?: number | undefined +} +declare var j: J +declare function fpa(fpa: { fpa: FPA }): void +fpa({ fpa: j })`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties3.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties3.ts new file mode 100644 index 0000000000000..12f508e4e9a82 --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties3.ts @@ -0,0 +1,18 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +// @Filename: fixExactOptionalUnassignableProperties2.ts +//// import { INodeModules } from 'foo' +//// interface J { +//// a?: number | undefined +//// } +//// declare var inm: INodeModules +//// declare var j: J +//// inm/**/ = j +//// console.log(inm) +// @Filename: node_modules/@types/foo/index.d.ts +//// export interface INodeModules { +//// a?: number +//// } +verify.codeFixAvailable([]); diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties4.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties4.ts new file mode 100644 index 0000000000000..466bfdf7e3051 --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties4.ts @@ -0,0 +1,30 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +// @Filename: fixExactOptionalUnassignableProperties4.ts +//// interface User { +//// name: string +//// email?: string +//// } +//// const user: User = { +//// name: "Andrew", +//// email: undefined, +//// } +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface User { + name: string + email?: string | undefined +} +const user: User = { + name: "Andrew", + email: undefined, +}`, +}); diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties5.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties5.ts new file mode 100644 index 0000000000000..8756c3002a5c0 --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties5.ts @@ -0,0 +1,27 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +// @Filename: fixExactOptionalUnassignableProperties5.ts +//// interface User { +//// name: string +//// email?: string +//// } +//// declare const user: User +//// user.email = undefined; +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`interface User { + name: string + email?: string | undefined +} +declare const user: User +user.email = undefined;`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts new file mode 100644 index 0000000000000..32eb41a370288 --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts @@ -0,0 +1,29 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +// @Filename: fixExactOptionalUnassignableProperties6.ts +// based on snapshotterInjected.ts in microsoft/playwright +//// type Data = { +//// f?: (x: number) => void, +//// additional?: number, +//// nop: string, +//// }; +//// declare function e(o: any): Data; +//// e(101).f = undefined +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`type Data = { + f?: ((x: number) => void) | undefined, + additional?: number | undefined, + nop: string, +}; +declare function e(o: any): Data; +e(101).f = undefined`, +}); diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties7.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties7.ts new file mode 100644 index 0000000000000..43b60f51d8989 --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties7.ts @@ -0,0 +1,13 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +// @Filename: fixExactOptionalUnassignableProperties6.ts +// based on snapshotterInjected.ts in microsoft/playwright +//// class Feh { +//// _requestFinished(error?: string) { +//// this._finishedPromiseCallback({ error/**/ }); +//// } +//// private _finishedPromiseCallback: (arg: { error?: string }) => void = () => {}; +//// } +verify.codeFixAvailable([ ]); \ No newline at end of file diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties8.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties8.ts new file mode 100644 index 0000000000000..b87ce681b203d --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties8.ts @@ -0,0 +1,29 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +// @Filename: fixExactOptionalUnassignableProperties6.ts +// based on snapshotterInjected.ts in microsoft/playwright +//// type Data = { +//// x?: { +//// y?: number +//// } +//// } +//// declare var d: Data +//// d.x = { y: undefined } +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } +]); +verify.codeFix({ + description: ts.Diagnostics.Add_undefined_to_optional_property_type.message, + index: 0, + newFileContent: +`type Data = { + x?: { + y?: number | undefined + } +} +declare var d: Data +d.x = { y: undefined }`, +}); + diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties9.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties9.ts new file mode 100644 index 0000000000000..54ddf6c450417 --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties9.ts @@ -0,0 +1,14 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +//// interface IAny { +//// a?: any +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var iany: IAny +//// declare var j: J +//// iany/**/ = j +verify.codeFixAvailable([]);