Skip to content

Commit 1fd5bd2

Browse files
authored
Filter out self-fulfilling completions (#35709)
1 parent ec84392 commit 1fd5bd2

File tree

4 files changed

+81
-3
lines changed

4 files changed

+81
-3
lines changed

src/services/completions.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,18 +2540,30 @@ namespace ts.Completions {
25402540
}
25412541

25422542
function getPropertiesForObjectExpression(contextualType: Type, baseConstrainedType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] {
2543-
const type = baseConstrainedType && !(baseConstrainedType.flags & TypeFlags.AnyOrUnknown)
2544-
? checker.getUnionType([contextualType, baseConstrainedType])
2543+
const hasBaseType = baseConstrainedType && baseConstrainedType !== contextualType;
2544+
const type = hasBaseType && !(baseConstrainedType!.flags & TypeFlags.AnyOrUnknown)
2545+
? checker.getUnionType([contextualType, baseConstrainedType!])
25452546
: contextualType;
25462547

2547-
return type.isUnion()
2548+
const properties = type.isUnion()
25482549
? checker.getAllPossiblePropertiesOfTypes(type.types.filter(memberType =>
25492550
// If we're providing completions for an object literal, skip primitive, array-like, or callable types since those shouldn't be implemented by object literals.
25502551
!(memberType.flags & TypeFlags.Primitive ||
25512552
checker.isArrayLikeType(memberType) ||
25522553
typeHasCallOrConstructSignatures(memberType, checker) ||
25532554
checker.isTypeInvalidDueToUnionDiscriminant(memberType, obj))))
25542555
: type.getApparentProperties();
2556+
2557+
return hasBaseType ? properties.filter(hasDeclarationOtherThanSelf) : properties;
2558+
2559+
// Filter out members whose only declaration is the object literal itself to avoid
2560+
// self-fulfilling completions like:
2561+
//
2562+
// function f<T>(x: T) {}
2563+
// f({ abc/**/: "" }) // `abc` is a member of `T` but only because it declares itself
2564+
function hasDeclarationOtherThanSelf(member: Symbol) {
2565+
return some(member.declarations, decl => decl.parent !== obj);
2566+
}
25552567
}
25562568

25572569
/**
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface Test {
4+
//// keyPath?: string;
5+
//// autoIncrement?: boolean;
6+
////}
7+
////
8+
////function test<T extends Record<string, Test>>(opt: T) { }
9+
////
10+
////test({
11+
//// a: {
12+
//// keyPath: '',
13+
//// a/**/
14+
//// }
15+
////})
16+
17+
verify.completions({
18+
marker: "",
19+
exact: [{
20+
name: "autoIncrement",
21+
sortText: completion.SortText.OptionalMember
22+
}]
23+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function f1<T>(x: T) {}
4+
////f1({ abc/*1*/ });
5+
////
6+
////function f2<T extends { xyz: number }>(x: T) {}
7+
////f2({ x/*2*/ });
8+
9+
10+
verify.completions({
11+
marker: "1",
12+
exact: []
13+
});
14+
15+
verify.completions({
16+
marker: "2",
17+
exact: ["xyz"]
18+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function f<T extends { x: number }>(p: T & (T extends { hello: string } ? { goodbye: number } : {})) {}
4+
////f({ x/*x*/: 0, hello/*hello*/: "", goodbye/*goodbye*/: 0, abc/*abc*/: "" })
5+
6+
7+
verify.completions({
8+
marker: "x",
9+
exact: ["x"]
10+
});
11+
12+
verify.completions({
13+
marker: "hello",
14+
exact: []
15+
});
16+
17+
verify.completions({
18+
marker: "goodbye",
19+
exact: ["goodbye"]
20+
});
21+
22+
verify.completions({
23+
marker: "abc",
24+
exact: []
25+
});

0 commit comments

Comments
 (0)