From f398f4ce942ff6718882201f243b420387d14447 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 16 Dec 2019 13:08:05 -0800 Subject: [PATCH] Filter out self-fulfilling completions --- src/services/completions.ts | 18 ++++++++++--- .../fourslash/completionsSelfDeclaring1.ts | 23 +++++++++++++++++ .../fourslash/completionsSelfDeclaring2.ts | 18 +++++++++++++ .../fourslash/completionsSelfDeclaring3.ts | 25 +++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 tests/cases/fourslash/completionsSelfDeclaring1.ts create mode 100644 tests/cases/fourslash/completionsSelfDeclaring2.ts create mode 100644 tests/cases/fourslash/completionsSelfDeclaring3.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 23a64920a86e4..af3698d5cd1d7 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -2540,11 +2540,12 @@ namespace ts.Completions { } function getPropertiesForObjectExpression(contextualType: Type, baseConstrainedType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { - const type = baseConstrainedType && !(baseConstrainedType.flags & TypeFlags.AnyOrUnknown) - ? checker.getUnionType([contextualType, baseConstrainedType]) + const hasBaseType = baseConstrainedType && baseConstrainedType !== contextualType; + const type = hasBaseType && !(baseConstrainedType!.flags & TypeFlags.AnyOrUnknown) + ? checker.getUnionType([contextualType, baseConstrainedType!]) : contextualType; - return type.isUnion() + const properties = type.isUnion() ? checker.getAllPossiblePropertiesOfTypes(type.types.filter(memberType => // 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. !(memberType.flags & TypeFlags.Primitive || @@ -2552,6 +2553,17 @@ namespace ts.Completions { typeHasCallOrConstructSignatures(memberType, checker) || checker.isTypeInvalidDueToUnionDiscriminant(memberType, obj)))) : type.getApparentProperties(); + + return hasBaseType ? properties.filter(hasDeclarationOtherThanSelf) : properties; + + // Filter out members whose only declaration is the object literal itself to avoid + // self-fulfilling completions like: + // + // function f(x: T) {} + // f({ abc/**/: "" }) // `abc` is a member of `T` but only because it declares itself + function hasDeclarationOtherThanSelf(member: Symbol) { + return some(member.declarations, decl => decl.parent !== obj); + } } /** diff --git a/tests/cases/fourslash/completionsSelfDeclaring1.ts b/tests/cases/fourslash/completionsSelfDeclaring1.ts new file mode 100644 index 0000000000000..5dbaa3b013462 --- /dev/null +++ b/tests/cases/fourslash/completionsSelfDeclaring1.ts @@ -0,0 +1,23 @@ +/// + +////interface Test { +//// keyPath?: string; +//// autoIncrement?: boolean; +////} +//// +////function test>(opt: T) { } +//// +////test({ +//// a: { +//// keyPath: '', +//// a/**/ +//// } +////}) + +verify.completions({ + marker: "", + exact: [{ + name: "autoIncrement", + sortText: completion.SortText.OptionalMember + }] +}); diff --git a/tests/cases/fourslash/completionsSelfDeclaring2.ts b/tests/cases/fourslash/completionsSelfDeclaring2.ts new file mode 100644 index 0000000000000..ee313de90c0bf --- /dev/null +++ b/tests/cases/fourslash/completionsSelfDeclaring2.ts @@ -0,0 +1,18 @@ +/// + +////function f1(x: T) {} +////f1({ abc/*1*/ }); +//// +////function f2(x: T) {} +////f2({ x/*2*/ }); + + +verify.completions({ + marker: "1", + exact: [] +}); + +verify.completions({ + marker: "2", + exact: ["xyz"] +}); diff --git a/tests/cases/fourslash/completionsSelfDeclaring3.ts b/tests/cases/fourslash/completionsSelfDeclaring3.ts new file mode 100644 index 0000000000000..1045fa14ad909 --- /dev/null +++ b/tests/cases/fourslash/completionsSelfDeclaring3.ts @@ -0,0 +1,25 @@ +/// + +////function f(p: T & (T extends { hello: string } ? { goodbye: number } : {})) {} +////f({ x/*x*/: 0, hello/*hello*/: "", goodbye/*goodbye*/: 0, abc/*abc*/: "" }) + + +verify.completions({ + marker: "x", + exact: ["x"] +}); + +verify.completions({ + marker: "hello", + exact: [] +}); + +verify.completions({ + marker: "goodbye", + exact: ["goodbye"] +}); + +verify.completions({ + marker: "abc", + exact: [] +});