Skip to content

Commit ef802b1

Browse files
authored
feat(60312): Add missing properties for satisfies (microsoft#60314)
1 parent 55f1248 commit ef802b1

14 files changed

+375
-15
lines changed

src/services/codefixes/fixAddMissingMember.ts

+18-12
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,13 @@ import {
7979
isPropertyAccessExpression,
8080
isPropertyDeclaration,
8181
isReturnStatement,
82+
isSatisfiesExpression,
8283
isSourceFile,
8384
isSourceFileFromLibrary,
8485
isSourceFileJS,
8586
isTransientSymbol,
8687
isTypeLiteralNode,
88+
isYieldExpression,
8789
JsxOpeningLikeElement,
8890
LanguageVariant,
8991
lastOrUndefined,
@@ -141,6 +143,7 @@ const errorCodes = [
141143
Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more.code,
142144
Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code,
143145
Diagnostics.Cannot_find_name_0.code,
146+
Diagnostics.Type_0_does_not_satisfy_the_expected_type_1.code,
144147
];
145148

146149
enum InfoKind {
@@ -188,9 +191,11 @@ registerCodeFix({
188191
return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => {
189192
eachDiagnostic(context, errorCodes, diag => {
190193
const info = getInfo(diag.file, diag.start, diag.code, checker, context.program);
191-
if (!info || !addToSeen(seen, getNodeId(info.parentDeclaration) + "#" + (info.kind === InfoKind.ObjectLiteral ? info.identifier : info.token.text))) {
192-
return;
193-
}
194+
if (info === undefined) return;
195+
196+
const nodeId = getNodeId(info.parentDeclaration) + "#" + (info.kind === InfoKind.ObjectLiteral ? info.identifier || getNodeId(info.token) : info.token.text);
197+
if (!addToSeen(seen, nodeId)) return;
198+
194199
if (fixId === fixMissingFunctionDeclaration && (info.kind === InfoKind.Function || info.kind === InfoKind.Signature)) {
195200
addFunctionDeclaration(changes, context, info);
196201
}
@@ -275,7 +280,7 @@ interface FunctionInfo {
275280
interface ObjectLiteralInfo {
276281
readonly kind: InfoKind.ObjectLiteral;
277282
readonly token: Node;
278-
readonly identifier: string;
283+
readonly identifier: string | undefined;
279284
readonly properties: Symbol[];
280285
readonly parentDeclaration: ObjectLiteralExpression;
281286
readonly indentation?: number;
@@ -320,15 +325,16 @@ function getInfo(sourceFile: SourceFile, tokenPos: number, errorCode: number, ch
320325
return { kind: InfoKind.ObjectLiteral, token: param.name, identifier: param.name.text, properties, parentDeclaration: parent };
321326
}
322327

323-
if (token.kind === SyntaxKind.OpenBraceToken && isObjectLiteralExpression(parent)) {
324-
const targetType = (checker.getContextualType(parent) || checker.getTypeAtLocation(parent))?.getNonNullableType();
325-
const properties = arrayFrom(checker.getUnmatchedProperties(checker.getTypeAtLocation(parent), targetType, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ false));
326-
if (!length(properties)) return undefined;
327-
328-
// no identifier needed because the whole parentDeclaration has the error
329-
const identifier = "";
328+
if (token.kind === SyntaxKind.OpenBraceToken || isSatisfiesExpression(parent) || isReturnStatement(parent)) {
329+
const expression = (isSatisfiesExpression(parent) || isReturnStatement(parent)) && parent.expression ? parent.expression : parent;
330+
if (isObjectLiteralExpression(expression)) {
331+
const targetType = isSatisfiesExpression(parent) ? checker.getTypeFromTypeNode(parent.type) :
332+
checker.getContextualType(expression) || checker.getTypeAtLocation(expression);
333+
const properties = arrayFrom(checker.getUnmatchedProperties(checker.getTypeAtLocation(parent), targetType.getNonNullableType(), /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ false));
334+
if (!length(properties)) return undefined;
330335

331-
return { kind: InfoKind.ObjectLiteral, token: parent, identifier, properties, parentDeclaration: parent };
336+
return { kind: InfoKind.ObjectLiteral, token: parent, identifier: undefined, properties, parentDeclaration: expression, indentation: isReturnStatement(expression.parent) || isYieldExpression(expression.parent) ? 0 : undefined };
337+
}
332338
}
333339

334340
if (!isMemberName(token)) return undefined;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface Foo {
4+
//// a: number;
5+
//// b: string;
6+
//// c: 1;
7+
//// d: "d";
8+
//// e: "e1" | "e2";
9+
//// f(x: number, y: number): void;
10+
//// g: (x: number, y: number) => void;
11+
//// h: number[];
12+
//// i: bigint;
13+
//// j: undefined | "special-string";
14+
//// k: `--${string}`;
15+
////}
16+
////[|const b = {} satisfies Foo;|]
17+
18+
verify.codeFix({
19+
index: 0,
20+
description: ts.Diagnostics.Add_missing_properties.message,
21+
newRangeContent:
22+
`const b = {
23+
a: 0,
24+
b: "",
25+
c: 1,
26+
d: "d",
27+
e: "e1",
28+
f: function(x: number, y: number): void {
29+
throw new Error("Function not implemented.");
30+
},
31+
g: function(x: number, y: number): void {
32+
throw new Error("Function not implemented.");
33+
},
34+
h: [],
35+
i: 0n,
36+
j: "special-string",
37+
k: ""
38+
} satisfies Foo;`,
39+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////interface Foo {
4+
//// a: number;
5+
//// b: string;
6+
//// c: any;
7+
////}
8+
////[|class C {
9+
//// public c = {} satisfies Foo;
10+
////}|]
11+
12+
verify.codeFix({
13+
index: 0,
14+
description: ts.Diagnostics.Add_missing_properties.message,
15+
newRangeContent:
16+
`class C {
17+
public c = {
18+
a: 0,
19+
b: "",
20+
c: undefined
21+
} satisfies Foo;
22+
}`
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////interface Foo {
4+
//// a: number;
5+
//// b: string;
6+
////}
7+
////[|const foo = { a: 10 } satisfies Foo;|]
8+
9+
verify.codeFix({
10+
index: 0,
11+
description: ts.Diagnostics.Add_missing_properties.message,
12+
newRangeContent:
13+
`const foo = {
14+
a: 10,
15+
b: ""
16+
} satisfies Foo;`
17+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////type T = {
4+
//// a: null;
5+
////}
6+
////
7+
////[|const foo = {} satisfies T;|]
8+
9+
verify.codeFix({
10+
index: 0,
11+
description: ts.Diagnostics.Add_missing_properties.message,
12+
newRangeContent:
13+
`const foo = {
14+
a: null
15+
} satisfies T;`
16+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////interface I {
4+
//// x: number;
5+
//// y: number;
6+
////}
7+
////class C {
8+
//// public p: number;
9+
//// m(x: number, y: I) {}
10+
////}
11+
////[|const foo = {} satisfies C;|]
12+
13+
verify.codeFix({
14+
index: 0,
15+
description: ts.Diagnostics.Add_missing_properties.message,
16+
newRangeContent:
17+
`const foo = {
18+
p: 0,
19+
m: function(x: number, y: I): void {
20+
throw new Error("Function not implemented.");
21+
}
22+
} satisfies C;`
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////enum E1 {
4+
//// A, B
5+
////}
6+
////enum E2 {
7+
//// A
8+
////}
9+
////enum E3 {
10+
////}
11+
////interface I {
12+
//// x: E1;
13+
//// y: E2;
14+
//// z: E3;
15+
////}
16+
////[|const foo = {} satisfies I;|]
17+
18+
verify.codeFix({
19+
index: 0,
20+
description: ts.Diagnostics.Add_missing_properties.message,
21+
newRangeContent:
22+
`const foo = {
23+
x: E1.A,
24+
y: E2.A,
25+
z: 0
26+
} satisfies I;`
27+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////class A {
4+
//// constructor() {}
5+
////}
6+
////
7+
////abstract class B {}
8+
////
9+
////class C {
10+
//// constructor(a: string, b: number, c: A) {}
11+
////}
12+
////
13+
////interface I {
14+
//// a: A;
15+
//// b: B;
16+
//// c: C;
17+
////}
18+
////[|const foo = {} satisfies I;|]
19+
20+
verify.codeFix({
21+
index: 0,
22+
description: ts.Diagnostics.Add_missing_properties.message,
23+
newRangeContent:
24+
`const foo = {
25+
a: new A,
26+
b: undefined,
27+
c: undefined
28+
} satisfies I;`
29+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////interface I {
4+
//// a: {
5+
//// x: number;
6+
//// y: { z: string; };
7+
//// }
8+
////}
9+
////[|const foo = {} satisfies I;|]
10+
11+
verify.codeFix({
12+
index: 0,
13+
description: ts.Diagnostics.Add_missing_properties.message,
14+
newRangeContent:
15+
`const foo = {
16+
a: {
17+
x: 0,
18+
y: {
19+
z: ""
20+
}
21+
}
22+
} satisfies I;`
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////interface Bar {
4+
//// a: number;
5+
////}
6+
////
7+
////interface Foo<T, U> {
8+
//// foo(a: T): U;
9+
////}
10+
////[|const x = {} satisfies Foo<string, Bar>;|]
11+
12+
verify.codeFix({
13+
index: 0,
14+
description: ts.Diagnostics.Add_missing_properties.message,
15+
newRangeContent:
16+
`const x = {
17+
foo: function(a: string): Bar {
18+
throw new Error("Function not implemented.");
19+
}
20+
} satisfies Foo<string, Bar>;`,
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////type A = { a: string };
4+
////type B = { b: string };
5+
////
6+
////[|const c = { } satisfies A satisfies B;|]
7+
8+
verify.codeFix({
9+
index: 0,
10+
description: ts.Diagnostics.Add_missing_properties.message,
11+
newRangeContent:
12+
`const c = {
13+
a: ""
14+
} satisfies A satisfies B;`,
15+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////interface Foo {
4+
//// a: number;
5+
//// b: string;
6+
//// c: 1;
7+
//// d: "d";
8+
//// e: "e1" | "e2";
9+
//// f(x: number, y: number): void;
10+
//// g: (x: number, y: number) => void;
11+
//// h: number[];
12+
//// i: bigint;
13+
//// j: undefined | "special-string";
14+
//// k: `--${string}`;
15+
////}
16+
////const f = (): Foo => {
17+
//// [|return { };|]
18+
////};
19+
20+
verify.codeFix({
21+
index: 0,
22+
description: ts.Diagnostics.Add_missing_properties.message,
23+
newRangeContent:
24+
`return {
25+
a: 0,
26+
b: "",
27+
c: 1,
28+
d: "d",
29+
e: "e1",
30+
f: function(x: number, y: number): void {
31+
throw new Error("Function not implemented.");
32+
},
33+
g: function(x: number, y: number): void {
34+
throw new Error("Function not implemented.");
35+
},
36+
h: [],
37+
i: 0n,
38+
j: "special-string",
39+
k: ""
40+
};`,
41+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/// <reference path='fourslash.ts' />
2+
// @lib: es2020
3+
// @target: es2020
4+
5+
////interface Foo {
6+
//// a: number;
7+
//// b: string;
8+
//// c: 1;
9+
//// d: "d";
10+
//// e: "e1" | "e2";
11+
//// f(x: number, y: number): void;
12+
//// g: (x: number, y: number) => void;
13+
//// h: number[];
14+
//// i: bigint;
15+
//// j: undefined | "special-string";
16+
//// k: `--${string}`;
17+
////}
18+
////const f = function* (): Generator<Foo, void, any> {
19+
//// [|yield {};|]
20+
////};
21+
22+
verify.codeFix({
23+
index: 0,
24+
description: ts.Diagnostics.Add_missing_properties.message,
25+
newRangeContent:
26+
`yield {
27+
a: 0,
28+
b: "",
29+
c: 1,
30+
d: "d",
31+
e: "e1",
32+
f: function(x: number, y: number): void {
33+
throw new Error("Function not implemented.");
34+
},
35+
g: function(x: number, y: number): void {
36+
throw new Error("Function not implemented.");
37+
},
38+
h: [],
39+
i: 0n,
40+
j: "special-string",
41+
k: ""
42+
};`,
43+
});

0 commit comments

Comments
 (0)