diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index c3473b6251eae..6b618860c8727 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -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) && (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); diff --git a/tests/cases/fourslash/completionListStringParenthesizedExpression.ts b/tests/cases/fourslash/completionListStringParenthesizedExpression.ts new file mode 100644 index 0000000000000..89a0230638927 --- /dev/null +++ b/tests/cases/fourslash/completionListStringParenthesizedExpression.ts @@ -0,0 +1,38 @@ +/// + +////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 } + ] + } +); diff --git a/tests/cases/fourslash/completionListStringParenthesizedType.ts b/tests/cases/fourslash/completionListStringParenthesizedType.ts new file mode 100644 index 0000000000000..a23f379379215 --- /dev/null +++ b/tests/cases/fourslash/completionListStringParenthesizedType.ts @@ -0,0 +1,85 @@ +/// + +////type T1 = "a" | "b" | "c"; +////type T2 = {}; +//// +////type T3 = T2<"[|/*1*/|]">; +////type T4 = T2<("[|/*2*/|]")>; +////type T5 = T2<(("[|/*3*/|]"))>; +////type T6 = T2<((("[|/*4*/|]")))>; +//// +////type T7

= {}; +////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 } + ] + } +);