Skip to content

Commit

Permalink
feat(60312): Add missing properties for satisfies (#60314)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk authored Nov 8, 2024
1 parent 55f1248 commit ef802b1
Show file tree
Hide file tree
Showing 14 changed files with 375 additions and 15 deletions.
30 changes: 18 additions & 12 deletions src/services/codefixes/fixAddMissingMember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,13 @@ import {
isPropertyAccessExpression,
isPropertyDeclaration,
isReturnStatement,
isSatisfiesExpression,
isSourceFile,
isSourceFileFromLibrary,
isSourceFileJS,
isTransientSymbol,
isTypeLiteralNode,
isYieldExpression,
JsxOpeningLikeElement,
LanguageVariant,
lastOrUndefined,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
39 changes: 39 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties33.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/// <reference path="fourslash.ts" />

////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;`,
});
23 changes: 23 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties34.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />

////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;
}`
});
17 changes: 17 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties35.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// <reference path='fourslash.ts' />

////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;`
});
16 changes: 16 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties36.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />

////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;`
});
23 changes: 23 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties37.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />

////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;`
});
27 changes: 27 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties38.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/// <reference path='fourslash.ts' />

////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;`
});
29 changes: 29 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties39.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// <reference path='fourslash.ts' />

////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;`
});
23 changes: 23 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties40.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />

////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;`
});
21 changes: 21 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties41.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />

////interface Bar {
//// a: number;
////}
////
////interface Foo<T, U> {
//// foo(a: T): U;
////}
////[|const x = {} satisfies Foo<string, Bar>;|]

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<string, Bar>;`,
});
15 changes: 15 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties42.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />

////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;`,
});
41 changes: 41 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties43.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/// <reference path='fourslash.ts' />

////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: ""
};`,
});
43 changes: 43 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties44.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/// <reference path='fourslash.ts' />
// @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<Foo, void, any> {
//// [|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: ""
};`,
});
Loading

0 comments on commit ef802b1

Please sign in to comment.