From 664d7492c1e6c969f5709b25be3f8f0f6631ba3d Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 13 Jul 2021 08:36:13 -0700 Subject: [PATCH 01/16] Simple first version Doesn't cover or test any complicated variations. --- src/compiler/checker.ts | 19 ++++- src/compiler/diagnosticMessages.json | 12 +++ src/compiler/types.ts | 1 + .../codefixes/addOptionalPropertyUndefined.ts | 77 +++++++++++++++++++ src/services/tsconfig.json | 1 + ...fixExactOptionalUnassignableProperties1.ts | 32 ++++++++ 6 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/services/codefixes/addOptionalPropertyUndefined.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 016ce44683c8a..b88c2ba524999 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -633,6 +633,7 @@ namespace ts { isTupleType, isArrayLikeType, isTypeInvalidDueToUnionDiscriminant, + getExactOptionalUnassignableProperties, getAllPossiblePropertiesOfTypes, getSuggestedSymbolForNonexistentProperty, getSuggestionForNonexistentProperty, @@ -17873,6 +17874,7 @@ namespace ts { function reportErrorResults(source: Type, target: Type, result: Ternary, isComparingJsxAttributes: boolean) { if (!result && reportErrors) { + let message = headMessage const sourceHasBase = !!getSingleBaseForNonAugmentingSubtype(originalSource); const targetHasBase = !!getSingleBaseForNonAugmentingSubtype(originalTarget); source = (originalSource.aliasSymbol || sourceHasBase) ? originalSource : source; @@ -17904,15 +17906,18 @@ namespace ts { return result; } } + 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 { errorInfo = elaborateNeverIntersection(errorInfo, originalTarget); } - if (!headMessage && maybeSuppress) { + if (!message && maybeSuppress) { lastSkippedInfo = [source, target]; // Used by, eg, missing property checking to replace the top-level message with a more informative one return result; } - reportRelationError(headMessage, source, target); + reportRelationError(message, source, target); } } } @@ -19574,6 +19579,16 @@ namespace ts { return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral); } + // TODO: Only issue with source has undefined, target does not, and they are otherwise assignable/the same (or who cares) + function getExactOptionalUnassignableProperties(source: Type, target: Type) { + return checker.getPropertiesOfType(target).filter(targetProp => { + const sourceProp = getPropertyOfType(source, targetProp.escapedName) + return sourceProp && targetProp.valueDeclaration + && maybeTypeOfKind(getTypeOfSymbol(sourceProp), TypeFlags.Undefined) + && hasQuestionToken(targetProp.valueDeclaration) + && containsMissingType(getTypeOfSymbol(targetProp)) }) + } + function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { return findMatchingDiscriminantType(source, target, isRelatedTo, /*skipPartial*/ true) || findMatchingTypeReferenceOrTypeAliasReference(source, target) || diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 134588f89fe57..cd84bae0bd99a 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1698,6 +1698,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 @@ -7058,6 +7062,14 @@ "category": "Message", "code": 95166 }, + "Add 'undefined' to all optional properties": { + "category": "Message", + "code": 95167 + }, + "Add 'undefined' to optional property type": { + "category": "Message", + "code": 95168 + }, "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 8db830a649428..d9a0acdbbb278 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4301,6 +4301,7 @@ namespace ts { * e.g. it specifies `kind: "a"` and obj has `kind: "b"`. */ /* @internal */ isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean; + /* @internal */ getExactOptionalUnassignableProperties(source: Type, target: 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/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts new file mode 100644 index 0000000000000..7301ad5dbf74b --- /dev/null +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -0,0 +1,77 @@ +/* @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_types_of_the_target_s_properties.code + ]; + + registerCodeFix({ + errorCodes, + getCodeActions(context) { + const typeChecker = context.program.getTypeChecker(); + const info = getInfo(context.sourceFile, context.span.start, typeChecker); + if (!info.length) { + return undefined; + } + // if method, it has to be rewritten to property + // skip any and unions with any + // add to existing unions + // parenthesise conditional types and arrows (the printer should take care of that, but it needs a test) + // test with destructuring, I've no idea what to do there + const changes = textChanges.ChangeTracker.with(context, t => addUndefinedToOptionalProperty(t, info)); + return [createCodeFixAction(addOptionalPropertyUndefined, changes, Diagnostics.Add_undefined_to_optional_property_type, addOptionalPropertyUndefined, Diagnostics.Add_undefined_to_all_optional_properties)]; + }, + fixIds: [addOptionalPropertyUndefined], + getAllCodeActions: context => { + const { program } = context; + const checker = program.getTypeChecker(); + const seen = new Map(); + return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { + eachDiagnostic(context, errorCodes, diag => { + const info = getInfo(diag.file, diag.start, checker); + if (!info.length) { + return; + } + for (const add of info) { + if (addToSeen(seen, add.id + "")) { + addUndefinedToOptionalProperty(changes, info); + } + } + }); + })); + }, + }); + + function getInfo(sourceFile: SourceFile, tokenPos: number, checker: TypeChecker): Symbol[] { + // The target of the incorrect assignment + // eg + // this.definite = 1; -OR- definite = source + // ^^^^ ^^^^^^^^ + const targetToken = getTokenAtPosition(sourceFile, tokenPos); + const isOK = (isIdentifier(targetToken) || isPrivateIdentifier(targetToken)) + && isBinaryExpression(targetToken.parent) + && targetToken.parent.operatorToken.kind === SyntaxKind.EqualsToken; + if (!isOK) { + // TODO: Walk up through lhs instead + return []; + } + const sourceNode = targetToken.parent.right; + // TODO: Also can apply to function calls, and then you have to get the signature, then its parameters, then the type of a particular parameter + // TODO: Also skip 'any' and node_modules and if target is not in node_modules or is built-in + return checker.getExactOptionalUnassignableProperties(checker.getTypeAtLocation(sourceNode), checker.getTypeAtLocation(targetToken)) + } + + 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/tests/cases/fourslash/fixExactOptionalUnassignableProperties1.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties1.ts new file mode 100644 index 0000000000000..b419883e44de6 --- /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`, +}); + From 671096b0ff0b935610c0a5112001be8881e81230 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 14 Jul 2021 09:57:41 -0700 Subject: [PATCH 02/16] Lots of cases work Destructuring does not. But - skipping node_modules and lib.* does. - call expressions does - property access, including with private identifiers, does --- src/compiler/checker.ts | 17 +-- src/compiler/diagnosticMessages.json | 4 + src/harness/fourslashImpl.ts | 3 +- .../codefixes/addOptionalPropertyUndefined.ts | 53 +++++--- ...fixExactOptionalUnassignableProperties1.ts | 18 +-- ...fixExactOptionalUnassignableProperties2.ts | 114 ++++++++++++++++++ ...fixExactOptionalUnassignableProperties3.ts | 18 +++ 7 files changed, 196 insertions(+), 31 deletions(-) create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties3.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c6602e01cd6aa..d452a662164e9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17657,10 +17657,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); } @@ -17875,7 +17883,6 @@ namespace ts { function reportErrorResults(source: Type, target: Type, result: Ternary, isComparingJsxAttributes: boolean) { if (!result && reportErrors) { - let message = headMessage const sourceHasBase = !!getSingleBaseForNonAugmentingSubtype(originalSource); const targetHasBase = !!getSingleBaseForNonAugmentingSubtype(originalTarget); source = (originalSource.aliasSymbol || sourceHasBase) ? originalSource : source; @@ -17907,18 +17914,15 @@ namespace ts { return result; } } - 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 { errorInfo = elaborateNeverIntersection(errorInfo, originalTarget); } - if (!message && maybeSuppress) { + if (!headMessage && maybeSuppress) { lastSkippedInfo = [source, target]; // Used by, eg, missing property checking to replace the top-level message with a more informative one return result; } - reportRelationError(message, source, target); + reportRelationError(headMessage, source, target); } } } @@ -19580,7 +19584,6 @@ namespace ts { return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral); } - // TODO: Only issue with source has undefined, target does not, and they are otherwise assignable/the same (or who cares) function getExactOptionalUnassignableProperties(source: Type, target: Type) { return checker.getPropertiesOfType(target).filter(targetProp => { const sourceProp = getPropertyOfType(source, targetProp.escapedName) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index a9a803737c1d4..81fc5761f02b8 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1714,6 +1714,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 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/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts index 7301ad5dbf74b..0989723751e70 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -3,7 +3,8 @@ 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_types_of_the_target_s_properties.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({ @@ -43,23 +44,47 @@ namespace ts.codefix { }, }); - function getInfo(sourceFile: SourceFile, tokenPos: number, checker: TypeChecker): Symbol[] { + function getTarget(file: SourceFile, pos: number) { + const start = getTokenAtPosition(file, pos) + if (isPropertyAccessExpression(start.parent) && start.parent.expression === start) { + return start.parent + } + else if (isIdentifier(start) || isPrivateIdentifier(start)) { + return start + } + return undefined + } + + function getSourceTarget(file: SourceFile, pos: number, checker: TypeChecker) { + const target = getTarget(file, pos) + if (!target) return undefined + if (isBinaryExpression(target.parent) && target.parent.operatorToken.kind === SyntaxKind.EqualsToken) { + return [target.parent.right, target] + } + else if (isCallExpression(target.parent)) { + const n = checker.getSymbolAtLocation(target.parent.expression) + if (!n?.valueDeclaration) return undefined + if (!isIdentifier(target)) return undefined; + const i = target.parent.arguments.indexOf(target) + const name = (n.valueDeclaration as any as SignatureDeclaration).parameters[i].name + if (isIdentifier(name)) return [target, name] + } + // It's possible to handle destructuring by recording the path up through the structure + // and following the reverse path down through the right-hand-side, but that's not worth the effort + return undefined + } + + function getInfo(file: SourceFile, pos: number, checker: TypeChecker): Symbol[] { // The target of the incorrect assignment // eg // this.definite = 1; -OR- definite = source // ^^^^ ^^^^^^^^ - const targetToken = getTokenAtPosition(sourceFile, tokenPos); - const isOK = (isIdentifier(targetToken) || isPrivateIdentifier(targetToken)) - && isBinaryExpression(targetToken.parent) - && targetToken.parent.operatorToken.kind === SyntaxKind.EqualsToken; - if (!isOK) { - // TODO: Walk up through lhs instead - return []; - } - const sourceNode = targetToken.parent.right; - // TODO: Also can apply to function calls, and then you have to get the signature, then its parameters, then the type of a particular parameter - // TODO: Also skip 'any' and node_modules and if target is not in node_modules or is built-in - return checker.getExactOptionalUnassignableProperties(checker.getTypeAtLocation(sourceNode), checker.getTypeAtLocation(targetToken)) + const sourceTarget = getSourceTarget(file, pos, checker) + if (!sourceTarget) return [] + const [sourceNode, targetNode] = sourceTarget + const target = checker.getTypeAtLocation(targetNode) + if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/(node_modules|^lib)/))) return []; + return checker.getExactOptionalUnassignableProperties(checker.getTypeAtLocation(sourceNode), target) } function addUndefinedToOptionalProperty(changes: textChanges.ChangeTracker, toAdd: Symbol[]) { diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties1.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties1.ts index b419883e44de6..993263eeb003e 100644 --- a/tests/cases/fourslash/fixExactOptionalUnassignableProperties1.ts +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties1.ts @@ -2,15 +2,15 @@ // @strictNullChecks: true // @exactOptionalPropertyTypes: true -////interface I { -//// a?: number -////} -////interface J { -//// a?: number | undefined -////} -////declare var i: I -////declare var j: J -////i/**/ = j +//// 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 } ]); diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts new file mode 100644 index 0000000000000..25a1e7bf78a76 --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts @@ -0,0 +1,114 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true + +//// interface I { +//// a?: number +//// } +//// interface IAny { +//// a?: any +//// } +//// interface IF { +//// a?: number +//// } +//// interface IC { +//// a?: number +//// } +//// interface IC2 { +//// a?: number +//// } +//// interface ICP { +//// a?: number +//// } +//// interface ID { +//// a?: number +//// } +//// interface J { +//// a?: number | undefined +//// } +//// declare var i: I +//// declare var iany: IAny +//// declare var if: IF +//// declare var j: J +//// i = j +//// iany = j +//// function fi(if_: IF) { return if_ } +//// fi(j) +//// class C { +//// ic: IC +//// ic2: IC2 +//// m() { this.ic = j } +//// } +//// var c = new C() +//// c.ic2 = j +//// class CP { +//// #icp: ICP +//// m() { this.#icp = j } +//// } +//// declare var id: ID +//// ({ id } = { id: j }) +//// declare var pd: PropertyDescriptor +//// interface PartialWrongPropertyDescriptor { +//// configurable?: boolean | undefined +//// } +//// declare var pwpd: PartialWrongPropertyDescriptor +//// pd = pwpd + +verify.codeFixAll({ + fixId: "addOptionalPropertyUndefined", + fixAllDescription: ts.Diagnostics.Add_undefined_to_all_optional_properties.message, + newFileContent: +`interface I { + a?: number | undefined +} +interface IAny { + a?: any +} +interface IF { + a?: number | undefined +} +interface IC { + a?: number | undefined +} +interface IC2 { + a?: number | undefined +} +interface ICP { + a?: number | undefined +} +interface ID { + a?: number +} +interface J { + a?: number | undefined +} +declare var i: I +declare var iany: IAny +declare var if: IF +declare var j: J +i = j +iany = j +function fi(if_: IF) { return if_ } +fi(j) +class C { + ic: IC + ic2: IC2 + m() { this.ic = j } +} +var c = new C() +c.ic2 = j +class CP { + #icp: ICP + m() { this.#icp = j } +} +declare var id: ID +({ id } = { id: j }) +declare var pd: PropertyDescriptor +interface PartialWrongPropertyDescriptor { + configurable?: boolean | undefined +} +declare var pwpd: PartialWrongPropertyDescriptor +pd = pwpd`, +}); + 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([]); From a7288527bfee153475e0418264979aa512f80eff Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 14 Jul 2021 14:00:25 -0700 Subject: [PATCH 03/16] Support variable declarations, property assignments, destructuring As long as it's not nested --- .../codefixes/addOptionalPropertyUndefined.ts | 53 +++++++++++-------- ...fixExactOptionalUnassignableProperties2.ts | 50 ++++++++++++++++- 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/src/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts index 0989723751e70..7c5142b01dca6 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -44,46 +44,55 @@ namespace ts.codefix { }, }); - function getTarget(file: SourceFile, pos: number) { + // The target of the incorrect assignment + // eg + // this.definite = 1; -OR- definite = source + // ^^^^ ^^^^^^^^ + // TODO: More examples here + function getTarget(file: SourceFile, pos: number): MemberName | PropertyAccessExpression | undefined { const start = getTokenAtPosition(file, pos) - if (isPropertyAccessExpression(start.parent) && start.parent.expression === start) { - return start.parent - } - else if (isIdentifier(start) || isPrivateIdentifier(start)) { - return start - } - return undefined + return isPropertyAccessExpression(start.parent) && start.parent.expression === start ? start.parent + : isIdentifier(start) || isPrivateIdentifier(start) ? start + : undefined; } - function getSourceTarget(file: SourceFile, pos: number, checker: TypeChecker) { - const target = getTarget(file, pos) + function getSourceTarget(target: Node | undefined, checker: TypeChecker): { source: Node, target: Node } | undefined { if (!target) return undefined if (isBinaryExpression(target.parent) && target.parent.operatorToken.kind === SyntaxKind.EqualsToken) { - return [target.parent.right, target] + return { source: target.parent.right, target: target.parent.left } + } + else if (isVariableDeclaration(target.parent) && target.parent.initializer) { + return { source: target.parent.initializer, target: target.parent.name } } else if (isCallExpression(target.parent)) { const n = checker.getSymbolAtLocation(target.parent.expression) if (!n?.valueDeclaration) return undefined - if (!isIdentifier(target)) return undefined; + if (!isExpression(target)) return undefined; const i = target.parent.arguments.indexOf(target) const name = (n.valueDeclaration as any as SignatureDeclaration).parameters[i].name - if (isIdentifier(name)) return [target, name] + if (isIdentifier(name)) return { source: target, target: name } + } + else if (isPropertyAssignment(target.parent) && isIdentifier(target.parent.name) || + isShorthandPropertyAssignment(target.parent)) { + const parentTarget = getSourceTarget(target.parent.parent, checker) + if (!parentTarget) return undefined + const prop = checker.getPropertyOfType(checker.getTypeAtLocation(parentTarget.target), (target.parent.name as Identifier).text) + const declaration = prop?.declarations?.[0] + if (!declaration) return undefined + return { + source: isPropertyAssignment(target.parent) ? target.parent.initializer : target.parent.name, + target: declaration + } } - // It's possible to handle destructuring by recording the path up through the structure - // and following the reverse path down through the right-hand-side, but that's not worth the effort return undefined } function getInfo(file: SourceFile, pos: number, checker: TypeChecker): Symbol[] { - // The target of the incorrect assignment - // eg - // this.definite = 1; -OR- definite = source - // ^^^^ ^^^^^^^^ - const sourceTarget = getSourceTarget(file, pos, checker) + const sourceTarget = getSourceTarget(getTarget(file, pos), checker) if (!sourceTarget) return [] - const [sourceNode, targetNode] = sourceTarget + const { source: sourceNode, target: targetNode } = sourceTarget const target = checker.getTypeAtLocation(targetNode) - if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/(node_modules|^lib)/))) return []; + if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/(node_modules|^lib\.)/))) return []; return checker.getExactOptionalUnassignableProperties(checker.getTypeAtLocation(sourceNode), target) } diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts index 25a1e7bf78a76..62efb1aa22007 100644 --- a/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts @@ -24,6 +24,22 @@ //// interface ID { //// a?: number //// } +//// interface More { +//// a?: number +//// b?: number +//// } +//// interface Assignment { +//// a?: number +//// } +//// interface PropertyAssignment { +//// a?: number +//// } +//// interface ShorthandPropertyAssignment { +//// a?: number +//// } +//// interface FPA { +//// a?: number +//// } //// interface J { //// a?: number | undefined //// } @@ -54,6 +70,13 @@ //// } //// declare var pwpd: PartialWrongPropertyDescriptor //// pd = pwpd +//// declare var more: More +//// more = j +//// var assignment: Assignment = j +//// var opa: { pa: PropertyAssignment } = { pa: j } +//// var ospa: { j: ShorthandPropertyAssignment } = { j } +//// declare function fpa(fpa: { fpa: FPA }): void +//// fpa({ fpa: j }) verify.codeFixAll({ fixId: "addOptionalPropertyUndefined", @@ -78,7 +101,23 @@ interface ICP { a?: number | undefined } interface ID { - a?: number + a?: number | undefined +} +interface More { + a?: number | undefined + b?: number +} +interface Assignment { + a?: number | undefined +} +interface PropertyAssignment { + a?: number | undefined +} +interface ShorthandPropertyAssignment { + a?: number | undefined +} +interface FPA { + a?: number | undefined } interface J { a?: number | undefined @@ -109,6 +148,13 @@ interface PartialWrongPropertyDescriptor { configurable?: boolean | undefined } declare var pwpd: PartialWrongPropertyDescriptor -pd = pwpd`, +pd = pwpd +declare var more: More +more = j +var assignment: Assignment = j +var opa: { pa: PropertyAssignment } = { pa: j } +var ospa: { j: ShorthandPropertyAssignment } = { j } +declare function fpa(fpa: { fpa: FPA }): void +fpa({ fpa: j })`, }); From 715f235b7b4681c34cb5139f333523eab92f2c25 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 14 Jul 2021 15:21:18 -0700 Subject: [PATCH 04/16] More cleanup --- src/compiler/checker.ts | 9 +- .../codefixes/addOptionalPropertyUndefined.ts | 120 ++++++++++-------- 2 files changed, 70 insertions(+), 59 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d452a662164e9..4f681bcb5d100 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17658,7 +17658,7 @@ namespace ts { 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 + 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; @@ -17667,7 +17667,7 @@ namespace ts { 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 + 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); @@ -19586,11 +19586,12 @@ namespace ts { function getExactOptionalUnassignableProperties(source: Type, target: Type) { return checker.getPropertiesOfType(target).filter(targetProp => { - const sourceProp = getPropertyOfType(source, targetProp.escapedName) + const sourceProp = getPropertyOfType(source, targetProp.escapedName); return sourceProp && targetProp.valueDeclaration && maybeTypeOfKind(getTypeOfSymbol(sourceProp), TypeFlags.Undefined) && hasQuestionToken(targetProp.valueDeclaration) - && containsMissingType(getTypeOfSymbol(targetProp)) }) + && containsMissingType(getTypeOfSymbol(targetProp)); +}); } function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { diff --git a/src/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts index 7c5142b01dca6..29f1d68f008c1 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -11,16 +11,11 @@ namespace ts.codefix { errorCodes, getCodeActions(context) { const typeChecker = context.program.getTypeChecker(); - const info = getInfo(context.sourceFile, context.span.start, typeChecker); - if (!info.length) { + const toAdd = getPropertiesToAdd(context.sourceFile, context.span.start, typeChecker); + if (!toAdd.length) { return undefined; } - // if method, it has to be rewritten to property - // skip any and unions with any - // add to existing unions - // parenthesise conditional types and arrows (the printer should take care of that, but it needs a test) - // test with destructuring, I've no idea what to do there - const changes = textChanges.ChangeTracker.with(context, t => addUndefinedToOptionalProperty(t, info)); + const changes = textChanges.ChangeTracker.with(context, t => addUndefinedToOptionalProperty(t, toAdd)); return [createCodeFixAction(addOptionalPropertyUndefined, changes, Diagnostics.Add_undefined_to_optional_property_type, addOptionalPropertyUndefined, Diagnostics.Add_undefined_to_all_optional_properties)]; }, fixIds: [addOptionalPropertyUndefined], @@ -30,81 +25,96 @@ namespace ts.codefix { const seen = new Map(); return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { eachDiagnostic(context, errorCodes, diag => { - const info = getInfo(diag.file, diag.start, checker); - if (!info.length) { + const toAdd = getPropertiesToAdd(diag.file, diag.start, checker); + if (!toAdd.length) { return; } - for (const add of info) { - if (addToSeen(seen, add.id + "")) { - addUndefinedToOptionalProperty(changes, info); + let untouched = true; + for (const add of toAdd) { + if (!addToSeen(seen, add.id + "")) { + untouched = false; } } + if (untouched) {addUndefinedToOptionalProperty(changes, toAdd);} }); })); }, }); - // The target of the incorrect assignment - // eg - // this.definite = 1; -OR- definite = source - // ^^^^ ^^^^^^^^ - // TODO: More examples here - function getTarget(file: SourceFile, pos: number): MemberName | PropertyAccessExpression | undefined { - const start = getTokenAtPosition(file, pos) + function getPropertiesToAdd(file: SourceFile, pos: number, checker: TypeChecker): Symbol[] { + const sourceTarget = getSourceTarget(getErrorNode(file, pos), checker); + if (!sourceTarget) { + return []; + } + const { source: sourceNode, target: targetNode } = sourceTarget; + const target = checker.getTypeAtLocation(targetNode); + if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/(node_modules|^lib\.)/))) { + return []; + } + return checker.getExactOptionalUnassignableProperties(checker.getTypeAtLocation(sourceNode), target); + } + + /** + * Get the part of the incorrect assignment that is useful for type-checking + * eg + * this.definite = 1; ---> `this.definite` + * ^^^^ + * definite = source ----> `definite` + * ^^^^^^^^ + */ + function getErrorNode(file: SourceFile, pos: number): MemberName | PropertyAccessExpression | undefined { + const start = getTokenAtPosition(file, pos); return isPropertyAccessExpression(start.parent) && start.parent.expression === start ? start.parent : isIdentifier(start) || isPrivateIdentifier(start) ? start : undefined; } - function getSourceTarget(target: Node | undefined, checker: TypeChecker): { source: Node, target: Node } | undefined { - if (!target) return undefined - if (isBinaryExpression(target.parent) && target.parent.operatorToken.kind === SyntaxKind.EqualsToken) { - return { source: target.parent.right, target: target.parent.left } + /** + * 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 (isVariableDeclaration(target.parent) && target.parent.initializer) { - return { source: target.parent.initializer, target: target.parent.name } + else if (isBinaryExpression(errorNode.parent) && errorNode.parent.operatorToken.kind === SyntaxKind.EqualsToken) { + return { source: errorNode.parent.right, target: errorNode.parent.left }; } - else if (isCallExpression(target.parent)) { - const n = checker.getSymbolAtLocation(target.parent.expression) - if (!n?.valueDeclaration) return undefined - if (!isExpression(target)) return undefined; - const i = target.parent.arguments.indexOf(target) - const name = (n.valueDeclaration as any as SignatureDeclaration).parameters[i].name - if (isIdentifier(name)) return { source: target, target: name } + else if (isVariableDeclaration(errorNode.parent) && errorNode.parent.initializer) { + return { source: errorNode.parent.initializer, target: errorNode.parent.name }; } - else if (isPropertyAssignment(target.parent) && isIdentifier(target.parent.name) || - isShorthandPropertyAssignment(target.parent)) { - const parentTarget = getSourceTarget(target.parent.parent, checker) - if (!parentTarget) return undefined - const prop = checker.getPropertyOfType(checker.getTypeAtLocation(parentTarget.target), (target.parent.name as Identifier).text) - const declaration = prop?.declarations?.[0] - if (!declaration) return undefined + else if (isCallExpression(errorNode.parent)) { + const n = checker.getSymbolAtLocation(errorNode.parent.expression); + if (!n?.valueDeclaration) return undefined; + if (!isExpression(errorNode)) return undefined; + const i = errorNode.parent.arguments.indexOf(errorNode); + 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(target.parent) ? target.parent.initializer : target.parent.name, + source: isPropertyAssignment(errorNode.parent) ? errorNode.parent.initializer : errorNode.parent.name, target: declaration - } + }; } - return undefined - } - - function getInfo(file: SourceFile, pos: number, checker: TypeChecker): Symbol[] { - const sourceTarget = getSourceTarget(getTarget(file, pos), checker) - if (!sourceTarget) return [] - const { source: sourceNode, target: targetNode } = sourceTarget - const target = checker.getTypeAtLocation(targetNode) - if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/(node_modules|^lib\.)/))) return []; - return checker.getExactOptionalUnassignableProperties(checker.getTypeAtLocation(sourceNode), target) + return undefined; } function addUndefinedToOptionalProperty(changes: textChanges.ChangeTracker, toAdd: Symbol[]) { for (const add of toAdd) { - const d = add.valueDeclaration + 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) + ]); + changes.replaceNode(d.getSourceFile(), d.type, t); } } } From cfea6e1543685a990cec66ab3d5101e193dd43ed Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 15 Jul 2021 09:01:49 -0700 Subject: [PATCH 05/16] skip all d.ts, not just node_modules/lib --- src/services/codefixes/addOptionalPropertyUndefined.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts index 29f1d68f008c1..9b903c058e6dc 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -48,7 +48,7 @@ namespace ts.codefix { } const { source: sourceNode, target: targetNode } = sourceTarget; const target = checker.getTypeAtLocation(targetNode); - if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/(node_modules|^lib\.)/))) { + if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/\.d\.ts$/))) { return []; } return checker.getExactOptionalUnassignableProperties(checker.getTypeAtLocation(sourceNode), target); From 41f0525338881e93bc771bbf0a4885358915fb5e Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 3 Aug 2021 08:41:57 -0700 Subject: [PATCH 06/16] Offer a codefix for a lot more cases --- src/compiler/checker.ts | 56 +++++---- src/compiler/diagnosticMessages.json | 4 + src/compiler/types.ts | 1 + .../codefixes/addOptionalPropertyUndefined.ts | 20 +++- .../strictOptionalProperties1.errors.txt | 107 +++++++++++------- ...fixExactOptionalUnassignableProperties4.ts | 30 +++++ ...fixExactOptionalUnassignableProperties5.ts | 27 +++++ 7 files changed, 178 insertions(+), 67 deletions(-) create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties4.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties5.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b0798861ca8ba..b1fead210642b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -634,6 +634,7 @@ namespace ts { isArrayLikeType, isTypeInvalidDueToUnionDiscriminant, getExactOptionalUnassignableProperties, + isExactOptionalPropertyMismatch, getAllPossiblePropertiesOfTypes, getSuggestedSymbolForNonexistentProperty, getSuggestionForNonexistentProperty, @@ -16754,24 +16755,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]; @@ -16799,7 +16805,6 @@ namespace ts { } } } - reportedError = true; } } } @@ -19593,13 +19598,14 @@ namespace ts { } function getExactOptionalUnassignableProperties(source: Type, target: Type) { - return checker.getPropertiesOfType(target).filter(targetProp => { - const sourceProp = getPropertyOfType(source, targetProp.escapedName); - return sourceProp && targetProp.valueDeclaration - && maybeTypeOfKind(getTypeOfSymbol(sourceProp), TypeFlags.Undefined) - && hasQuestionToken(targetProp.valueDeclaration) - && containsMissingType(getTypeOfSymbol(targetProp)); -}); + return getPropertiesOfType(target) + .filter(targetProp => isExactOptionalPropertyMismatch(getTypeOfPropertyOfType(source, targetProp.escapedName), getTypeOfSymbol(targetProp))); + } + + function isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined) { + return !!source && !!target + && !(getObjectFlags(source) & ObjectFlags.Tuple) && !(getObjectFlags(target) & ObjectFlags.Tuple) + && maybeTypeOfKind(source, TypeFlags.Undefined) && !!containsMissingType(target); } function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { @@ -32463,8 +32469,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 7fecbbbe6b875..e0067e37b6b08 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1882,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 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a40eae0721d84..c56794c891783 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4306,6 +4306,7 @@ namespace ts { */ /* @internal */ isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean; /* @internal */ getExactOptionalUnassignableProperties(source: Type, target: Type): Symbol[]; + /* @internal */ isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined): boolean; /** * 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/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts index 9b903c058e6dc..fcef149fc28df 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -3,8 +3,9 @@ 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 + 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({ @@ -48,12 +49,27 @@ namespace ts.codefix { } const { source: sourceNode, target: targetNode } = sourceTarget; const target = checker.getTypeAtLocation(targetNode); + const source = checker.getTypeAtLocation(sourceNode); if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/\.d\.ts$/))) { return []; } - return checker.getExactOptionalUnassignableProperties(checker.getTypeAtLocation(sourceNode), target); + const targetPropertyType = getTargetPropertyType(checker, targetNode); + if (targetPropertyType && checker.isExactOptionalPropertyMismatch(source, targetPropertyType)) { + const s = checker.getSymbolAtLocation((targetNode as PropertyAccessExpression).name); + return s ? [s] : []; + } + return checker.getExactOptionalUnassignableProperties(source, target); } + function getTargetPropertyType(checker: TypeChecker, targetNode: Node) { + if (isPropertySignature(targetNode)) { + return checker.getTypeAtLocation(targetNode.name); + } + else if (isPropertyAccessExpression(targetNode)) { + return checker.getTypeOfPropertyOfType(checker.getTypeAtLocation(targetNode.expression), targetNode.name.text); + } + return undefined; + } /** * Get the part of the incorrect assignment that is useful for type-checking * eg diff --git a/tests/baselines/reference/strictOptionalProperties1.errors.txt b/tests/baselines/reference/strictOptionalProperties1.errors.txt index 7149ec35687b9..f98e463310458 100644 --- a/tests/baselines/reference/strictOptionalProperties1.errors.txt +++ b/tests/baselines/reference/strictOptionalProperties1.errors.txt @@ -1,27 +1,38 @@ -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?]'. +tests/cases/compiler/strictOptionalProperties1.ts(64,5): error TS2375: Type '[number, string?, string?]' is not assignable to type '[number, string?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. Target allows only 2 element(s) but source may have more. -tests/cases/compiler/strictOptionalProperties1.ts(73,5): error TS2322: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]'. +tests/cases/compiler/strictOptionalProperties1.ts(73,5): error TS2375: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. 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?]'. +tests/cases/compiler/strictOptionalProperties1.ts(74,5): error TS2375: Type '[never?, never?, true?]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. 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 TS2375: Type '[number, undefined, true]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. + 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 TS2375: Type '[number, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. + 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 TS2375: Type '[number, string, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. + 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 TS2375: Type '[number, undefined, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. + 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 (17 errors) ==== +==== tests/cases/compiler/strictOptionalProperties1.ts (16 errors) ==== function f1(obj: { a?: string, b?: string | undefined }) { let a = obj.a; // string | undefined let b = obj.b; // string | undefined @@ -29,7 +40,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property 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; } @@ -37,8 +48,8 @@ tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property 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; @@ -48,7 +59,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property 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; @@ -58,7 +69,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property 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; @@ -85,7 +96,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property 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; } @@ -100,8 +111,8 @@ tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property function f4a(t1: [number, string?], t2: [number, string?, string?]) { t1 = t2; // Wasn't an error, but should be ~~ -!!! error TS2322: Type '[number, string?, string?]' is not assignable to type '[number, string?]'. -!!! error TS2322: Target allows only 2 element(s) but source may have more. +!!! error TS2375: Type '[number, string?, string?]' is not assignable to type '[number, string?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Target allows only 2 element(s) but source may have more. } function f5(t: [number, string?, boolean?]) { @@ -112,15 +123,17 @@ tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property t = [42, , ,]; t = [42, , , ,]; // Error ~ -!!! error TS2322: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]'. -!!! error TS2322: Target allows only 3 element(s) but source may have more. +!!! error TS2375: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Target allows only 3 element(s) but source may have more. t = [, , true]; // Error ~ -!!! 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. +!!! error TS2375: Type '[never?, never?, true?]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: 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 TS2375: Type '[number, undefined, true]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Type at position 1 in source is not compatible with type at position 1 in target. +!!! error TS2375: Type 'undefined' is not assignable to type 'string'. } function f6() { @@ -145,32 +158,38 @@ tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property 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 TS2375: Type '[number, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Type at position 1 in source is not compatible with type at position 1 in target. +!!! error TS2375: 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 TS2375: Type '[number, string, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Type at position 2 in source is not compatible with type at position 2 in target. +!!! error TS2375: 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 TS2375: Type '[number, undefined, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Type at position 1 in source is not compatible with type at position 1 in target. +!!! error TS2375: 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 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;`, +}); + From cdd40d3e2d43a4ed65eb2ed8115d98aee0c1e124 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 3 Aug 2021 09:08:28 -0700 Subject: [PATCH 07/16] remove incorrect tuple check --- src/compiler/checker.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 74140c97b3843..719f694278dd9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19603,9 +19603,7 @@ namespace ts { } function isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined) { - return !!source && !!target - && !(getObjectFlags(source) & ObjectFlags.Tuple) && !(getObjectFlags(target) & ObjectFlags.Tuple) - && maybeTypeOfKind(source, TypeFlags.Undefined) && !!containsMissingType(target); + return !!source && !!target && maybeTypeOfKind(source, TypeFlags.Undefined) && !!containsMissingType(target); } function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { From f018b8e634705b2f8eff819f51c720bfef1dda5f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 3 Aug 2021 10:06:53 -0700 Subject: [PATCH 08/16] Use getSymbolId instead of converting to string Co-authored-by: Andrew Branch --- src/services/codefixes/addOptionalPropertyUndefined.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts index fcef149fc28df..56174cfe3c491 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -32,7 +32,7 @@ namespace ts.codefix { } let untouched = true; for (const add of toAdd) { - if (!addToSeen(seen, add.id + "")) { + if (!addToSeen(seen, getSymbolId(add) + "")) { untouched = false; } } From 960e657808be6a2ace9b341e3daa3add4fda85c9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 3 Aug 2021 10:18:15 -0700 Subject: [PATCH 09/16] add test + switch to tracking number symbol ids --- .../codefixes/addOptionalPropertyUndefined.ts | 4 +- .../strictOptionalProperties1.errors.txt | 27 +++++++++++- .../reference/strictOptionalProperties1.js | 28 +++++++++++++ .../strictOptionalProperties1.symbols | 42 +++++++++++++++++++ .../reference/strictOptionalProperties1.types | 38 +++++++++++++++++ .../compiler/strictOptionalProperties1.ts | 15 +++++++ 6 files changed, 151 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts index 56174cfe3c491..1af85c9fc375d 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -23,7 +23,7 @@ namespace ts.codefix { getAllCodeActions: context => { const { program } = context; const checker = program.getTypeChecker(); - const seen = new Map(); + const seen = new Map(); return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { eachDiagnostic(context, errorCodes, diag => { const toAdd = getPropertiesToAdd(diag.file, diag.start, checker); @@ -32,7 +32,7 @@ namespace ts.codefix { } let untouched = true; for (const add of toAdd) { - if (!addToSeen(seen, getSymbolId(add) + "")) { + if (!addToSeen(seen, getSymbolId(add))) { untouched = false; } } diff --git a/tests/baselines/reference/strictOptionalProperties1.errors.txt b/tests/baselines/reference/strictOptionalProperties1.errors.txt index f98e463310458..b7b452b5a89be 100644 --- a/tests/baselines/reference/strictOptionalProperties1.errors.txt +++ b/tests/baselines/reference/strictOptionalProperties1.errors.txt @@ -30,9 +30,13 @@ tests/cases/compiler/strictOptionalProperties1.ts(111,7): error TS2375: Type '{ 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(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 (16 errors) ==== +==== tests/cases/compiler/strictOptionalProperties1.ts (18 errors) ==== function f1(obj: { a?: string, b?: string | undefined }) { let a = obj.a; // string | undefined let b = obj.b; // string | undefined @@ -261,4 +265,25 @@ tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property } 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 const 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'. + \ No newline at end of file diff --git a/tests/baselines/reference/strictOptionalProperties1.js b/tests/baselines/reference/strictOptionalProperties1.js index 9f00711561343..f17e6e7b749be 100644 --- a/tests/baselines/reference/strictOptionalProperties1.js +++ b/tests/baselines/reference/strictOptionalProperties1.js @@ -179,6 +179,21 @@ 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 const u2: U2 +u1.email = e // error, but only because boolean isn't in email's type +u2.email = e // error, and suggest adding undefined + //// [strictOptionalProperties1.js] @@ -309,6 +324,8 @@ 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 //// [strictOptionalProperties1.d.ts] @@ -382,3 +399,14 @@ 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 const u2: U2; diff --git a/tests/baselines/reference/strictOptionalProperties1.symbols b/tests/baselines/reference/strictOptionalProperties1.symbols index bfae149bdbef0..17cfd9a572c8b 100644 --- a/tests/baselines/reference/strictOptionalProperties1.symbols +++ b/tests/baselines/reference/strictOptionalProperties1.symbols @@ -571,3 +571,45 @@ 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 const u2: U2 +>u2 : Symbol(u2, Decl(strictOptionalProperties1.ts, 191, 13)) +>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, 13)) +>email : Symbol(U2.email, Decl(strictOptionalProperties1.ts, 186, 16)) +>e : Symbol(e, Decl(strictOptionalProperties1.ts, 189, 13)) + + diff --git a/tests/baselines/reference/strictOptionalProperties1.types b/tests/baselines/reference/strictOptionalProperties1.types index c59cbc5d269c2..aaef06929c2a6 100644 --- a/tests/baselines/reference/strictOptionalProperties1.types +++ b/tests/baselines/reference/strictOptionalProperties1.types @@ -683,3 +683,41 @@ 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 const 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 + + diff --git a/tests/cases/compiler/strictOptionalProperties1.ts b/tests/cases/compiler/strictOptionalProperties1.ts index 1a71bb3dd75ae..cae1e7686146b 100644 --- a/tests/cases/compiler/strictOptionalProperties1.ts +++ b/tests/cases/compiler/strictOptionalProperties1.ts @@ -182,3 +182,18 @@ 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 const u2: U2 +u1.email = e // error, but only because boolean isn't in email's type +u2.email = e // error, and suggest adding undefined + From cdbe9697d16105343098dc209a1ccca6e58c2f27 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 3 Aug 2021 11:35:50 -0700 Subject: [PATCH 10/16] Address PR comments --- src/compiler/diagnosticMessages.json | 6 +- .../codefixes/addOptionalPropertyUndefined.ts | 10 ++- .../strictOptionalProperties1.errors.txt | 75 +++++++++++-------- .../reference/strictOptionalProperties1.js | 12 ++- .../strictOptionalProperties1.symbols | 17 ++++- .../reference/strictOptionalProperties1.types | 16 +++- .../compiler/strictOptionalProperties1.ts | 6 +- 7 files changed, 96 insertions(+), 46 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index e0067e37b6b08..911dd2efc7df2 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1738,7 +1738,7 @@ "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.": { + "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 }, @@ -1754,7 +1754,7 @@ "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.": { + "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 }, @@ -1882,7 +1882,7 @@ "category": "Error", "code": 2410 }, - "Type '{0}' is not assignable to type '{1}' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the type of the target.": { + "Type '{0}' is not assignable to type '{1}' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target.": { "category": "Error", "code": 2412 }, diff --git a/src/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts index 1af85c9fc375d..6826766522079 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -36,7 +36,9 @@ namespace ts.codefix { untouched = false; } } - if (untouched) {addUndefinedToOptionalProperty(changes, toAdd);} + if (untouched) { + addUndefinedToOptionalProperty(changes, toAdd); + } }); })); }, @@ -45,18 +47,18 @@ namespace ts.codefix { function getPropertiesToAdd(file: SourceFile, pos: number, checker: TypeChecker): Symbol[] { const sourceTarget = getSourceTarget(getErrorNode(file, pos), checker); if (!sourceTarget) { - return []; + return emptyArray; } const { source: sourceNode, target: targetNode } = sourceTarget; const target = checker.getTypeAtLocation(targetNode); const source = checker.getTypeAtLocation(sourceNode); if (target.symbol?.declarations?.some(d => getSourceFileOfNode(d).fileName.match(/\.d\.ts$/))) { - return []; + return emptyArray; } const targetPropertyType = getTargetPropertyType(checker, targetNode); if (targetPropertyType && checker.isExactOptionalPropertyMismatch(source, targetPropertyType)) { const s = checker.getSymbolAtLocation((targetNode as PropertyAccessExpression).name); - return s ? [s] : []; + return s ? [s] : emptyArray; } return checker.getExactOptionalUnassignableProperties(source, target); } diff --git a/tests/baselines/reference/strictOptionalProperties1.errors.txt b/tests/baselines/reference/strictOptionalProperties1.errors.txt index b7b452b5a89be..4a28c32e4cc22 100644 --- a/tests/baselines/reference/strictOptionalProperties1.errors.txt +++ b/tests/baselines/reference/strictOptionalProperties1.errors.txt @@ -1,42 +1,45 @@ -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. +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 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(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 TS2375: Type '[number, string?, string?]' is not assignable to type '[number, string?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +tests/cases/compiler/strictOptionalProperties1.ts(64,5): error TS2375: Type '[number, string?, string?]' is not assignable to type '[number, string?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. Target allows only 2 element(s) but source may have more. -tests/cases/compiler/strictOptionalProperties1.ts(73,5): error TS2375: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +tests/cases/compiler/strictOptionalProperties1.ts(73,5): error TS2375: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. Target allows only 3 element(s) but source may have more. -tests/cases/compiler/strictOptionalProperties1.ts(74,5): error TS2375: Type '[never?, never?, true?]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +tests/cases/compiler/strictOptionalProperties1.ts(74,5): error TS2375: Type '[never?, never?, true?]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. Source provides no match for required element at position 0 in target. -tests/cases/compiler/strictOptionalProperties1.ts(75,5): error TS2375: Type '[number, undefined, true]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +tests/cases/compiler/strictOptionalProperties1.ts(75,5): error TS2375: Type '[number, undefined, true]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. 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. +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 TS2375: Type '[number, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +tests/cases/compiler/strictOptionalProperties1.ts(105,7): error TS2375: Type '[number, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. 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 TS2375: Type '[number, string, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +tests/cases/compiler/strictOptionalProperties1.ts(106,7): error TS2375: Type '[number, string, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. 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 TS2375: Type '[number, undefined, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +tests/cases/compiler/strictOptionalProperties1.ts(107,7): error TS2375: Type '[number, undefined, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. 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. +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(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. +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 (18 errors) ==== +==== tests/cases/compiler/strictOptionalProperties1.ts (19 errors) ==== function f1(obj: { a?: string, b?: string | undefined }) { let a = obj.a; // string | undefined let b = obj.b; // string | undefined @@ -44,7 +47,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'st obj.b = 'hello'; obj.a = undefined; // Error ~~~~~ -!!! error TS2412: Type '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' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. obj.b = undefined; } @@ -52,7 +55,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'st obj = obj; obj.a = obj.a; // Error ~~~~~ -!!! 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 '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) { @@ -63,7 +66,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'st obj.a; obj.a = obj.a; // Error ~~~~~ -!!! error TS2412: Type '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' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. } if (obj.hasOwnProperty('a')) { obj.a; @@ -73,7 +76,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'st obj.a; obj.a = obj.a; // Error ~~~~~ -!!! error TS2412: Type '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' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. } if ('b' in obj) { obj.b; @@ -100,7 +103,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'st obj.b = 'hello'; obj.a = undefined; // Error ~~~~~ -!!! error TS2412: Type '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' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. obj.b = undefined; } @@ -115,7 +118,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'st function f4a(t1: [number, string?], t2: [number, string?, string?]) { t1 = t2; // Wasn't an error, but should be ~~ -!!! error TS2375: Type '[number, string?, string?]' is not assignable to type '[number, string?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Type '[number, string?, string?]' is not assignable to type '[number, string?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. !!! error TS2375: Target allows only 2 element(s) but source may have more. } @@ -127,15 +130,15 @@ tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'st t = [42, , ,]; t = [42, , , ,]; // Error ~ -!!! error TS2375: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. !!! error TS2375: Target allows only 3 element(s) but source may have more. t = [, , true]; // Error ~ -!!! error TS2375: Type '[never?, never?, true?]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Type '[never?, never?, true?]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. !!! error TS2375: Source provides no match for required element at position 0 in target. t = [42, undefined, true]; // Error ~ -!!! error TS2375: Type '[number, undefined, true]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Type '[number, undefined, true]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. !!! error TS2375: Type at position 1 in source is not compatible with type at position 1 in target. !!! error TS2375: Type 'undefined' is not assignable to type 'string'. } @@ -163,7 +166,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'st const defaultProps: Pick = { foo: 'foo' }; const inputProps: InputProps = { foo: undefined, bar: 'bar' }; ~~~~~~~~~~ -!!! 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: 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 }; @@ -173,17 +176,17 @@ tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'st const t1: [number, string?, boolean?] = [1]; const t2: [number, string?, boolean?] = [1, undefined]; ~~ -!!! error TS2375: Type '[number, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Type '[number, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. !!! error TS2375: Type at position 1 in source is not compatible with type at position 1 in target. !!! error TS2375: Type 'undefined' is not assignable to type 'string'. const t3: [number, string?, boolean?] = [1, "string", undefined]; ~~ -!!! error TS2375: Type '[number, string, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Type '[number, string, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. !!! error TS2375: Type at position 2 in source is not compatible with type at position 2 in target. !!! error TS2375: Type 'undefined' is not assignable to type 'boolean'. const t4: [number, string?, boolean?] = [1, undefined, undefined]; ~~ -!!! error TS2375: Type '[number, undefined, undefined]' is not assignable to type '[number, string?, boolean?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. +!!! error TS2375: Type '[number, undefined, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. !!! error TS2375: Type at position 1 in source is not compatible with type at position 1 in target. !!! error TS2375: Type 'undefined' is not assignable to type 'string'. @@ -191,7 +194,7 @@ tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'st const x: { foo?: number } = { foo: undefined }; ~ -!!! 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: 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 }; @@ -276,14 +279,22 @@ tests/cases/compiler/strictOptionalProperties1.ts(194,1): error TS2412: Type 'st } declare const e: string | boolean | undefined declare const u1: U1 - declare const u2: U2 + 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 '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 + } \ No newline at end of file diff --git a/tests/baselines/reference/strictOptionalProperties1.js b/tests/baselines/reference/strictOptionalProperties1.js index f17e6e7b749be..d8a6f37ed7185 100644 --- a/tests/baselines/reference/strictOptionalProperties1.js +++ b/tests/baselines/reference/strictOptionalProperties1.js @@ -190,9 +190,13 @@ interface U2 { } declare const e: string | boolean | undefined declare const u1: U1 -declare const u2: U2 +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 +} @@ -326,6 +330,10 @@ function aa(input) { } 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 +}; //// [strictOptionalProperties1.d.ts] @@ -409,4 +417,4 @@ interface U2 { } declare const e: string | boolean | undefined; declare const u1: U1; -declare const u2: U2; +declare let u2: U2; diff --git a/tests/baselines/reference/strictOptionalProperties1.symbols b/tests/baselines/reference/strictOptionalProperties1.symbols index 17cfd9a572c8b..102b6eec3cd35 100644 --- a/tests/baselines/reference/strictOptionalProperties1.symbols +++ b/tests/baselines/reference/strictOptionalProperties1.symbols @@ -596,8 +596,8 @@ declare const u1: U1 >u1 : Symbol(u1, Decl(strictOptionalProperties1.ts, 190, 13)) >U1 : Symbol(U1, Decl(strictOptionalProperties1.ts, 179, 41)) -declare const u2: U2 ->u2 : Symbol(u2, Decl(strictOptionalProperties1.ts, 191, 13)) +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 @@ -608,8 +608,19 @@ u1.email = e // error, but only because boolean isn't in email's type 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, 13)) +>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) +} + diff --git a/tests/baselines/reference/strictOptionalProperties1.types b/tests/baselines/reference/strictOptionalProperties1.types index aaef06929c2a6..e927b0c004ec0 100644 --- a/tests/baselines/reference/strictOptionalProperties1.types +++ b/tests/baselines/reference/strictOptionalProperties1.types @@ -703,7 +703,7 @@ declare const e: string | boolean | undefined declare const u1: U1 >u1 : U1 -declare const u2: U2 +declare let u2: U2 >u2 : U2 u1.email = e // error, but only because boolean isn't in email's type @@ -720,4 +720,18 @@ u2.email = e // error, and suggest adding undefined >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 +} + diff --git a/tests/cases/compiler/strictOptionalProperties1.ts b/tests/cases/compiler/strictOptionalProperties1.ts index cae1e7686146b..19e0d90f5427d 100644 --- a/tests/cases/compiler/strictOptionalProperties1.ts +++ b/tests/cases/compiler/strictOptionalProperties1.ts @@ -193,7 +193,11 @@ interface U2 { } declare const e: string | boolean | undefined declare const u1: U1 -declare const u2: U2 +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 +} From e9607f10f224743c76fe2243065fe066fe2bffe7 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 4 Aug 2021 09:00:01 -0700 Subject: [PATCH 11/16] Exclude tuples from suggestion --- src/compiler/checker.ts | 1 + .../strictOptionalProperties1.errors.txt | 50 +++++++++---------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index da7f8f606f350..85026f816410b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19580,6 +19580,7 @@ namespace ts { } 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))); } diff --git a/tests/baselines/reference/strictOptionalProperties1.errors.txt b/tests/baselines/reference/strictOptionalProperties1.errors.txt index 4a28c32e4cc22..43db0858fde17 100644 --- a/tests/baselines/reference/strictOptionalProperties1.errors.txt +++ b/tests/baselines/reference/strictOptionalProperties1.errors.txt @@ -5,25 +5,25 @@ tests/cases/compiler/strictOptionalProperties1.ts(20,9): error TS2412: Type 'und 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 TS2375: Type '[number, string?, string?]' is not assignable to type '[number, string?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. +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. -tests/cases/compiler/strictOptionalProperties1.ts(73,5): error TS2375: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. +tests/cases/compiler/strictOptionalProperties1.ts(73,5): error TS2322: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]'. Target allows only 3 element(s) but source may have more. -tests/cases/compiler/strictOptionalProperties1.ts(74,5): error TS2375: Type '[never?, never?, true?]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. +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,5): error TS2375: Type '[number, undefined, true]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. +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 TS2375: Type '[number, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. +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 TS2375: Type '[number, string, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. +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 TS2375: Type '[number, undefined, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. +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. @@ -118,8 +118,8 @@ tests/cases/compiler/strictOptionalProperties1.ts(195,1): error TS2375: Type '{ function f4a(t1: [number, string?], t2: [number, string?, string?]) { t1 = t2; // Wasn't an error, but should be ~~ -!!! error TS2375: Type '[number, string?, string?]' is not assignable to type '[number, string?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. -!!! error TS2375: Target allows only 2 element(s) but source may have more. +!!! error TS2322: Type '[number, string?, string?]' is not assignable to type '[number, string?]'. +!!! error TS2322: Target allows only 2 element(s) but source may have more. } function f5(t: [number, string?, boolean?]) { @@ -130,17 +130,17 @@ tests/cases/compiler/strictOptionalProperties1.ts(195,1): error TS2375: Type '{ t = [42, , ,]; t = [42, , , ,]; // Error ~ -!!! error TS2375: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. -!!! error TS2375: Target allows only 3 element(s) but source may have more. +!!! error TS2322: Type '[number, never?, never?, never?]' is not assignable to type '[number, string?, boolean?]'. +!!! error TS2322: Target allows only 3 element(s) but source may have more. t = [, , true]; // Error ~ -!!! error TS2375: Type '[never?, never?, true?]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. -!!! error TS2375: Source provides no match for required element at position 0 in target. +!!! 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 TS2375: Type '[number, undefined, true]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. -!!! error TS2375: Type at position 1 in source is not compatible with type at position 1 in target. -!!! error TS2375: 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() { @@ -176,19 +176,19 @@ tests/cases/compiler/strictOptionalProperties1.ts(195,1): error TS2375: Type '{ const t1: [number, string?, boolean?] = [1]; const t2: [number, string?, boolean?] = [1, undefined]; ~~ -!!! error TS2375: Type '[number, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. -!!! error TS2375: Type at position 1 in source is not compatible with type at position 1 in target. -!!! error TS2375: 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 TS2375: Type '[number, string, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. -!!! error TS2375: Type at position 2 in source is not compatible with type at position 2 in target. -!!! error TS2375: 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 TS2375: Type '[number, undefined, undefined]' is not assignable to type '[number, string?, boolean?]' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. -!!! error TS2375: Type at position 1 in source is not compatible with type at position 1 in target. -!!! error TS2375: Type 'undefined' is not assignable to type 'string'. +!!! 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 From 2c1982f3ca7283036dfde5c5a826e20e44b00a13 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 4 Aug 2021 09:57:34 -0700 Subject: [PATCH 12/16] Better way to get error node Plus add a check that errorNode is an argument to the call, not the call's expression. --- src/services/codefixes/addMissingAwait.ts | 28 ++++++------------- .../codefixes/addOptionalPropertyUndefined.ts | 24 ++++------------ src/services/utilities.ts | 16 +++++++++++ ...fixExactOptionalUnassignableProperties6.ts | 25 +++++++++++++++++ 4 files changed, 55 insertions(+), 38 deletions(-) create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts diff --git a/src/services/codefixes/addMissingAwait.ts b/src/services/codefixes/addMissingAwait.ts index e3a15d8864144..6d69844e4389b 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 index 6826766522079..0759a38cfde47 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -12,7 +12,7 @@ namespace ts.codefix { errorCodes, getCodeActions(context) { const typeChecker = context.program.getTypeChecker(); - const toAdd = getPropertiesToAdd(context.sourceFile, context.span.start, typeChecker); + const toAdd = getPropertiesToAdd(context.sourceFile, context.span, typeChecker); if (!toAdd.length) { return undefined; } @@ -26,7 +26,7 @@ namespace ts.codefix { const seen = new Map(); return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { eachDiagnostic(context, errorCodes, diag => { - const toAdd = getPropertiesToAdd(diag.file, diag.start, checker); + const toAdd = getPropertiesToAdd(diag.file, diag, checker); if (!toAdd.length) { return; } @@ -44,8 +44,8 @@ namespace ts.codefix { }, }); - function getPropertiesToAdd(file: SourceFile, pos: number, checker: TypeChecker): Symbol[] { - const sourceTarget = getSourceTarget(getErrorNode(file, pos), checker); + function getPropertiesToAdd(file: SourceFile, span: TextSpan, checker: TypeChecker): Symbol[] { + const sourceTarget = getSourceTarget(getFixableErrorSpanExpression(file, span), checker); if (!sourceTarget) { return emptyArray; } @@ -72,20 +72,6 @@ namespace ts.codefix { } return undefined; } - /** - * Get the part of the incorrect assignment that is useful for type-checking - * eg - * this.definite = 1; ---> `this.definite` - * ^^^^ - * definite = source ----> `definite` - * ^^^^^^^^ - */ - function getErrorNode(file: SourceFile, pos: number): MemberName | PropertyAccessExpression | undefined { - const start = getTokenAtPosition(file, pos); - return isPropertyAccessExpression(start.parent) && start.parent.expression === start ? start.parent - : isIdentifier(start) || isPrivateIdentifier(start) ? start - : undefined; - } /** * Find the source and target of the incorrect assignment. @@ -101,7 +87,7 @@ namespace ts.codefix { else if (isVariableDeclaration(errorNode.parent) && errorNode.parent.initializer) { return { source: errorNode.parent.initializer, target: errorNode.parent.name }; } - else if (isCallExpression(errorNode.parent)) { + else if (isCallExpression(errorNode.parent) && errorNode.parent.arguments.indexOf(errorNode as Expression) > -1) { const n = checker.getSymbolAtLocation(errorNode.parent.expression); if (!n?.valueDeclaration) return undefined; if (!isExpression(errorNode)) return undefined; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index b67c8da542046..f1f557e1d1364 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/cases/fourslash/fixExactOptionalUnassignableProperties6.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts new file mode 100644 index 0000000000000..0d251b910787b --- /dev/null +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts @@ -0,0 +1,25 @@ +/// + +// @strictNullChecks: true +// @exactOptionalPropertyTypes: true +// @Filename: fixExactOptionalUnassignableProperties6.ts +// based on snapshotterInjected.ts in microsoft/playwright +//// type Data = { +//// p?: boolean, +//// }; +//// declare function e(o: any): Data; +//// e(101).p = 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 = { + p?: boolean | undefined, +}; +declare function e(o: any): Data; +e(101).p = undefined`, +}); From 3ffe9dbbc9ddaa58101c4dd76febf1d16c4d0d8c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 4 Aug 2021 09:59:45 -0700 Subject: [PATCH 13/16] fix semicolon lint --- src/services/codefixes/addMissingAwait.ts | 2 +- src/services/utilities.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/codefixes/addMissingAwait.ts b/src/services/codefixes/addMissingAwait.ts index 6d69844e4389b..01f7ece1a0388 100644 --- a/src/services/codefixes/addMissingAwait.ts +++ b/src/services/codefixes/addMissingAwait.ts @@ -60,7 +60,7 @@ namespace ts.codefix { }); function getAwaitErrorSpanExpression(sourceFile: SourceFile, errorCode: number, span: TextSpan, cancellationToken: CancellationToken, program: Program) { - const expression = getFixableErrorSpanExpression(sourceFile, span) + const expression = getFixableErrorSpanExpression(sourceFile, span); return expression && isMissingAwaitError(sourceFile, errorCode, span, cancellationToken, program) && isInsideAwaitableBody(expression) ? expression : undefined; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index f1f557e1d1364..928f0b58a393d 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -3098,7 +3098,7 @@ namespace ts { return isExpression(node) && textSpansEqual(span, createTextSpanFromNode(node, sourceFile)); }) as Expression | undefined; - return expression + return expression; } /** From 3e7079876e8ab3e9dec5e662b64d8225c0af3404 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 4 Aug 2021 18:01:24 -0700 Subject: [PATCH 14/16] fix another crash --- .../codefixes/addOptionalPropertyUndefined.ts | 5 +++-- .../fixExactOptionalUnassignableProperties6.ts | 4 ++-- .../fixExactOptionalUnassignableProperties7.ts | 13 +++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties7.ts diff --git a/src/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts index 0759a38cfde47..21d1c7e1d9020 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -87,11 +87,12 @@ namespace ts.codefix { else if (isVariableDeclaration(errorNode.parent) && errorNode.parent.initializer) { return { source: errorNode.parent.initializer, target: errorNode.parent.name }; } - else if (isCallExpression(errorNode.parent) && errorNode.parent.arguments.indexOf(errorNode as Expression) > -1) { + else if (isCallExpression(errorNode.parent)) { const n = checker.getSymbolAtLocation(errorNode.parent.expression); - if (!n?.valueDeclaration) return undefined; + 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 }; } diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts index 0d251b910787b..a0cb446f3e45f 100644 --- a/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts @@ -5,7 +5,7 @@ // @Filename: fixExactOptionalUnassignableProperties6.ts // based on snapshotterInjected.ts in microsoft/playwright //// type Data = { -//// p?: boolean, +//// p?: (x: number) => void, //// }; //// declare function e(o: any): Data; //// e(101).p = undefined @@ -18,7 +18,7 @@ verify.codeFix({ index: 0, newFileContent: `type Data = { - p?: boolean | undefined, + p?: ((x: number) => void) | undefined, }; declare function e(o: any): Data; e(101).p = 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 From 4b7b3fe2b379d1d9ba24eba3c855cedfb93c15b9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 5 Aug 2021 13:43:31 -0700 Subject: [PATCH 15/16] Simplify: add undefined to all optional propertie whether or not somebody tried to assign undefined to them in the erroneous assignment --- src/compiler/checker.ts | 7 +++-- src/compiler/types.ts | 3 +- .../codefixes/addOptionalPropertyUndefined.ts | 24 +++++---------- ...fixExactOptionalUnassignableProperties2.ts | 2 +- ...fixExactOptionalUnassignableProperties6.ts | 12 +++++--- ...fixExactOptionalUnassignableProperties8.ts | 29 +++++++++++++++++++ 6 files changed, 52 insertions(+), 25 deletions(-) create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties8.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 85026f816410b..84e0b36b3c36b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -634,8 +634,7 @@ namespace ts { isTupleType, isArrayLikeType, isTypeInvalidDueToUnionDiscriminant, - getExactOptionalUnassignableProperties, - isExactOptionalPropertyMismatch, + getExactOptionalProperties, getAllPossiblePropertiesOfTypes, getSuggestedSymbolForNonexistentProperty, getSuggestionForNonexistentProperty, @@ -19589,6 +19588,10 @@ namespace ts { 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) || diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d44c14ec69073..d2309465d8536 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4307,8 +4307,7 @@ namespace ts { * e.g. it specifies `kind: "a"` and obj has `kind: "b"`. */ /* @internal */ isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean; - /* @internal */ getExactOptionalUnassignableProperties(source: Type, target: Type): Symbol[]; - /* @internal */ isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined): 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/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts index 21d1c7e1d9020..c3701aa225023 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -50,27 +50,19 @@ namespace ts.codefix { return emptyArray; } const { source: sourceNode, target: targetNode } = sourceTarget; - const target = checker.getTypeAtLocation(targetNode); - const source = checker.getTypeAtLocation(sourceNode); + 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; } - const targetPropertyType = getTargetPropertyType(checker, targetNode); - if (targetPropertyType && checker.isExactOptionalPropertyMismatch(source, targetPropertyType)) { - const s = checker.getSymbolAtLocation((targetNode as PropertyAccessExpression).name); - return s ? [s] : emptyArray; - } - return checker.getExactOptionalUnassignableProperties(source, target); + return checker.getExactOptionalProperties(target); } - function getTargetPropertyType(checker: TypeChecker, targetNode: Node) { - if (isPropertySignature(targetNode)) { - return checker.getTypeAtLocation(targetNode.name); - } - else if (isPropertyAccessExpression(targetNode)) { - return checker.getTypeOfPropertyOfType(checker.getTypeAtLocation(targetNode.expression), targetNode.name.text); - } - return undefined; + 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(); } /** diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts index 62efb1aa22007..6e54cd9706387 100644 --- a/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts @@ -105,7 +105,7 @@ interface ID { } interface More { a?: number | undefined - b?: number + b?: number | undefined } interface Assignment { a?: number | undefined diff --git a/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts index a0cb446f3e45f..32eb41a370288 100644 --- a/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts +++ b/tests/cases/fourslash/fixExactOptionalUnassignableProperties6.ts @@ -5,10 +5,12 @@ // @Filename: fixExactOptionalUnassignableProperties6.ts // based on snapshotterInjected.ts in microsoft/playwright //// type Data = { -//// p?: (x: number) => void, +//// f?: (x: number) => void, +//// additional?: number, +//// nop: string, //// }; //// declare function e(o: any): Data; -//// e(101).p = undefined +//// e(101).f = undefined verify.codeFixAvailable([ { description: ts.Diagnostics.Add_undefined_to_optional_property_type.message } ]); @@ -18,8 +20,10 @@ verify.codeFix({ index: 0, newFileContent: `type Data = { - p?: ((x: number) => void) | undefined, + f?: ((x: number) => void) | undefined, + additional?: number | undefined, + nop: string, }; declare function e(o: any): Data; -e(101).p = undefined`, +e(101).f = undefined`, }); 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 }`, +}); + From d53550990bfd1b36cdad9cd9520d2856abdce527 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 5 Aug 2021 14:46:24 -0700 Subject: [PATCH 16/16] remove fix-all --- src/compiler/diagnosticMessages.json | 6 +- .../codefixes/addOptionalPropertyUndefined.ts | 24 +-- ...ixExactOptionalUnassignableProperties10.ts | 32 ++++ ...ixExactOptionalUnassignableProperties11.ts | 36 ++++ ...ixExactOptionalUnassignableProperties12.ts | 38 +++++ ...ixExactOptionalUnassignableProperties13.ts | 36 ++++ ...ixExactOptionalUnassignableProperties14.ts | 32 ++++ ...ixExactOptionalUnassignableProperties15.ts | 34 ++++ ...ixExactOptionalUnassignableProperties16.ts | 30 ++++ ...ixExactOptionalUnassignableProperties17.ts | 30 ++++ ...ixExactOptionalUnassignableProperties18.ts | 30 ++++ ...ixExactOptionalUnassignableProperties19.ts | 32 ++++ ...fixExactOptionalUnassignableProperties2.ts | 160 ------------------ ...fixExactOptionalUnassignableProperties9.ts | 14 ++ 14 files changed, 346 insertions(+), 188 deletions(-) create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties10.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties11.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties12.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties13.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties14.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties15.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties16.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties17.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties18.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties19.ts delete mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts create mode 100644 tests/cases/fourslash/fixExactOptionalUnassignableProperties9.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 911dd2efc7df2..1d4487994a3e5 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7122,13 +7122,9 @@ "category": "Message", "code": 95166 }, - "Add 'undefined' to all optional properties": { - "category": "Message", - "code": 95167 - }, "Add 'undefined' to optional property type": { "category": "Message", - "code": 95168 + "code": 95167 }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { diff --git a/src/services/codefixes/addOptionalPropertyUndefined.ts b/src/services/codefixes/addOptionalPropertyUndefined.ts index c3701aa225023..13b80ed8a11e1 100644 --- a/src/services/codefixes/addOptionalPropertyUndefined.ts +++ b/src/services/codefixes/addOptionalPropertyUndefined.ts @@ -17,31 +17,9 @@ namespace ts.codefix { return undefined; } const changes = textChanges.ChangeTracker.with(context, t => addUndefinedToOptionalProperty(t, toAdd)); - return [createCodeFixAction(addOptionalPropertyUndefined, changes, Diagnostics.Add_undefined_to_optional_property_type, addOptionalPropertyUndefined, Diagnostics.Add_undefined_to_all_optional_properties)]; + return [createCodeFixActionWithoutFixAll(addOptionalPropertyUndefined, changes, Diagnostics.Add_undefined_to_optional_property_type)]; }, fixIds: [addOptionalPropertyUndefined], - getAllCodeActions: context => { - const { program } = context; - const checker = program.getTypeChecker(); - const seen = new Map(); - return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { - eachDiagnostic(context, errorCodes, diag => { - const toAdd = getPropertiesToAdd(diag.file, diag, checker); - if (!toAdd.length) { - return; - } - let untouched = true; - for (const add of toAdd) { - if (!addToSeen(seen, getSymbolId(add))) { - untouched = false; - } - } - if (untouched) { - addUndefinedToOptionalProperty(changes, toAdd); - } - }); - })); - }, }); function getPropertiesToAdd(file: SourceFile, span: TextSpan, checker: TypeChecker): Symbol[] { 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/fixExactOptionalUnassignableProperties2.ts b/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts deleted file mode 100644 index 6e54cd9706387..0000000000000 --- a/tests/cases/fourslash/fixExactOptionalUnassignableProperties2.ts +++ /dev/null @@ -1,160 +0,0 @@ -/// - -// @strictNullChecks: true -// @exactOptionalPropertyTypes: true - -//// interface I { -//// a?: number -//// } -//// interface IAny { -//// a?: any -//// } -//// interface IF { -//// a?: number -//// } -//// interface IC { -//// a?: number -//// } -//// interface IC2 { -//// a?: number -//// } -//// interface ICP { -//// a?: number -//// } -//// interface ID { -//// a?: number -//// } -//// interface More { -//// a?: number -//// b?: number -//// } -//// interface Assignment { -//// a?: number -//// } -//// interface PropertyAssignment { -//// a?: number -//// } -//// interface ShorthandPropertyAssignment { -//// a?: number -//// } -//// interface FPA { -//// a?: number -//// } -//// interface J { -//// a?: number | undefined -//// } -//// declare var i: I -//// declare var iany: IAny -//// declare var if: IF -//// declare var j: J -//// i = j -//// iany = j -//// function fi(if_: IF) { return if_ } -//// fi(j) -//// class C { -//// ic: IC -//// ic2: IC2 -//// m() { this.ic = j } -//// } -//// var c = new C() -//// c.ic2 = j -//// class CP { -//// #icp: ICP -//// m() { this.#icp = j } -//// } -//// declare var id: ID -//// ({ id } = { id: j }) -//// declare var pd: PropertyDescriptor -//// interface PartialWrongPropertyDescriptor { -//// configurable?: boolean | undefined -//// } -//// declare var pwpd: PartialWrongPropertyDescriptor -//// pd = pwpd -//// declare var more: More -//// more = j -//// var assignment: Assignment = j -//// var opa: { pa: PropertyAssignment } = { pa: j } -//// var ospa: { j: ShorthandPropertyAssignment } = { j } -//// declare function fpa(fpa: { fpa: FPA }): void -//// fpa({ fpa: j }) - -verify.codeFixAll({ - fixId: "addOptionalPropertyUndefined", - fixAllDescription: ts.Diagnostics.Add_undefined_to_all_optional_properties.message, - newFileContent: -`interface I { - a?: number | undefined -} -interface IAny { - a?: any -} -interface IF { - a?: number | undefined -} -interface IC { - a?: number | undefined -} -interface IC2 { - a?: number | undefined -} -interface ICP { - a?: number | undefined -} -interface ID { - a?: number | undefined -} -interface More { - a?: number | undefined - b?: number | undefined -} -interface Assignment { - a?: number | undefined -} -interface PropertyAssignment { - a?: number | undefined -} -interface ShorthandPropertyAssignment { - a?: number | undefined -} -interface FPA { - a?: number | undefined -} -interface J { - a?: number | undefined -} -declare var i: I -declare var iany: IAny -declare var if: IF -declare var j: J -i = j -iany = j -function fi(if_: IF) { return if_ } -fi(j) -class C { - ic: IC - ic2: IC2 - m() { this.ic = j } -} -var c = new C() -c.ic2 = j -class CP { - #icp: ICP - m() { this.#icp = j } -} -declare var id: ID -({ id } = { id: j }) -declare var pd: PropertyDescriptor -interface PartialWrongPropertyDescriptor { - configurable?: boolean | undefined -} -declare var pwpd: PartialWrongPropertyDescriptor -pd = pwpd -declare var more: More -more = j -var assignment: Assignment = j -var opa: { pa: PropertyAssignment } = { pa: j } -var ospa: { j: ShorthandPropertyAssignment } = { j } -declare function fpa(fpa: { fpa: FPA }): void -fpa({ fpa: j })`, -}); - 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([]);