diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index c57d2a37fd355..471cbab82c6c9 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -79,11 +79,13 @@ import { isPropertyAccessExpression, isPropertyDeclaration, isReturnStatement, + isSatisfiesExpression, isSourceFile, isSourceFileFromLibrary, isSourceFileJS, isTransientSymbol, isTypeLiteralNode, + isYieldExpression, JsxOpeningLikeElement, LanguageVariant, lastOrUndefined, @@ -141,6 +143,7 @@ const errorCodes = [ Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more.code, Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code, Diagnostics.Cannot_find_name_0.code, + Diagnostics.Type_0_does_not_satisfy_the_expected_type_1.code, ]; enum InfoKind { @@ -188,9 +191,11 @@ registerCodeFix({ return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { eachDiagnostic(context, errorCodes, diag => { const info = getInfo(diag.file, diag.start, diag.code, checker, context.program); - if (!info || !addToSeen(seen, getNodeId(info.parentDeclaration) + "#" + (info.kind === InfoKind.ObjectLiteral ? info.identifier : info.token.text))) { - return; - } + if (info === undefined) return; + + const nodeId = getNodeId(info.parentDeclaration) + "#" + (info.kind === InfoKind.ObjectLiteral ? info.identifier || getNodeId(info.token) : info.token.text); + if (!addToSeen(seen, nodeId)) return; + if (fixId === fixMissingFunctionDeclaration && (info.kind === InfoKind.Function || info.kind === InfoKind.Signature)) { addFunctionDeclaration(changes, context, info); } @@ -275,7 +280,7 @@ interface FunctionInfo { interface ObjectLiteralInfo { readonly kind: InfoKind.ObjectLiteral; readonly token: Node; - readonly identifier: string; + readonly identifier: string | undefined; readonly properties: Symbol[]; readonly parentDeclaration: ObjectLiteralExpression; readonly indentation?: number; @@ -320,15 +325,16 @@ function getInfo(sourceFile: SourceFile, tokenPos: number, errorCode: number, ch return { kind: InfoKind.ObjectLiteral, token: param.name, identifier: param.name.text, properties, parentDeclaration: parent }; } - if (token.kind === SyntaxKind.OpenBraceToken && isObjectLiteralExpression(parent)) { - const targetType = (checker.getContextualType(parent) || checker.getTypeAtLocation(parent))?.getNonNullableType(); - const properties = arrayFrom(checker.getUnmatchedProperties(checker.getTypeAtLocation(parent), targetType, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ false)); - if (!length(properties)) return undefined; - - // no identifier needed because the whole parentDeclaration has the error - const identifier = ""; + if (token.kind === SyntaxKind.OpenBraceToken || isSatisfiesExpression(parent) || isReturnStatement(parent)) { + const expression = (isSatisfiesExpression(parent) || isReturnStatement(parent)) && parent.expression ? parent.expression : parent; + if (isObjectLiteralExpression(expression)) { + const targetType = isSatisfiesExpression(parent) ? checker.getTypeFromTypeNode(parent.type) : + checker.getContextualType(expression) || checker.getTypeAtLocation(expression); + const properties = arrayFrom(checker.getUnmatchedProperties(checker.getTypeAtLocation(parent), targetType.getNonNullableType(), /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ false)); + if (!length(properties)) return undefined; - return { kind: InfoKind.ObjectLiteral, token: parent, identifier, properties, parentDeclaration: parent }; + return { kind: InfoKind.ObjectLiteral, token: parent, identifier: undefined, properties, parentDeclaration: expression, indentation: isReturnStatement(expression.parent) || isYieldExpression(expression.parent) ? 0 : undefined }; + } } if (!isMemberName(token)) return undefined; diff --git a/tests/cases/fourslash/codeFixAddMissingProperties33.ts b/tests/cases/fourslash/codeFixAddMissingProperties33.ts new file mode 100644 index 0000000000000..c034471a4205a --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties33.ts @@ -0,0 +1,39 @@ +/// + +////interface Foo { +//// a: number; +//// b: string; +//// c: 1; +//// d: "d"; +//// e: "e1" | "e2"; +//// f(x: number, y: number): void; +//// g: (x: number, y: number) => void; +//// h: number[]; +//// i: bigint; +//// j: undefined | "special-string"; +//// k: `--${string}`; +////} +////[|const b = {} satisfies Foo;|] + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`const b = { + a: 0, + b: "", + c: 1, + d: "d", + e: "e1", + f: function(x: number, y: number): void { + throw new Error("Function not implemented."); + }, + g: function(x: number, y: number): void { + throw new Error("Function not implemented."); + }, + h: [], + i: 0n, + j: "special-string", + k: "" +} satisfies Foo;`, +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties34.ts b/tests/cases/fourslash/codeFixAddMissingProperties34.ts new file mode 100644 index 0000000000000..a74658c2ed5c1 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties34.ts @@ -0,0 +1,23 @@ +/// + +////interface Foo { +//// a: number; +//// b: string; +//// c: any; +////} +////[|class C { +//// public c = {} satisfies Foo; +////}|] + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`class C { + public c = { + a: 0, + b: "", + c: undefined + } satisfies Foo; +}` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties35.ts b/tests/cases/fourslash/codeFixAddMissingProperties35.ts new file mode 100644 index 0000000000000..f9b120cba71c2 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties35.ts @@ -0,0 +1,17 @@ +/// + +////interface Foo { +//// a: number; +//// b: string; +////} +////[|const foo = { a: 10 } satisfies Foo;|] + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`const foo = { + a: 10, + b: "" +} satisfies Foo;` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties36.ts b/tests/cases/fourslash/codeFixAddMissingProperties36.ts new file mode 100644 index 0000000000000..629943c1cf3a8 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties36.ts @@ -0,0 +1,16 @@ +/// + +////type T = { +//// a: null; +////} +//// +////[|const foo = {} satisfies T;|] + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`const foo = { + a: null +} satisfies T;` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties37.ts b/tests/cases/fourslash/codeFixAddMissingProperties37.ts new file mode 100644 index 0000000000000..cdc0b7e82f775 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties37.ts @@ -0,0 +1,23 @@ +/// + +////interface I { +//// x: number; +//// y: number; +////} +////class C { +//// public p: number; +//// m(x: number, y: I) {} +////} +////[|const foo = {} satisfies C;|] + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`const foo = { + p: 0, + m: function(x: number, y: I): void { + throw new Error("Function not implemented."); + } +} satisfies C;` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties38.ts b/tests/cases/fourslash/codeFixAddMissingProperties38.ts new file mode 100644 index 0000000000000..0932e6581f0c9 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties38.ts @@ -0,0 +1,27 @@ +/// + +////enum E1 { +//// A, B +////} +////enum E2 { +//// A +////} +////enum E3 { +////} +////interface I { +//// x: E1; +//// y: E2; +//// z: E3; +////} +////[|const foo = {} satisfies I;|] + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`const foo = { + x: E1.A, + y: E2.A, + z: 0 +} satisfies I;` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties39.ts b/tests/cases/fourslash/codeFixAddMissingProperties39.ts new file mode 100644 index 0000000000000..d7f99cbfcb8c7 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties39.ts @@ -0,0 +1,29 @@ +/// + +////class A { +//// constructor() {} +////} +//// +////abstract class B {} +//// +////class C { +//// constructor(a: string, b: number, c: A) {} +////} +//// +////interface I { +//// a: A; +//// b: B; +//// c: C; +////} +////[|const foo = {} satisfies I;|] + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`const foo = { + a: new A, + b: undefined, + c: undefined +} satisfies I;` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties40.ts b/tests/cases/fourslash/codeFixAddMissingProperties40.ts new file mode 100644 index 0000000000000..ca640fe0ec8f5 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties40.ts @@ -0,0 +1,23 @@ +/// + +////interface I { +//// a: { +//// x: number; +//// y: { z: string; }; +//// } +////} +////[|const foo = {} satisfies I;|] + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`const foo = { + a: { + x: 0, + y: { + z: "" + } + } +} satisfies I;` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties41.ts b/tests/cases/fourslash/codeFixAddMissingProperties41.ts new file mode 100644 index 0000000000000..42330c13daa7d --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties41.ts @@ -0,0 +1,21 @@ +/// + +////interface Bar { +//// a: number; +////} +//// +////interface Foo { +//// foo(a: T): U; +////} +////[|const x = {} satisfies Foo;|] + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`const x = { + foo: function(a: string): Bar { + throw new Error("Function not implemented."); + } +} satisfies Foo;`, +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties42.ts b/tests/cases/fourslash/codeFixAddMissingProperties42.ts new file mode 100644 index 0000000000000..184a36914c041 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties42.ts @@ -0,0 +1,15 @@ +/// + +////type A = { a: string }; +////type B = { b: string }; +//// +////[|const c = { } satisfies A satisfies B;|] + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`const c = { + a: "" +} satisfies A satisfies B;`, +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties43.ts b/tests/cases/fourslash/codeFixAddMissingProperties43.ts new file mode 100644 index 0000000000000..bf50ec73974c9 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties43.ts @@ -0,0 +1,41 @@ +/// + +////interface Foo { +//// a: number; +//// b: string; +//// c: 1; +//// d: "d"; +//// e: "e1" | "e2"; +//// f(x: number, y: number): void; +//// g: (x: number, y: number) => void; +//// h: number[]; +//// i: bigint; +//// j: undefined | "special-string"; +//// k: `--${string}`; +////} +////const f = (): Foo => { +//// [|return { };|] +////}; + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`return { + a: 0, + b: "", + c: 1, + d: "d", + e: "e1", + f: function(x: number, y: number): void { + throw new Error("Function not implemented."); + }, + g: function(x: number, y: number): void { + throw new Error("Function not implemented."); + }, + h: [], + i: 0n, + j: "special-string", + k: "" +};`, +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties44.ts b/tests/cases/fourslash/codeFixAddMissingProperties44.ts new file mode 100644 index 0000000000000..5cb87bd2a53d9 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties44.ts @@ -0,0 +1,43 @@ +/// +// @lib: es2020 +// @target: es2020 + +////interface Foo { +//// a: number; +//// b: string; +//// c: 1; +//// d: "d"; +//// e: "e1" | "e2"; +//// f(x: number, y: number): void; +//// g: (x: number, y: number) => void; +//// h: number[]; +//// i: bigint; +//// j: undefined | "special-string"; +//// k: `--${string}`; +////} +////const f = function* (): Generator { +//// [|yield {};|] +////}; + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newRangeContent: +`yield { + a: 0, + b: "", + c: 1, + d: "d", + e: "e1", + f: function(x: number, y: number): void { + throw new Error("Function not implemented."); + }, + g: function(x: number, y: number): void { + throw new Error("Function not implemented."); + }, + h: [], + i: 0n, + j: "special-string", + k: "" +};`, +}); diff --git a/tests/cases/fourslash/codeFixAddMissingProperties_all.ts b/tests/cases/fourslash/codeFixAddMissingProperties_all.ts index 78c7e6d165674..aacfe48ddd450 100644 --- a/tests/cases/fourslash/codeFixAddMissingProperties_all.ts +++ b/tests/cases/fourslash/codeFixAddMissingProperties_all.ts @@ -15,12 +15,17 @@ ////} ////const a: I1 = {}; ////const b: I2 = {}; -////class C { +////class C1 { //// public c: I1 = {}; ////} ////function fn1(foo: I2 = {}) {} ////function fn2(a: I1) {} ////fn2({}); +////const d = {} satisfies I1; +////const e = {} satisfies I2; +////class C2 { +//// public f = {} satisfies I1; +////} verify.codeFixAll({ fixId: "fixMissingProperties", @@ -56,7 +61,7 @@ const b: I2 = { a: undefined, b: undefined }; -class C { +class C1 { public c: I1 = { a: 0, b: "", @@ -88,5 +93,37 @@ fn2({ g: function(x: number, y: number): void { throw new Error("Function not implemented."); } -});` +}); +const d = { + a: 0, + b: "", + c: 1, + d: "d", + e: "e1", + f: function(x: number, y: number): void { + throw new Error("Function not implemented."); + }, + g: function(x: number, y: number): void { + throw new Error("Function not implemented."); + } +} satisfies I1; +const e = { + a: undefined, + b: undefined +} satisfies I2; +class C2 { + public f = { + a: 0, + b: "", + c: 1, + d: "d", + e: "e1", + f: function(x: number, y: number): void { + throw new Error("Function not implemented."); + }, + g: function(x: number, y: number): void { + throw new Error("Function not implemented."); + } + } satisfies I1; +}` });