Skip to content

Commit 3d24b85

Browse files
Completion list for type literals in type arguments (microsoft#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
1 parent f270529 commit 3d24b85

12 files changed

+241
-1
lines changed

src/compiler/checker.ts

+1
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ namespace ts {
547547
return node && getContextualTypeForJsxAttribute(node);
548548
},
549549
isContextSensitive,
550+
getTypeOfPropertyOfContextualType,
550551
getFullyQualifiedName,
551552
getResolvedSignature: (node, candidatesOutArray, argumentCount) =>
552553
getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal),

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4144,6 +4144,7 @@ namespace ts {
41444144
/* @internal */ getContextualTypeForArgumentAtIndex(call: CallLikeExpression, argIndex: number): Type | undefined;
41454145
/* @internal */ getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined;
41464146
/* @internal */ isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean;
4147+
/* @internal */ getTypeOfPropertyOfContextualType(type: Type, name: __String): Type | undefined;
41474148

41484149
/**
41494150
* returns unknownSignature in the case of an error.

src/services/completions.ts

+71-1
Original file line numberDiff line numberDiff line change
@@ -1510,7 +1510,8 @@ namespace ts.Completions {
15101510
}
15111511

15121512
function tryGetGlobalSymbols(): boolean {
1513-
const result: GlobalsSearch = tryGetObjectLikeCompletionSymbols()
1513+
const result: GlobalsSearch = tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols()
1514+
|| tryGetObjectLikeCompletionSymbols()
15141515
|| tryGetImportCompletionSymbols()
15151516
|| tryGetImportOrExportClauseCompletionSymbols()
15161517
|| tryGetLocalNamedExportCompletionSymbols()
@@ -1913,6 +1914,32 @@ namespace ts.Completions {
19131914
position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken)));
19141915
}
19151916

1917+
function tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols(): GlobalsSearch | undefined {
1918+
const typeLiteralNode = tryGetTypeLiteralNode(contextToken);
1919+
if (!typeLiteralNode) return GlobalsSearch.Continue;
1920+
1921+
const intersectionTypeNode = isIntersectionTypeNode(typeLiteralNode.parent) ? typeLiteralNode.parent : undefined;
1922+
const containerTypeNode = intersectionTypeNode || typeLiteralNode;
1923+
1924+
const containerExpectedType = getConstraintOfTypeArgumentProperty(containerTypeNode, typeChecker);
1925+
if (!containerExpectedType) return GlobalsSearch.Continue;
1926+
1927+
const containerActualType = typeChecker.getTypeFromTypeNode(containerTypeNode);
1928+
1929+
const members = getPropertiesForCompletion(containerExpectedType, typeChecker);
1930+
const existingMembers = getPropertiesForCompletion(containerActualType, typeChecker);
1931+
1932+
const existingMemberEscapedNames: Set<__String> = new Set();
1933+
existingMembers.forEach(s => existingMemberEscapedNames.add(s.escapedName));
1934+
1935+
symbols = filter(members, s => !existingMemberEscapedNames.has(s.escapedName));
1936+
1937+
completionKind = CompletionKind.ObjectPropertyDeclaration;
1938+
isNewIdentifierLocation = true;
1939+
1940+
return GlobalsSearch.Success;
1941+
}
1942+
19161943
/**
19171944
* Aggregates relevant symbols for completion in object literals and object binding patterns.
19181945
* Relevant symbols are stored in the captured 'symbols' variable.
@@ -2859,6 +2886,49 @@ namespace ts.Completions {
28592886
}
28602887
}
28612888

2889+
function tryGetTypeLiteralNode(node: Node): TypeLiteralNode | undefined {
2890+
if (!node) return undefined;
2891+
2892+
const parent = node.parent;
2893+
2894+
switch (node.kind) {
2895+
case SyntaxKind.OpenBraceToken:
2896+
if (isTypeLiteralNode(parent)) {
2897+
return parent;
2898+
}
2899+
break;
2900+
case SyntaxKind.SemicolonToken:
2901+
case SyntaxKind.CommaToken:
2902+
case SyntaxKind.Identifier:
2903+
if (parent.kind === SyntaxKind.PropertySignature && isTypeLiteralNode(parent.parent)) {
2904+
return parent.parent;
2905+
}
2906+
break;
2907+
}
2908+
2909+
return undefined;
2910+
}
2911+
2912+
function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): Type | undefined {
2913+
if (!node) return undefined;
2914+
2915+
if (isTypeNode(node) && isTypeReferenceType(node.parent)) {
2916+
return checker.getTypeArgumentConstraint(node);
2917+
}
2918+
2919+
const t = getConstraintOfTypeArgumentProperty(node.parent, checker);
2920+
if (!t) return undefined;
2921+
2922+
switch (node.kind) {
2923+
case SyntaxKind.PropertySignature:
2924+
return checker.getTypeOfPropertyOfContextualType(t, node.symbol.escapedName);
2925+
case SyntaxKind.IntersectionType:
2926+
case SyntaxKind.TypeLiteral:
2927+
case SyntaxKind.UnionType:
2928+
return t;
2929+
}
2930+
}
2931+
28622932
// TODO: GH#19856 Would like to return `node is Node & { parent: (ClassElement | TypeElement) & { parent: ObjectTypeDeclaration } }` but then compilation takes > 10 minutes
28632933
function isFromObjectTypeDeclaration(node: Node): boolean {
28642934
return node.parent && isClassOrTypeElement(node.parent) && isObjectTypeDeclaration(node.parent.parent);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface Foo {
4+
//// one: string;
5+
//// two: number;
6+
//// 333: symbol;
7+
//// '4four': boolean;
8+
//// '5 five': object;
9+
//// number: string;
10+
//// Object: number;
11+
////}
12+
////
13+
////interface Bar<T extends Foo> {
14+
//// foo: T;
15+
////}
16+
////
17+
////var foobar: Bar<{/**/
18+
19+
verify.completions({
20+
marker: "",
21+
exact: ["one", "two", "\"333\"", "\"4four\"", "\"5 five\"", "number", "Object"],
22+
isNewIdentifierLocation: true
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface Foo {
4+
//// one: string;
5+
//// two: number;
6+
////}
7+
////
8+
////interface Bar<T extends Foo> {
9+
//// foo: T;
10+
////}
11+
////
12+
////var foobar: Bar<{ on/**/
13+
14+
verify.completions({ marker: "", exact: ["one", "two"], isNewIdentifierLocation: true });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface Foo {
4+
//// one: string;
5+
//// two: number;
6+
////}
7+
////
8+
////interface Bar<T extends Foo> {
9+
//// foo: T;
10+
////}
11+
////
12+
////var foobar: Bar<{ one: string, /**/
13+
14+
verify.completions({ marker: "", exact: "two", isNewIdentifierLocation: true });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface Foo {
4+
//// one: string;
5+
//// two: number;
6+
////}
7+
////
8+
////interface Bar<T extends Foo> {
9+
//// foo: T;
10+
////}
11+
////
12+
////var foobar: Bar<{ one: string } & {/**/
13+
14+
verify.completions({ marker: "", exact: "two", isNewIdentifierLocation: true });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface Foo {
4+
//// one: string;
5+
//// two: number;
6+
////}
7+
////
8+
////interface Bar<T extends Foo> {
9+
//// foo: T;
10+
////}
11+
////
12+
////var foobar: Bar<{ prop1: string } & {/**/
13+
14+
verify.completions({ marker: "", exact: ["one", "two"], isNewIdentifierLocation: true });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface Foo {
4+
//// one: string;
5+
//// two: number;
6+
////}
7+
////
8+
////interface Bar<T extends Foo> {
9+
//// foo: T;
10+
////}
11+
////
12+
////var foobar: Bar<{ one: string } | {/**/
13+
14+
verify.completions({ marker: "", exact: ["one", "two"], isNewIdentifierLocation: true });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface Foo {
4+
//// one: string;
5+
//// two: {
6+
//// three: number;
7+
//// }
8+
////}
9+
////
10+
////interface Bar<T extends Foo> {
11+
//// foo: T;
12+
////}
13+
////
14+
////var foobar: Bar<{
15+
//// two: {/**/
16+
17+
verify.completions({ marker: "", exact: "three", isNewIdentifierLocation: true });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface Foo {
4+
//// one: string;
5+
//// two: {
6+
//// three: {
7+
//// four: number;
8+
//// five: string;
9+
//// }
10+
//// }
11+
////}
12+
////
13+
////interface Bar<T extends Foo> {
14+
//// foo: T;
15+
////}
16+
////
17+
////var foobar: Bar<{
18+
//// two: {
19+
//// three: {
20+
//// five: string,
21+
//// /*4*/
22+
//// },
23+
//// /*0*/
24+
//// },
25+
//// /*1*/
26+
////}>;
27+
28+
verify.completions(
29+
{ marker: "4", exact: "four", isNewIdentifierLocation: true },
30+
{ marker: "0", exact: [], isNewIdentifierLocation: true },
31+
{ marker: "1", exact: "one", isNewIdentifierLocation: true },
32+
);
33+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface Foo {
4+
//// one: string;
5+
//// two: {
6+
//// three: {
7+
//// four: number;
8+
//// five: string;
9+
//// }
10+
//// }
11+
////}
12+
////
13+
////interface Bar<T extends Foo> {
14+
//// foo: T;
15+
////}
16+
////
17+
////var foobar: Bar<{
18+
//// two: {
19+
//// three: { five:/*g*/ } & {/*4*/},
20+
//// }
21+
////}>;
22+
23+
verify.completions({ marker: "g", includes: ["Foo", "Bar", ...completion.globalTypes] });
24+
verify.completions({ marker: "4", exact: "four", isNewIdentifierLocation: true });
25+

0 commit comments

Comments
 (0)