Provide string completions within unions in indexed access types (#53225)

This commit is contained in:
Mateusz Burzyński 2023-03-29 19:07:34 +02:00 committed by GitHub
parent d105b6a994
commit ae2b4afdcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 49 additions and 34 deletions

View File

@ -83,7 +83,6 @@ import {
isString,
isStringLiteral,
isStringLiteralLike,
isTypeReferenceNode,
isUrl,
JsxAttribute,
LanguageServiceHost,
@ -342,40 +341,10 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
switch (parent.kind) {
case SyntaxKind.LiteralType: {
const grandParent = walkUpParentheses(parent.parent);
switch (grandParent.kind) {
case SyntaxKind.ExpressionWithTypeArguments:
case SyntaxKind.TypeReference: {
const typeArgument = findAncestor(parent, n => n.parent === grandParent) as LiteralTypeNode;
if (typeArgument) {
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false };
}
return undefined;
}
case SyntaxKind.IndexedAccessType:
// Get all apparent property names
// i.e. interface Foo {
// foo: string;
// bar: string;
// }
// let x: Foo["/*completion position*/"]
const { indexType, objectType } = grandParent as IndexedAccessTypeNode;
if (!rangeContainsPosition(indexType, position)) {
return undefined;
}
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType));
case SyntaxKind.ImportType:
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) };
case SyntaxKind.UnionType: {
if (!isTypeReferenceNode(grandParent.parent)) {
return undefined;
}
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode);
const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(grandParent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value));
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false };
}
default:
return undefined;
if (grandParent.kind === SyntaxKind.ImportType) {
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) };
}
return fromUnionableLiteralType(grandParent);
}
case SyntaxKind.PropertyAssignment:
if (isObjectLiteralExpression(parent.parent) && (parent as PropertyAssignment).name === node) {
@ -443,6 +412,44 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
return fromContextualType() || fromContextualType(ContextFlags.None);
}
function fromUnionableLiteralType(grandParent: Node): StringLiteralCompletionsFromTypes | StringLiteralCompletionsFromProperties | undefined {
switch (grandParent.kind) {
case SyntaxKind.ExpressionWithTypeArguments:
case SyntaxKind.TypeReference: {
const typeArgument = findAncestor(parent, n => n.parent === grandParent) as LiteralTypeNode;
if (typeArgument) {
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false };
}
return undefined;
}
case SyntaxKind.IndexedAccessType:
// Get all apparent property names
// i.e. interface Foo {
// foo: string;
// bar: string;
// }
// let x: Foo["/*completion position*/"]
const { indexType, objectType } = grandParent as IndexedAccessTypeNode;
if (!rangeContainsPosition(indexType, position)) {
return undefined;
}
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType));
case SyntaxKind.UnionType: {
const result = fromUnionableLiteralType(walkUpParentheses(grandParent.parent));
if (!result) {
return undefined;
}
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode);
if (result.kind === StringLiteralCompletionKind.Properties) {
return { kind: StringLiteralCompletionKind.Properties, symbols: result.symbols.filter(sym => !contains(alreadyUsedTypes, sym.name)), hasIndexSignature: result.hasIndexSignature };
}
return { kind: StringLiteralCompletionKind.Types, types: result.types.filter(t => !contains(alreadyUsedTypes, t.value)), isNewIdentifier: false };
}
default:
return undefined;
}
}
function fromContextualType(contextFlags: ContextFlags = ContextFlags.Completions): StringLiteralCompletionsFromTypes | undefined {
// Get completion for string literal from string literal type
// i.e. var x: "hi" | "hello" = "/*completion position*/"

View File

@ -0,0 +1,8 @@
/// <reference path="fourslash.ts" />
//// type Foo = { a: string; b: number; c: boolean; };
//// type A = Foo["/*1*/"];
//// type AorB = Foo["a" | "/*2*/"];
verify.completions({ marker: ["1"], exact: ["a", "b", "c"] });
verify.completions({ marker: ["2"], exact: ["b", "c"] });