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
This commit is contained in:
Tiago Tristao 2021-04-20 18:24:17 +01:00 committed by GitHub
parent f2705294ac
commit 3d24b85f9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 241 additions and 1 deletions

View File

@ -547,6 +547,7 @@ namespace ts {
return node && getContextualTypeForJsxAttribute(node);
},
isContextSensitive,
getTypeOfPropertyOfContextualType,
getFullyQualifiedName,
getResolvedSignature: (node, candidatesOutArray, argumentCount) =>
getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal),

View File

@ -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.

View File

@ -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);

View File

@ -0,0 +1,23 @@
/// <reference path="fourslash.ts" />
////interface Foo {
//// one: string;
//// two: number;
//// 333: symbol;
//// '4four': boolean;
//// '5 five': object;
//// number: string;
//// Object: number;
////}
////
////interface Bar<T extends Foo> {
//// foo: T;
////}
////
////var foobar: Bar<{/**/
verify.completions({
marker: "",
exact: ["one", "two", "\"333\"", "\"4four\"", "\"5 five\"", "number", "Object"],
isNewIdentifierLocation: true
});

View File

@ -0,0 +1,14 @@
/// <reference path="fourslash.ts" />
////interface Foo {
//// one: string;
//// two: number;
////}
////
////interface Bar<T extends Foo> {
//// foo: T;
////}
////
////var foobar: Bar<{ on/**/
verify.completions({ marker: "", exact: ["one", "two"], isNewIdentifierLocation: true });

View File

@ -0,0 +1,14 @@
/// <reference path="fourslash.ts" />
////interface Foo {
//// one: string;
//// two: number;
////}
////
////interface Bar<T extends Foo> {
//// foo: T;
////}
////
////var foobar: Bar<{ one: string, /**/
verify.completions({ marker: "", exact: "two", isNewIdentifierLocation: true });

View File

@ -0,0 +1,14 @@
/// <reference path="fourslash.ts" />
////interface Foo {
//// one: string;
//// two: number;
////}
////
////interface Bar<T extends Foo> {
//// foo: T;
////}
////
////var foobar: Bar<{ one: string } & {/**/
verify.completions({ marker: "", exact: "two", isNewIdentifierLocation: true });

View File

@ -0,0 +1,14 @@
/// <reference path="fourslash.ts" />
////interface Foo {
//// one: string;
//// two: number;
////}
////
////interface Bar<T extends Foo> {
//// foo: T;
////}
////
////var foobar: Bar<{ prop1: string } & {/**/
verify.completions({ marker: "", exact: ["one", "two"], isNewIdentifierLocation: true });

View File

@ -0,0 +1,14 @@
/// <reference path="fourslash.ts" />
////interface Foo {
//// one: string;
//// two: number;
////}
////
////interface Bar<T extends Foo> {
//// foo: T;
////}
////
////var foobar: Bar<{ one: string } | {/**/
verify.completions({ marker: "", exact: ["one", "two"], isNewIdentifierLocation: true });

View File

@ -0,0 +1,17 @@
/// <reference path="fourslash.ts" />
////interface Foo {
//// one: string;
//// two: {
//// three: number;
//// }
////}
////
////interface Bar<T extends Foo> {
//// foo: T;
////}
////
////var foobar: Bar<{
//// two: {/**/
verify.completions({ marker: "", exact: "three", isNewIdentifierLocation: true });

View File

@ -0,0 +1,33 @@
/// <reference path="fourslash.ts" />
////interface Foo {
//// one: string;
//// two: {
//// three: {
//// four: number;
//// five: string;
//// }
//// }
////}
////
////interface Bar<T extends Foo> {
//// 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 },
);

View File

@ -0,0 +1,25 @@
/// <reference path="fourslash.ts" />
////interface Foo {
//// one: string;
//// two: {
//// three: {
//// four: number;
//// five: string;
//// }
//// }
////}
////
////interface Bar<T extends Foo> {
//// 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 });