Skip to content

Commit 8384018

Browse files
authored
fix(26141): show completions for string parenthesized types (#39697)
1 parent ea842c4 commit 8384018

File tree

3 files changed

+154
-11
lines changed

3 files changed

+154
-11
lines changed

src/services/stringCompletions.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,32 +108,41 @@ namespace ts.Completions.StringCompletions {
108108
}
109109
type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: readonly PathCompletion[] } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes;
110110
function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined {
111-
const { parent } = node;
111+
const parent = walkUpParentheses(node.parent);
112112
switch (parent.kind) {
113-
case SyntaxKind.LiteralType:
114-
switch (parent.parent.kind) {
115-
case SyntaxKind.TypeReference:
116-
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent as LiteralTypeNode)), isNewIdentifier: false };
113+
case SyntaxKind.LiteralType: {
114+
const grandParent = walkUpParentheses(parent.parent);
115+
switch (grandParent.kind) {
116+
case SyntaxKind.TypeReference: {
117+
const typeReference = grandParent as TypeReferenceNode;
118+
const typeArgument = findAncestor(parent, n => n.parent === typeReference) as LiteralTypeNode;
119+
if (typeArgument) {
120+
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false };
121+
}
122+
return undefined;
123+
}
117124
case SyntaxKind.IndexedAccessType:
118125
// Get all apparent property names
119126
// i.e. interface Foo {
120127
// foo: string;
121128
// bar: string;
122129
// }
123130
// let x: Foo["/*completion position*/"]
124-
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((parent.parent as IndexedAccessTypeNode).objectType));
131+
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((grandParent as IndexedAccessTypeNode).objectType));
125132
case SyntaxKind.ImportType:
126133
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
127134
case SyntaxKind.UnionType: {
128-
if (!isTypeReferenceNode(parent.parent.parent)) return undefined;
129-
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(parent.parent as UnionTypeNode, parent as LiteralTypeNode);
130-
const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent.parent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value));
135+
if (!isTypeReferenceNode(grandParent.parent)) {
136+
return undefined;
137+
}
138+
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode);
139+
const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(grandParent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value));
131140
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false };
132141
}
133142
default:
134143
return undefined;
135144
}
136-
145+
}
137146
case SyntaxKind.PropertyAssignment:
138147
if (isObjectLiteralExpression(parent.parent) && (<PropertyAssignment>parent).name === node) {
139148
// Get quoted name of properties of the object literal expression
@@ -154,7 +163,7 @@ namespace ts.Completions.StringCompletions {
154163

155164
case SyntaxKind.ElementAccessExpression: {
156165
const { expression, argumentExpression } = parent as ElementAccessExpression;
157-
if (node === argumentExpression) {
166+
if (node === skipParentheses(argumentExpression)) {
158167
// Get all names of properties on the expression
159168
// i.e. interface A {
160169
// 'prop1': string
@@ -199,6 +208,17 @@ namespace ts.Completions.StringCompletions {
199208
}
200209
}
201210

211+
function walkUpParentheses(node: Node) {
212+
switch (node.kind) {
213+
case SyntaxKind.ParenthesizedType:
214+
return walkUpParenthesizedTypes(node);
215+
case SyntaxKind.ParenthesizedExpression:
216+
return walkUpParenthesizedExpressions(node);
217+
default:
218+
return node;
219+
}
220+
}
221+
202222
function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): readonly string[] {
203223
return mapDefined(union.types, type =>
204224
type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////const foo = {
4+
//// a: 1,
5+
//// b: 1,
6+
//// c: 1
7+
////}
8+
////const a = foo["[|/*1*/|]"];
9+
////const b = foo[("[|/*2*/|]")];
10+
////const c = foo[(("[|/*3*/|]"))];
11+
12+
const [r1, r2, r3] = test.ranges();
13+
verify.completions(
14+
{
15+
marker: "1",
16+
exact: [
17+
{ name: "a", replacementSpan: r1 },
18+
{ name: "b", replacementSpan: r1 },
19+
{ name: "c", replacementSpan: r1 }
20+
]
21+
},
22+
{
23+
marker: "2",
24+
exact: [
25+
{ name: "a", replacementSpan: r2 },
26+
{ name: "b", replacementSpan: r2 },
27+
{ name: "c", replacementSpan: r2 }
28+
]
29+
},
30+
{
31+
marker: "3",
32+
exact: [
33+
{ name: "a", replacementSpan: r3 },
34+
{ name: "b", replacementSpan: r3 },
35+
{ name: "c", replacementSpan: r3 }
36+
]
37+
}
38+
);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////type T1 = "a" | "b" | "c";
4+
////type T2<T extends T1> = {};
5+
////
6+
////type T3 = T2<"[|/*1*/|]">;
7+
////type T4 = T2<("[|/*2*/|]")>;
8+
////type T5 = T2<(("[|/*3*/|]"))>;
9+
////type T6 = T2<((("[|/*4*/|]")))>;
10+
////
11+
////type T7<P extends T1, K extends T1> = {};
12+
////type T8 = T7<"a", ((("[|/*5*/|]")))>;
13+
////
14+
////interface Foo {
15+
//// a: number;
16+
//// b: number;
17+
////}
18+
////const a: Foo["[|/*6*/|]"];
19+
////const b: Foo[("[|/*7*/|]")];
20+
////const b: Foo[(("[|/*8*/|]"))];
21+
22+
const [r1, r2, r3, r4, r5, r6, r7, r8] = test.ranges();
23+
verify.completions(
24+
{
25+
marker: "1",
26+
exact: [
27+
{ name: "a", replacementSpan: r1 },
28+
{ name: "b", replacementSpan: r1 },
29+
{ name: "c", replacementSpan: r1 }
30+
]
31+
},
32+
{
33+
marker: "2",
34+
exact: [
35+
{ name: "a", replacementSpan: r2 },
36+
{ name: "b", replacementSpan: r2 },
37+
{ name: "c", replacementSpan: r2 }
38+
]
39+
},
40+
{
41+
marker: "3",
42+
exact: [
43+
{ name: "a", replacementSpan: r3 },
44+
{ name: "b", replacementSpan: r3 },
45+
{ name: "c", replacementSpan: r3 }
46+
]
47+
},
48+
{
49+
marker: "4",
50+
exact: [
51+
{ name: "a", replacementSpan: r4 },
52+
{ name: "b", replacementSpan: r4 },
53+
{ name: "c", replacementSpan: r4 }
54+
]
55+
},
56+
{
57+
marker: "5",
58+
exact: [
59+
{ name: "a", replacementSpan: r5 },
60+
{ name: "b", replacementSpan: r5 },
61+
{ name: "c", replacementSpan: r5 }
62+
]
63+
},
64+
{
65+
marker: "6",
66+
exact: [
67+
{ name: "a", replacementSpan: r6 },
68+
{ name: "b", replacementSpan: r6 }
69+
]
70+
},
71+
{
72+
marker: "7",
73+
exact: [
74+
{ name: "a", replacementSpan: r7 },
75+
{ name: "b", replacementSpan: r7 }
76+
]
77+
},
78+
{
79+
marker: "8",
80+
exact: [
81+
{ name: "a", replacementSpan: r8 },
82+
{ name: "b", replacementSpan: r8 }
83+
]
84+
}
85+
);

0 commit comments

Comments
 (0)