Skip to content

Commit ae2b4af

Browse files
authored
Provide string completions within unions in indexed access types (microsoft#53225)
1 parent d105b6a commit ae2b4af

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
@@ -83,7 +83,6 @@ import {
8383
isString,
8484
isStringLiteral,
8585
isStringLiteralLike,
86-
isTypeReferenceNode,
8786
isUrl,
8887
JsxAttribute,
8988
LanguageServiceHost,
@@ -342,40 +341,10 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
342341
switch (parent.kind) {
343342
case SyntaxKind.LiteralType: {
344343
const grandParent = walkUpParentheses(parent.parent);
345-
switch (grandParent.kind) {
346-
case SyntaxKind.ExpressionWithTypeArguments:
347-
case SyntaxKind.TypeReference: {
348-
const typeArgument = findAncestor(parent, n => n.parent === grandParent) as LiteralTypeNode;
349-
if (typeArgument) {
350-
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false };
351-
}
352-
return undefined;
353-
}
354-
case SyntaxKind.IndexedAccessType:
355-
// Get all apparent property names
356-
// i.e. interface Foo {
357-
// foo: string;
358-
// bar: string;
359-
// }
360-
// let x: Foo["/*completion position*/"]
361-
const { indexType, objectType } = grandParent as IndexedAccessTypeNode;
362-
if (!rangeContainsPosition(indexType, position)) {
363-
return undefined;
364-
}
365-
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType));
366-
case SyntaxKind.ImportType:
367-
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) };
368-
case SyntaxKind.UnionType: {
369-
if (!isTypeReferenceNode(grandParent.parent)) {
370-
return undefined;
371-
}
372-
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode);
373-
const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(grandParent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value));
374-
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false };
375-
}
376-
default:
377-
return undefined;
344+
if (grandParent.kind === SyntaxKind.ImportType) {
345+
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) };
378346
}
347+
return fromUnionableLiteralType(grandParent);
379348
}
380349
case SyntaxKind.PropertyAssignment:
381350
if (isObjectLiteralExpression(parent.parent) && (parent as PropertyAssignment).name === node) {
@@ -443,6 +412,44 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
443412
return fromContextualType() || fromContextualType(ContextFlags.None);
444413
}
445414

415+
function fromUnionableLiteralType(grandParent: Node): StringLiteralCompletionsFromTypes | StringLiteralCompletionsFromProperties | undefined {
416+
switch (grandParent.kind) {
417+
case SyntaxKind.ExpressionWithTypeArguments:
418+
case SyntaxKind.TypeReference: {
419+
const typeArgument = findAncestor(parent, n => n.parent === grandParent) as LiteralTypeNode;
420+
if (typeArgument) {
421+
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false };
422+
}
423+
return undefined;
424+
}
425+
case SyntaxKind.IndexedAccessType:
426+
// Get all apparent property names
427+
// i.e. interface Foo {
428+
// foo: string;
429+
// bar: string;
430+
// }
431+
// let x: Foo["/*completion position*/"]
432+
const { indexType, objectType } = grandParent as IndexedAccessTypeNode;
433+
if (!rangeContainsPosition(indexType, position)) {
434+
return undefined;
435+
}
436+
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType));
437+
case SyntaxKind.UnionType: {
438+
const result = fromUnionableLiteralType(walkUpParentheses(grandParent.parent));
439+
if (!result) {
440+
return undefined;
441+
}
442+
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode);
443+
if (result.kind === StringLiteralCompletionKind.Properties) {
444+
return { kind: StringLiteralCompletionKind.Properties, symbols: result.symbols.filter(sym => !contains(alreadyUsedTypes, sym.name)), hasIndexSignature: result.hasIndexSignature };
445+
}
446+
return { kind: StringLiteralCompletionKind.Types, types: result.types.filter(t => !contains(alreadyUsedTypes, t.value)), isNewIdentifier: false };
447+
}
448+
default:
449+
return undefined;
450+
}
451+
}
452+
446453
function fromContextualType(contextFlags: ContextFlags = ContextFlags.Completions): StringLiteralCompletionsFromTypes | undefined {
447454
// Get completion for string literal from string literal type
448455
// 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)