From 3d24b85f9e5dde6f35d6014539aeef9801e74493 Mon Sep 17 00:00:00 2001 From: Tiago Tristao Date: Tue, 20 Apr 2021 18:24:17 +0100 Subject: [PATCH] Completion list for type literals in type arguments (#43526) * Completion list for type literals in type arguments * Add tests * Refactor for better readability * - Support non-identifier keys - Move main logic onto tryGetGlobalSymbols function --- src/compiler/checker.ts | 1 + src/compiler/types.ts | 1 + src/services/completions.ts | 72 ++++++++++++++++++- ...letionListInTypeLiteralInTypeParameter1.ts | 23 ++++++ ...letionListInTypeLiteralInTypeParameter2.ts | 14 ++++ ...letionListInTypeLiteralInTypeParameter3.ts | 14 ++++ ...letionListInTypeLiteralInTypeParameter4.ts | 14 ++++ ...letionListInTypeLiteralInTypeParameter5.ts | 14 ++++ ...letionListInTypeLiteralInTypeParameter6.ts | 14 ++++ ...letionListInTypeLiteralInTypeParameter7.ts | 17 +++++ ...letionListInTypeLiteralInTypeParameter8.ts | 33 +++++++++ ...letionListInTypeLiteralInTypeParameter9.ts | 25 +++++++ 12 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter1.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter2.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter3.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter4.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter5.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter6.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter7.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter8.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter9.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7471ce4cdb5..588b5010b14 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -547,6 +547,7 @@ namespace ts { return node && getContextualTypeForJsxAttribute(node); }, isContextSensitive, + getTypeOfPropertyOfContextualType, getFullyQualifiedName, getResolvedSignature: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d4812e0b327..0dda734aedf 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4144,6 +4144,7 @@ namespace ts { /* @internal */ getContextualTypeForArgumentAtIndex(call: CallLikeExpression, argIndex: number): Type | undefined; /* @internal */ getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined; /* @internal */ isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean; + /* @internal */ getTypeOfPropertyOfContextualType(type: Type, name: __String): Type | undefined; /** * returns unknownSignature in the case of an error. diff --git a/src/services/completions.ts b/src/services/completions.ts index abe1cd1fd5c..509558b3559 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1510,7 +1510,8 @@ namespace ts.Completions { } function tryGetGlobalSymbols(): boolean { - const result: GlobalsSearch = tryGetObjectLikeCompletionSymbols() + const result: GlobalsSearch = tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols() + || tryGetObjectLikeCompletionSymbols() || tryGetImportCompletionSymbols() || tryGetImportOrExportClauseCompletionSymbols() || tryGetLocalNamedExportCompletionSymbols() @@ -1913,6 +1914,32 @@ namespace ts.Completions { position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken))); } + function tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols(): GlobalsSearch | undefined { + const typeLiteralNode = tryGetTypeLiteralNode(contextToken); + if (!typeLiteralNode) return GlobalsSearch.Continue; + + const intersectionTypeNode = isIntersectionTypeNode(typeLiteralNode.parent) ? typeLiteralNode.parent : undefined; + const containerTypeNode = intersectionTypeNode || typeLiteralNode; + + const containerExpectedType = getConstraintOfTypeArgumentProperty(containerTypeNode, typeChecker); + if (!containerExpectedType) return GlobalsSearch.Continue; + + const containerActualType = typeChecker.getTypeFromTypeNode(containerTypeNode); + + const members = getPropertiesForCompletion(containerExpectedType, typeChecker); + const existingMembers = getPropertiesForCompletion(containerActualType, typeChecker); + + const existingMemberEscapedNames: Set<__String> = new Set(); + existingMembers.forEach(s => existingMemberEscapedNames.add(s.escapedName)); + + symbols = filter(members, s => !existingMemberEscapedNames.has(s.escapedName)); + + completionKind = CompletionKind.ObjectPropertyDeclaration; + isNewIdentifierLocation = true; + + return GlobalsSearch.Success; + } + /** * Aggregates relevant symbols for completion in object literals and object binding patterns. * Relevant symbols are stored in the captured 'symbols' variable. @@ -2859,6 +2886,49 @@ namespace ts.Completions { } } + function tryGetTypeLiteralNode(node: Node): TypeLiteralNode | undefined { + if (!node) return undefined; + + const parent = node.parent; + + switch (node.kind) { + case SyntaxKind.OpenBraceToken: + if (isTypeLiteralNode(parent)) { + return parent; + } + break; + case SyntaxKind.SemicolonToken: + case SyntaxKind.CommaToken: + case SyntaxKind.Identifier: + if (parent.kind === SyntaxKind.PropertySignature && isTypeLiteralNode(parent.parent)) { + return parent.parent; + } + break; + } + + return undefined; + } + + function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): Type | undefined { + if (!node) return undefined; + + if (isTypeNode(node) && isTypeReferenceType(node.parent)) { + return checker.getTypeArgumentConstraint(node); + } + + const t = getConstraintOfTypeArgumentProperty(node.parent, checker); + if (!t) return undefined; + + switch (node.kind) { + case SyntaxKind.PropertySignature: + return checker.getTypeOfPropertyOfContextualType(t, node.symbol.escapedName); + case SyntaxKind.IntersectionType: + case SyntaxKind.TypeLiteral: + case SyntaxKind.UnionType: + return t; + } + } + // TODO: GH#19856 Would like to return `node is Node & { parent: (ClassElement | TypeElement) & { parent: ObjectTypeDeclaration } }` but then compilation takes > 10 minutes function isFromObjectTypeDeclaration(node: Node): boolean { return node.parent && isClassOrTypeElement(node.parent) && isObjectTypeDeclaration(node.parent.parent); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter1.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter1.ts new file mode 100644 index 00000000000..a6aa20b4944 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter1.ts @@ -0,0 +1,23 @@ +/// + +////interface Foo { +//// one: string; +//// two: number; +//// 333: symbol; +//// '4four': boolean; +//// '5 five': object; +//// number: string; +//// Object: number; +////} +//// +////interface Bar { +//// foo: T; +////} +//// +////var foobar: Bar<{/**/ + +verify.completions({ + marker: "", + exact: ["one", "two", "\"333\"", "\"4four\"", "\"5 five\"", "number", "Object"], + isNewIdentifierLocation: true +}); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter2.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter2.ts new file mode 100644 index 00000000000..4f52c9e0758 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter2.ts @@ -0,0 +1,14 @@ +/// + +////interface Foo { +//// one: string; +//// two: number; +////} +//// +////interface Bar { +//// foo: T; +////} +//// +////var foobar: Bar<{ on/**/ + +verify.completions({ marker: "", exact: ["one", "two"], isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter3.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter3.ts new file mode 100644 index 00000000000..9fb408e48c3 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter3.ts @@ -0,0 +1,14 @@ +/// + +////interface Foo { +//// one: string; +//// two: number; +////} +//// +////interface Bar { +//// foo: T; +////} +//// +////var foobar: Bar<{ one: string, /**/ + +verify.completions({ marker: "", exact: "two", isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter4.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter4.ts new file mode 100644 index 00000000000..20cd01b079d --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter4.ts @@ -0,0 +1,14 @@ +/// + +////interface Foo { +//// one: string; +//// two: number; +////} +//// +////interface Bar { +//// foo: T; +////} +//// +////var foobar: Bar<{ one: string } & {/**/ + +verify.completions({ marker: "", exact: "two", isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter5.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter5.ts new file mode 100644 index 00000000000..df89739c972 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter5.ts @@ -0,0 +1,14 @@ +/// + +////interface Foo { +//// one: string; +//// two: number; +////} +//// +////interface Bar { +//// foo: T; +////} +//// +////var foobar: Bar<{ prop1: string } & {/**/ + +verify.completions({ marker: "", exact: ["one", "two"], isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter6.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter6.ts new file mode 100644 index 00000000000..405882fe750 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter6.ts @@ -0,0 +1,14 @@ +/// + +////interface Foo { +//// one: string; +//// two: number; +////} +//// +////interface Bar { +//// foo: T; +////} +//// +////var foobar: Bar<{ one: string } | {/**/ + +verify.completions({ marker: "", exact: ["one", "two"], isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter7.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter7.ts new file mode 100644 index 00000000000..00691e4e6f8 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter7.ts @@ -0,0 +1,17 @@ +/// + +////interface Foo { +//// one: string; +//// two: { +//// three: number; +//// } +////} +//// +////interface Bar { +//// foo: T; +////} +//// +////var foobar: Bar<{ +//// two: {/**/ + +verify.completions({ marker: "", exact: "three", isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter8.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter8.ts new file mode 100644 index 00000000000..7220a862512 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter8.ts @@ -0,0 +1,33 @@ +/// + +////interface Foo { +//// one: string; +//// two: { +//// three: { +//// four: number; +//// five: string; +//// } +//// } +////} +//// +////interface Bar { +//// foo: T; +////} +//// +////var foobar: Bar<{ +//// two: { +//// three: { +//// five: string, +//// /*4*/ +//// }, +//// /*0*/ +//// }, +//// /*1*/ +////}>; + +verify.completions( + { marker: "4", exact: "four", isNewIdentifierLocation: true }, + { marker: "0", exact: [], isNewIdentifierLocation: true }, + { marker: "1", exact: "one", isNewIdentifierLocation: true }, +); + diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter9.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter9.ts new file mode 100644 index 00000000000..13f75f74c8a --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter9.ts @@ -0,0 +1,25 @@ +/// + +////interface Foo { +//// one: string; +//// two: { +//// three: { +//// four: number; +//// five: string; +//// } +//// } +////} +//// +////interface Bar { +//// foo: T; +////} +//// +////var foobar: Bar<{ +//// two: { +//// three: { five:/*g*/ } & {/*4*/}, +//// } +////}>; + +verify.completions({ marker: "g", includes: ["Foo", "Bar", ...completion.globalTypes] }); +verify.completions({ marker: "4", exact: "four", isNewIdentifierLocation: true }); +