fix(26141): show completions for string parenthesized types (#39697)

This commit is contained in:
Alexander T
2020-09-04 22:15:16 +03:00
committed by GitHub
parent ea842c411e
commit 8384018e68
3 changed files with 154 additions and 11 deletions

View File

@@ -108,12 +108,19 @@ namespace ts.Completions.StringCompletions {
}
type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: readonly PathCompletion[] } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes;
function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined {
const { parent } = node;
const parent = walkUpParentheses(node.parent);
switch (parent.kind) {
case SyntaxKind.LiteralType:
switch (parent.parent.kind) {
case SyntaxKind.TypeReference:
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent as LiteralTypeNode)), isNewIdentifier: false };
case SyntaxKind.LiteralType: {
const grandParent = walkUpParentheses(parent.parent);
switch (grandParent.kind) {
case SyntaxKind.TypeReference: {
const typeReference = grandParent as TypeReferenceNode;
const typeArgument = findAncestor(parent, n => n.parent === typeReference) 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 {
@@ -121,19 +128,21 @@ namespace ts.Completions.StringCompletions {
// bar: string;
// }
// let x: Foo["/*completion position*/"]
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((parent.parent as IndexedAccessTypeNode).objectType));
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((grandParent as IndexedAccessTypeNode).objectType));
case SyntaxKind.ImportType:
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
case SyntaxKind.UnionType: {
if (!isTypeReferenceNode(parent.parent.parent)) return undefined;
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(parent.parent as UnionTypeNode, parent as LiteralTypeNode);
const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent.parent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value));
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;
}
}
case SyntaxKind.PropertyAssignment:
if (isObjectLiteralExpression(parent.parent) && (<PropertyAssignment>parent).name === node) {
// Get quoted name of properties of the object literal expression
@@ -154,7 +163,7 @@ namespace ts.Completions.StringCompletions {
case SyntaxKind.ElementAccessExpression: {
const { expression, argumentExpression } = parent as ElementAccessExpression;
if (node === argumentExpression) {
if (node === skipParentheses(argumentExpression)) {
// Get all names of properties on the expression
// i.e. interface A {
// 'prop1': string
@@ -199,6 +208,17 @@ namespace ts.Completions.StringCompletions {
}
}
function walkUpParentheses(node: Node) {
switch (node.kind) {
case SyntaxKind.ParenthesizedType:
return walkUpParenthesizedTypes(node);
case SyntaxKind.ParenthesizedExpression:
return walkUpParenthesizedExpressions(node);
default:
return node;
}
}
function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): readonly string[] {
return mapDefined(union.types, type =>
type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined);

View File

@@ -0,0 +1,38 @@
/// <reference path='fourslash.ts'/>
////const foo = {
//// a: 1,
//// b: 1,
//// c: 1
////}
////const a = foo["[|/*1*/|]"];
////const b = foo[("[|/*2*/|]")];
////const c = foo[(("[|/*3*/|]"))];
const [r1, r2, r3] = test.ranges();
verify.completions(
{
marker: "1",
exact: [
{ name: "a", replacementSpan: r1 },
{ name: "b", replacementSpan: r1 },
{ name: "c", replacementSpan: r1 }
]
},
{
marker: "2",
exact: [
{ name: "a", replacementSpan: r2 },
{ name: "b", replacementSpan: r2 },
{ name: "c", replacementSpan: r2 }
]
},
{
marker: "3",
exact: [
{ name: "a", replacementSpan: r3 },
{ name: "b", replacementSpan: r3 },
{ name: "c", replacementSpan: r3 }
]
}
);

View File

@@ -0,0 +1,85 @@
/// <reference path='fourslash.ts'/>
////type T1 = "a" | "b" | "c";
////type T2<T extends T1> = {};
////
////type T3 = T2<"[|/*1*/|]">;
////type T4 = T2<("[|/*2*/|]")>;
////type T5 = T2<(("[|/*3*/|]"))>;
////type T6 = T2<((("[|/*4*/|]")))>;
////
////type T7<P extends T1, K extends T1> = {};
////type T8 = T7<"a", ((("[|/*5*/|]")))>;
////
////interface Foo {
//// a: number;
//// b: number;
////}
////const a: Foo["[|/*6*/|]"];
////const b: Foo[("[|/*7*/|]")];
////const b: Foo[(("[|/*8*/|]"))];
const [r1, r2, r3, r4, r5, r6, r7, r8] = test.ranges();
verify.completions(
{
marker: "1",
exact: [
{ name: "a", replacementSpan: r1 },
{ name: "b", replacementSpan: r1 },
{ name: "c", replacementSpan: r1 }
]
},
{
marker: "2",
exact: [
{ name: "a", replacementSpan: r2 },
{ name: "b", replacementSpan: r2 },
{ name: "c", replacementSpan: r2 }
]
},
{
marker: "3",
exact: [
{ name: "a", replacementSpan: r3 },
{ name: "b", replacementSpan: r3 },
{ name: "c", replacementSpan: r3 }
]
},
{
marker: "4",
exact: [
{ name: "a", replacementSpan: r4 },
{ name: "b", replacementSpan: r4 },
{ name: "c", replacementSpan: r4 }
]
},
{
marker: "5",
exact: [
{ name: "a", replacementSpan: r5 },
{ name: "b", replacementSpan: r5 },
{ name: "c", replacementSpan: r5 }
]
},
{
marker: "6",
exact: [
{ name: "a", replacementSpan: r6 },
{ name: "b", replacementSpan: r6 }
]
},
{
marker: "7",
exact: [
{ name: "a", replacementSpan: r7 },
{ name: "b", replacementSpan: r7 }
]
},
{
marker: "8",
exact: [
{ name: "a", replacementSpan: r8 },
{ name: "b", replacementSpan: r8 }
]
}
);