Skip to content

Commit 0717061

Browse files
committed
fix(26141): show completions for string parenthesized types
1 parent 191f4f6 commit 0717061

File tree

3 files changed

+163
-25
lines changed

3 files changed

+163
-25
lines changed

src/services/stringCompletions.ts

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -107,32 +107,11 @@ namespace ts.Completions.StringCompletions {
107107
readonly isNewIdentifier: boolean;
108108
}
109109
type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: readonly PathCompletion[] } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes;
110-
function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined {
110+
function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike | ParenthesizedExpression, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined {
111111
const { parent } = node;
112112
switch (parent.kind) {
113113
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 };
117-
case SyntaxKind.IndexedAccessType:
118-
// Get all apparent property names
119-
// i.e. interface Foo {
120-
// foo: string;
121-
// bar: string;
122-
// }
123-
// let x: Foo["/*completion position*/"]
124-
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((parent.parent as IndexedAccessTypeNode).objectType));
125-
case SyntaxKind.ImportType:
126-
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
127-
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));
131-
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false };
132-
}
133-
default:
134-
return undefined;
135-
}
114+
return isStringLiteralLike(node) ? getCompletionEntriesFromLiteral(sourceFile, node, typeChecker, compilerOptions, host) : undefined;
136115

137116
case SyntaxKind.PropertyAssignment:
138117
if (isObjectLiteralExpression(parent.parent) && (<PropertyAssignment>parent).name === node) {
@@ -186,8 +165,9 @@ namespace ts.Completions.StringCompletions {
186165
// import x = require("/*completion position*/");
187166
// var y = require("/*completion position*/");
188167
// export * from "/*completion position*/";
189-
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
190-
168+
return isStringLiteralLike(node) ? { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) } : undefined;
169+
case SyntaxKind.ParenthesizedExpression:
170+
return getStringLiteralCompletionEntries(sourceFile, parent as ParenthesizedExpression, position, typeChecker, compilerOptions, host);
191171
default:
192172
return fromContextualType();
193173
}
@@ -199,6 +179,41 @@ namespace ts.Completions.StringCompletions {
199179
}
200180
}
201181

182+
function getCompletionEntriesFromLiteral(sourceFile: SourceFile, node: StringLiteralLike | ParenthesizedTypeNode, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined {
183+
const parent = node.parent;
184+
switch (parent.parent.kind) {
185+
case SyntaxKind.TypeReference:
186+
if (isLiteralTypeNode(parent) || isParenthesizedTypeNode(parent)) {
187+
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent)), isNewIdentifier: false };
188+
}
189+
return undefined;
190+
case SyntaxKind.IndexedAccessType:
191+
// Get all apparent property names
192+
// i.e. interface Foo {
193+
// foo: string;
194+
// bar: string;
195+
// }
196+
// let x: Foo["/*completion position*/"]
197+
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((parent.parent as IndexedAccessTypeNode).objectType));
198+
case SyntaxKind.ImportType:
199+
return isStringLiteralLike(node) ? { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) } : undefined;
200+
case SyntaxKind.UnionType: {
201+
if (!isTypeReferenceNode(parent.parent.parent)) {
202+
return undefined;
203+
}
204+
const unionType = parent.parent as UnionTypeNode;
205+
const alreadyUsedTypes = isLiteralTypeNode(parent) ? getAlreadyUsedTypesInStringLiteralUnion(unionType, parent) : [];
206+
const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(unionType)).filter(t => !contains(alreadyUsedTypes, t.value));
207+
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false };
208+
}
209+
case SyntaxKind.ParenthesizedType:
210+
return getCompletionEntriesFromLiteral(sourceFile, parent as ParenthesizedTypeNode, typeChecker, compilerOptions, host);
211+
default:
212+
return undefined;
213+
}
214+
215+
}
216+
202217
function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): readonly string[] {
203218
return mapDefined(union.types, type =>
204219
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)