Skip to content

Commit 149c034

Browse files
committed
Provide string completions within unions in indexed access types
1 parent 32d2253 commit 149c034

File tree

2 files changed

+49
-34
lines changed

2 files changed

+49
-34
lines changed

src/services/stringCompletions.ts

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ import {
8282
isString,
8383
isStringLiteral,
8484
isStringLiteralLike,
85-
isTypeReferenceNode,
8685
isUrl,
8786
JsxAttribute,
8887
LanguageServiceHost,
@@ -341,40 +340,10 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
341340
switch (parent.kind) {
342341
case SyntaxKind.LiteralType: {
343342
const grandParent = walkUpParentheses(parent.parent);
344-
switch (grandParent.kind) {
345-
case SyntaxKind.ExpressionWithTypeArguments:
346-
case SyntaxKind.TypeReference: {
347-
const typeArgument = findAncestor(parent, n => n.parent === grandParent) as LiteralTypeNode;
348-
if (typeArgument) {
349-
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false };
350-
}
351-
return undefined;
352-
}
353-
case SyntaxKind.IndexedAccessType:
354-
// Get all apparent property names
355-
// i.e. interface Foo {
356-
// foo: string;
357-
// bar: string;
358-
// }
359-
// let x: Foo["/*completion position*/"]
360-
const { indexType, objectType } = grandParent as IndexedAccessTypeNode;
361-
if (!rangeContainsPosition(indexType, position)) {
362-
return undefined;
363-
}
364-
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType));
365-
case SyntaxKind.ImportType:
366-
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) };
367-
case SyntaxKind.UnionType: {
368-
if (!isTypeReferenceNode(grandParent.parent)) {
369-
return undefined;
370-
}
371-
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode);
372-
const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(grandParent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value));
373-
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false };
374-
}
375-
default:
376-
return undefined;
343+
if (grandParent.kind === SyntaxKind.ImportType) {
344+
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) };
377345
}
346+
return fromUnionableLiteralType(grandParent);
378347
}
379348
case SyntaxKind.PropertyAssignment:
380349
if (isObjectLiteralExpression(parent.parent) && (parent as PropertyAssignment).name === node) {
@@ -442,6 +411,44 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
442411
return fromContextualType();
443412
}
444413

414+
function fromUnionableLiteralType(grandParent: Node): StringLiteralCompletionsFromTypes | StringLiteralCompletionsFromProperties | undefined {
415+
switch (grandParent.kind) {
416+
case SyntaxKind.ExpressionWithTypeArguments:
417+
case SyntaxKind.TypeReference: {
418+
const typeArgument = findAncestor(parent, n => n.parent === grandParent) as LiteralTypeNode;
419+
if (typeArgument) {
420+
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false };
421+
}
422+
return undefined;
423+
}
424+
case SyntaxKind.IndexedAccessType:
425+
// Get all apparent property names
426+
// i.e. interface Foo {
427+
// foo: string;
428+
// bar: string;
429+
// }
430+
// let x: Foo["/*completion position*/"]
431+
const { indexType, objectType } = grandParent as IndexedAccessTypeNode;
432+
if (!rangeContainsPosition(indexType, position)) {
433+
return undefined;
434+
}
435+
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType));
436+
case SyntaxKind.UnionType: {
437+
const result = fromUnionableLiteralType(walkUpParentheses(grandParent.parent));
438+
if (!result) {
439+
return undefined;
440+
}
441+
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode);
442+
if (result.kind === StringLiteralCompletionKind.Properties) {
443+
return { kind: StringLiteralCompletionKind.Properties, symbols: result.symbols.filter(sym => !contains(alreadyUsedTypes, sym.name)), hasIndexSignature: result.hasIndexSignature };
444+
}
445+
return { kind: StringLiteralCompletionKind.Types, types: result.types.filter(t => !contains(alreadyUsedTypes, t.value)), isNewIdentifier: false };
446+
}
447+
default:
448+
return undefined;
449+
}
450+
}
451+
445452
function fromContextualType(contextFlags: ContextFlags = ContextFlags.Completions): StringLiteralCompletionsFromTypes | undefined {
446453
// Get completion for string literal from string literal type
447454
// i.e. var x: "hi" | "hello" = "/*completion position*/"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
//// type Foo = { a: string; b: number; c: boolean; };
4+
//// type A = Foo["/*1*/"];
5+
//// type AorB = Foo["a" | "/*2*/"];
6+
7+
verify.completions({ marker: ["1"], exact: ["a", "b", "c"] });
8+
verify.completions({ marker: ["2"], exact: ["b", "c"] });

0 commit comments

Comments
 (0)