From 4b9d6f8a6b10ccf8711c6d7dbdfba365eb54b924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Mar=C5=A1=C3=A1lek?= Date: Wed, 21 Aug 2024 21:55:04 +0200 Subject: [PATCH 1/7] init --- tests/cases/fourslash/completionsTemplateLiterals.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/cases/fourslash/completionsTemplateLiterals.ts diff --git a/tests/cases/fourslash/completionsTemplateLiterals.ts b/tests/cases/fourslash/completionsTemplateLiterals.ts new file mode 100644 index 0000000000000..c69f7b1466377 --- /dev/null +++ b/tests/cases/fourslash/completionsTemplateLiterals.ts @@ -0,0 +1,10 @@ +/// + +////type T = `${"prefix1"|"prefix2"}${string}`; +////let x: T = "/**/; + + +verify.completions({ + marker: "", + unsorted: ["prefix1", "prefix2"] +}); From 8c9de42e8abd6edfbdebf7e0c66302e08f97b393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Mar=C5=A1=C3=A1lek?= Date: Tue, 27 Aug 2024 22:21:38 +0200 Subject: [PATCH 2/7] initial implementation --- src/services/stringCompletions.ts | 24 ++++++++++--------- .../fourslash/completionsTemplateLiterals.ts | 11 +++++++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 667cdf24ca8aa..ac0cb5d5e7044 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -141,6 +141,7 @@ import { supportedTSImplementationExtensions, Symbol, SyntaxKind, + TemplateLiteralType, textPart, TextSpan, tryAndIgnoreErrors, @@ -276,7 +277,7 @@ function convertStringLiteralCompletions( ? CharacterCodes.singleQuote : CharacterCodes.doubleQuote; const entries = completion.types.map(type => ({ - name: escapeString(type.value, quoteChar), + name: escapeString(type.isStringLiteral() ? type.value : type.texts[0], quoteChar), // TODO support matching in the middle kindModifiers: ScriptElementKindModifier.none, kind: ScriptElementKind.string, sortText: SortText.LocationPriority, @@ -315,7 +316,7 @@ function stringLiteralCompletionDetails(name: string, location: Node, completion return match && createCompletionDetailsForSymbol(match, match.name, checker, sourceFile, location, cancellationToken); } case StringLiteralCompletionKind.Types: - return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.string, [textPart(name)]) : undefined; + return find(completion.types, t => t.isStringLiteral() && t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.string, [textPart(name)]) : undefined; default: return Debug.assertNever(completion); } @@ -378,9 +379,10 @@ interface StringLiteralCompletionsFromProperties { readonly symbols: readonly Symbol[]; readonly hasIndexSignature: boolean; } +type StringLiteralLikeType = StringLiteralType | TemplateLiteralType interface StringLiteralCompletionsFromTypes { readonly kind: StringLiteralCompletionKind.Types; - readonly types: readonly StringLiteralType[]; + readonly types: readonly (StringLiteralLikeType)[]; readonly isNewIdentifier: boolean; } type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths; readonly paths: readonly PathCompletion[]; } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes; @@ -456,7 +458,7 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL if (!contextualTypes) { return; } - const literals = contextualTypes.types.filter(literal => !tracker.hasValue(literal.value)); + const literals = contextualTypes.types.filter(literal => literal.isStringLiteral() && !tracker.hasValue(literal.value)); return { kind: StringLiteralCompletionKind.Types, types: literals, isNewIdentifier: false }; case SyntaxKind.ImportSpecifier: @@ -486,7 +488,7 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL 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 { kind: StringLiteralCompletionKind.Types, types: getStringLiteralLikeTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false }; } return undefined; } @@ -511,7 +513,7 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL 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 }; + return { kind: StringLiteralCompletionKind.Types, types: result.types.filter(t => t.isStringLiteral() && !contains(alreadyUsedTypes, t.value)), isNewIdentifier: false }; } default: return undefined; @@ -521,7 +523,7 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL 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*/" - const types = getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker, contextFlags)); + const types = getStringLiteralLikeTypes(getContextualTypeFromParent(node, typeChecker, contextFlags)); if (!types.length) { return; } @@ -559,7 +561,7 @@ function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: } } isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String); - return getStringLiteralTypes(type, uniques); + return getStringLiteralLikeTypes(type, uniques); }); return length(types) ? { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier } : undefined; } @@ -591,11 +593,11 @@ function stringLiteralCompletionsForObjectLiteral(checker: TypeChecker, objectLi }; } -function getStringLiteralTypes(type: Type | undefined, uniques = new Map()): readonly StringLiteralType[] { +function getStringLiteralLikeTypes(type: Type | undefined, uniques = new Map()): readonly StringLiteralLikeType[] { if (!type) return emptyArray; type = skipConstraint(type); - return type.isUnion() ? flatMap(type.types, t => getStringLiteralTypes(t, uniques)) : - type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) ? [type] : emptyArray; + return type.isUnion() ? flatMap(type.types, t => getStringLiteralLikeTypes(t, uniques)) : + type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) || (type.flags & TypeFlags.TemplateLiteral) ? [type as StringLiteralLikeType] : emptyArray; } interface NameAndKind { diff --git a/tests/cases/fourslash/completionsTemplateLiterals.ts b/tests/cases/fourslash/completionsTemplateLiterals.ts index c69f7b1466377..f607b5fa1b6e0 100644 --- a/tests/cases/fourslash/completionsTemplateLiterals.ts +++ b/tests/cases/fourslash/completionsTemplateLiterals.ts @@ -1,10 +1,17 @@ /// ////type T = `${"prefix1"|"prefix2"}${string}`; -////let x: T = "/**/; +////let x: T = "pr/*1*/; +////function f(x:`${"prefix1"|"prefix2"}${string}`); +////f("pr/*2*/"); + +verify.completions({ + marker: "1", + unsorted: ["prefix1", "prefix2"] +}); verify.completions({ - marker: "", + marker: "2", unsorted: ["prefix1", "prefix2"] }); From c162be661c8f0210cb36ae995fbfcb0b95df277c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Mar=C5=A1=C3=A1lek?= Date: Wed, 28 Aug 2024 22:24:37 +0200 Subject: [PATCH 3/7] add getStringLiteralCompletionFromTemplateLiteralTypeAndTextOfNode --- src/services/stringCompletions.ts | 47 +++++++++++---- .../fourslash/completionsTemplateLiterals.ts | 57 ++++++++++++++++--- 2 files changed, 87 insertions(+), 17 deletions(-) diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index ac0cb5d5e7044..93d290dc63c3a 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -42,6 +42,7 @@ import { ensureTrailingDirectorySeparator, equateStringsCaseSensitive, escapeString, + every, Extension, fileExtensionIsOneOf, filter, @@ -104,6 +105,7 @@ import { length, LiteralExpression, LiteralTypeNode, + map, mapDefined, MapLike, moduleExportNameTextEscaped, @@ -271,19 +273,27 @@ function convertStringLiteralCompletions( }; } case StringLiteralCompletionKind.Types: { + const textOfNode = getTextOfNode(contextToken) const quoteChar = contextToken.kind === SyntaxKind.NoSubstitutionTemplateLiteral ? CharacterCodes.backtick - : startsWith(getTextOfNode(contextToken), "'") + : startsWith(textOfNode, "'") ? CharacterCodes.singleQuote : CharacterCodes.doubleQuote; - const entries = completion.types.map(type => ({ - name: escapeString(type.isStringLiteral() ? type.value : type.texts[0], quoteChar), // TODO support matching in the middle - kindModifiers: ScriptElementKindModifier.none, - kind: ScriptElementKind.string, - sortText: SortText.LocationPriority, - replacementSpan: getReplacementSpanForContextToken(contextToken, position), - commitCharacters: [], - })); + let tokenTextContent = textOfNode + if (tokenTextContent) tokenTextContent = tokenTextContent.slice(1) + if (tokenTextContent.charCodeAt(tokenTextContent.length - 1) === quoteChar) tokenTextContent = tokenTextContent.slice(0, tokenTextContent.length - 1) + const uniques = new Map() + const entries = mapDefined(completion.types, type => { + const name = type.isStringLiteral() ? escapeString(type.value, quoteChar) : getStringLiteralCompletionFromTemplateLiteralTypeAndTextOfNode(type, tokenTextContent, quoteChar); + return addToSeen(uniques, name) ? { + name, + kindModifiers: ScriptElementKindModifier.none, + kind: ScriptElementKind.string, + sortText: SortText.LocationPriority, + replacementSpan: getReplacementSpanForContextToken(contextToken, position), + commitCharacters: [], + } : undefined + }); return { isGlobalCompletion: false, isMemberCompletion: false, @@ -298,6 +308,22 @@ function convertStringLiteralCompletions( } } +function getStringLiteralCompletionFromTemplateLiteralTypeAndTextOfNode(type: TemplateLiteralType, tokenTextContent: string, quoteChar: CharacterCodes.backtick | CharacterCodes.singleQuote | CharacterCodes.doubleQuote) { + let firstUnusedCharacterIndex = 0 + let textIndex = 0 + for (;textIndex < type.texts.length - 1 && firstUnusedCharacterIndex < tokenTextContent.length; textIndex++) { + const escapedText = escapeString(type.texts[textIndex], quoteChar) + const match = tokenTextContent.indexOf(escapedText, firstUnusedCharacterIndex) + if (match < 0) { + // The current text was not matched so we suggest appending it or replacing if we aren't passed the first ${string}. + return textIndex === 0 ? escapedText : tokenTextContent + escapedText + } + firstUnusedCharacterIndex = match + escapedText.length + } + // We can always suggest appending the last text, since if it is already present, it can be part of the last ${string}. + return tokenTextContent + escapeString(type.texts[type.texts.length - 1], quoteChar) +} + /** @internal */ export function getStringLiteralCompletionDetails(name: string, sourceFile: SourceFile, position: number, contextToken: Node | undefined, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken, preferences: UserPreferences) { if (!contextToken || !isStringLiteralLike(contextToken)) return undefined; @@ -597,7 +623,8 @@ function getStringLiteralLikeTypes(type: Type | undefined, uniques = new Map getStringLiteralLikeTypes(t, uniques)) : - type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) || (type.flags & TypeFlags.TemplateLiteral) ? [type as StringLiteralLikeType] : emptyArray; + type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) + || (type.flags & TypeFlags.TemplateLiteral && every((type as TemplateLiteralType).types, t => !!(TypeFlags.String & t.flags))) ? [type as StringLiteralLikeType] : emptyArray; } interface NameAndKind { diff --git a/tests/cases/fourslash/completionsTemplateLiterals.ts b/tests/cases/fourslash/completionsTemplateLiterals.ts index f607b5fa1b6e0..ef1ec6dd03bb9 100644 --- a/tests/cases/fourslash/completionsTemplateLiterals.ts +++ b/tests/cases/fourslash/completionsTemplateLiterals.ts @@ -1,17 +1,60 @@ /// -////type T = `${"prefix1"|"prefix2"}${string}`; -////let x: T = "pr/*1*/; +////type T = `${"prefix1"|"prefix2"}${string}${"middle1"|"middle2"}${string}${"suffix1"|"suffix2"}`; +////let x: T = /*1*/ +////x = "/*2a*/ +////x = "/*2b*/" +////x = "pre/*3a*/ +////x = "pre/*3b*/" +////x = "prefix1_/*4a*/ +////x = "prefix1_/*4b*/" +////x = "prefix1_middle1_/*5a*/ +////x = "prefix1_middle1_/*5b*/" +////x = "prefix1_middle1_suffix1/*6a*/ +////x = "prefix1_middle1_suffix1/*6b*/" -////function f(x:`${"prefix1"|"prefix2"}${string}`); -////f("pr/*2*/"); - -verify.completions({ +/*verify.completions({ marker: "1", + unsorted: ['"prefix1"', '"prefix2"'] +});*/ +/*verify.completions({ + marker: "2a", + unsorted: ["prefix1", "prefix2"] +}); +verify.completions({ + marker: "2b", unsorted: ["prefix1", "prefix2"] }); verify.completions({ - marker: "2", + marker: "3a", unsorted: ["prefix1", "prefix2"] }); +verify.completions({ + marker: "3b", + unsorted: ["prefix1", "prefix2"] +});*/ +verify.completions({ + marker: "4a", + unsorted: ["prefix1_middle1", "prefix1_middle2"] +}); +verify.completions({ + marker: "4b", + unsorted: ["prefix1_middle1", "prefix1_middle2"] +}); +verify.completions({ + marker: "5a", + unsorted: ["prefix1_middle1_suffix1", "prefix1_middle1_suffix2"] +}); +verify.completions({ + marker: "5b", + unsorted: ["prefix1_middle1_suffix1", "prefix1_middle1_suffix2"] +}); +verify.completions({ + marker: "6a", + unsorted: ["prefix1_middle1_suffix1", "prefix1_middle1_suffix1suffix1"] +}); +verify.completions({ + marker: "6b", + unsorted: ["prefix1_middle1_suffix1", "prefix1_middle1_suffix1suffix2"] +}); From 8d4927f9a4a78ab6af21571583f64ef891daccc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Mar=C5=A1=C3=A1lek?= Date: Thu, 29 Aug 2024 08:22:43 +0200 Subject: [PATCH 4/7] add tests --- .../fourslash/completionsTemplateLiterals.ts | 56 ++++--------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/tests/cases/fourslash/completionsTemplateLiterals.ts b/tests/cases/fourslash/completionsTemplateLiterals.ts index ef1ec6dd03bb9..6ba0c3d5329a6 100644 --- a/tests/cases/fourslash/completionsTemplateLiterals.ts +++ b/tests/cases/fourslash/completionsTemplateLiterals.ts @@ -1,7 +1,8 @@ /// -////type T = `${"prefix1"|"prefix2"}${string}${"middle1"|"middle2"}${string}${"suffix1"|"suffix2"}`; -////let x: T = /*1*/ +// @Filename: file1.ts +////declare let x: `${"prefix1"|"prefix2"}${string}${"middle1"|"middle2"}${string}${"suffix1"|"suffix2"}`; +//// //x = /*1*/ ////x = "/*2a*/ ////x = "/*2b*/" ////x = "pre/*3a*/ @@ -12,49 +13,12 @@ ////x = "prefix1_middle1_/*5b*/" ////x = "prefix1_middle1_suffix1/*6a*/ ////x = "prefix1_middle1_suffix1/*6b*/" +//// +////declare let y: `"'${string}'"`; +////y = "/*7a*/ +////y = "/*7b*/" +////y = '/*8a*/ +////y = '/*8b*/' -/*verify.completions({ - marker: "1", - unsorted: ['"prefix1"', '"prefix2"'] -});*/ -/*verify.completions({ - marker: "2a", - unsorted: ["prefix1", "prefix2"] -}); -verify.completions({ - marker: "2b", - unsorted: ["prefix1", "prefix2"] -}); -verify.completions({ - marker: "3a", - unsorted: ["prefix1", "prefix2"] -}); -verify.completions({ - marker: "3b", - unsorted: ["prefix1", "prefix2"] -});*/ -verify.completions({ - marker: "4a", - unsorted: ["prefix1_middle1", "prefix1_middle2"] -}); -verify.completions({ - marker: "4b", - unsorted: ["prefix1_middle1", "prefix1_middle2"] -}); -verify.completions({ - marker: "5a", - unsorted: ["prefix1_middle1_suffix1", "prefix1_middle1_suffix2"] -}); -verify.completions({ - marker: "5b", - unsorted: ["prefix1_middle1_suffix1", "prefix1_middle1_suffix2"] -}); -verify.completions({ - marker: "6a", - unsorted: ["prefix1_middle1_suffix1", "prefix1_middle1_suffix1suffix1"] -}); -verify.completions({ - marker: "6b", - unsorted: ["prefix1_middle1_suffix1", "prefix1_middle1_suffix1suffix2"] -}); +verify.baselineCompletions() From 9a2ce9bb5587f36c724138f2f60c235d37ae88dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Mar=C5=A1=C3=A1lek?= Date: Thu, 29 Aug 2024 10:08:13 +0200 Subject: [PATCH 5/7] add tests --- src/services/stringCompletions.ts | 32 +- .../completionsTemplateLiterals.baseline | 1049 +++++++++++++++++ .../fourslash/completionsTemplateLiterals.ts | 15 +- 3 files changed, 1076 insertions(+), 20 deletions(-) create mode 100644 tests/baselines/reference/completionsTemplateLiterals.baseline diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 93d290dc63c3a..dc13a5b8af2ad 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -273,16 +273,16 @@ function convertStringLiteralCompletions( }; } case StringLiteralCompletionKind.Types: { - const textOfNode = getTextOfNode(contextToken) + const textOfNode = getTextOfNode(contextToken); const quoteChar = contextToken.kind === SyntaxKind.NoSubstitutionTemplateLiteral ? CharacterCodes.backtick : startsWith(textOfNode, "'") ? CharacterCodes.singleQuote : CharacterCodes.doubleQuote; - let tokenTextContent = textOfNode - if (tokenTextContent) tokenTextContent = tokenTextContent.slice(1) - if (tokenTextContent.charCodeAt(tokenTextContent.length - 1) === quoteChar) tokenTextContent = tokenTextContent.slice(0, tokenTextContent.length - 1) - const uniques = new Map() + let tokenTextContent = textOfNode; + if (tokenTextContent) tokenTextContent = tokenTextContent.slice(1); + if (tokenTextContent.charCodeAt(tokenTextContent.length - 1) === quoteChar) tokenTextContent = tokenTextContent.slice(0, tokenTextContent.length - 1); + const uniques = new Map(); const entries = mapDefined(completion.types, type => { const name = type.isStringLiteral() ? escapeString(type.value, quoteChar) : getStringLiteralCompletionFromTemplateLiteralTypeAndTextOfNode(type, tokenTextContent, quoteChar); return addToSeen(uniques, name) ? { @@ -292,7 +292,7 @@ function convertStringLiteralCompletions( sortText: SortText.LocationPriority, replacementSpan: getReplacementSpanForContextToken(contextToken, position), commitCharacters: [], - } : undefined + } : undefined; }); return { isGlobalCompletion: false, @@ -309,19 +309,19 @@ function convertStringLiteralCompletions( } function getStringLiteralCompletionFromTemplateLiteralTypeAndTextOfNode(type: TemplateLiteralType, tokenTextContent: string, quoteChar: CharacterCodes.backtick | CharacterCodes.singleQuote | CharacterCodes.doubleQuote) { - let firstUnusedCharacterIndex = 0 - let textIndex = 0 - for (;textIndex < type.texts.length - 1 && firstUnusedCharacterIndex < tokenTextContent.length; textIndex++) { - const escapedText = escapeString(type.texts[textIndex], quoteChar) - const match = tokenTextContent.indexOf(escapedText, firstUnusedCharacterIndex) + let firstUnusedCharacterIndex = 0; + let textIndex = 0; + for (; textIndex < type.texts.length - 1; textIndex++) { + const escapedText = escapeString(type.texts[textIndex], quoteChar); + const match = tokenTextContent.indexOf(escapedText, firstUnusedCharacterIndex); if (match < 0) { // The current text was not matched so we suggest appending it or replacing if we aren't passed the first ${string}. - return textIndex === 0 ? escapedText : tokenTextContent + escapedText + return textIndex === 0 ? escapedText : tokenTextContent + escapedText; } - firstUnusedCharacterIndex = match + escapedText.length + firstUnusedCharacterIndex = match + escapedText.length; } // We can always suggest appending the last text, since if it is already present, it can be part of the last ${string}. - return tokenTextContent + escapeString(type.texts[type.texts.length - 1], quoteChar) + return tokenTextContent + escapeString(type.texts[textIndex], quoteChar); } /** @internal */ @@ -405,7 +405,7 @@ interface StringLiteralCompletionsFromProperties { readonly symbols: readonly Symbol[]; readonly hasIndexSignature: boolean; } -type StringLiteralLikeType = StringLiteralType | TemplateLiteralType +type StringLiteralLikeType = StringLiteralType | TemplateLiteralType; interface StringLiteralCompletionsFromTypes { readonly kind: StringLiteralCompletionKind.Types; readonly types: readonly (StringLiteralLikeType)[]; @@ -624,7 +624,7 @@ function getStringLiteralLikeTypes(type: Type | undefined, uniques = new Map getStringLiteralLikeTypes(t, uniques)) : type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) - || (type.flags & TypeFlags.TemplateLiteral && every((type as TemplateLiteralType).types, t => !!(TypeFlags.String & t.flags))) ? [type as StringLiteralLikeType] : emptyArray; + || (type.flags & TypeFlags.TemplateLiteral && every((type as TemplateLiteralType).types, t => !!(TypeFlags.String & t.flags))) ? [type as StringLiteralLikeType] : emptyArray; } interface NameAndKind { diff --git a/tests/baselines/reference/completionsTemplateLiterals.baseline b/tests/baselines/reference/completionsTemplateLiterals.baseline new file mode 100644 index 0000000000000..36b6ac8c00efd --- /dev/null +++ b/tests/baselines/reference/completionsTemplateLiterals.baseline @@ -0,0 +1,1049 @@ +// === Completions === +=== /tests/cases/fourslash/file1.ts === +// declare let x: `${"prefix1"|"prefix2"}${string}${"middle1"|"middle2"}${string}${"suffix1"|"suffix2"}`; +// x = " +// ^ +// | ---------------------------------------------------------------------- +// | (string) prefix1 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// x = "" +// ^ +// | ---------------------------------------------------------------------- +// | (string) prefix1 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// x = "wrong +// ^^^^^ +// | ---------------------------------------------------------------------- +// | (string) prefix1 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// x = "wrong" +// ^^^^^ +// | ---------------------------------------------------------------------- +// | (string) prefix1 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// x = "pre +// ^^^ +// | ---------------------------------------------------------------------- +// | (string) prefix1 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// x = "pre" +// ^^^ +// | ---------------------------------------------------------------------- +// | (string) prefix1 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// x = "prefix1_ +// ^^^^^^^^ +// | ---------------------------------------------------------------------- +// | (string) prefix1_middle1 +// | (string) prefix1_middle2 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// x = "prefix1_" +// ^^^^^^^^ +// | ---------------------------------------------------------------------- +// | (string) prefix1_middle1 +// | (string) prefix1_middle2 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// x = "prefix1_middle1_ +// ^^^^^^^^^^^^^^^^ +// | ---------------------------------------------------------------------- +// | (string) prefix1_middle1_suffix1 +// | (string) prefix1_middle1_suffix2 +// | (string) prefix1_middle1_middle2 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// x = "prefix1_middle1_" +// ^^^^^^^^^^^^^^^^ +// | ---------------------------------------------------------------------- +// | (string) prefix1_middle1_suffix1 +// | (string) prefix1_middle1_suffix2 +// | (string) prefix1_middle1_middle2 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// x = "prefix1_middle1_suffix1 +// ^^^^^^^^^^^^^^^^^^^^^^^ +// | ---------------------------------------------------------------------- +// | (string) prefix1_middle1_suffix1suffix1 +// | (string) prefix1_middle1_suffix1suffix2 +// | (string) prefix1_middle1_suffix1middle2 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// x = "prefix1_middle1_suffix1" +// ^^^^^^^^^^^^^^^^^^^^^^^ +// | ---------------------------------------------------------------------- +// | (string) prefix1_middle1_suffix1suffix1 +// | (string) prefix1_middle1_suffix1suffix2 +// | (string) prefix1_middle1_suffix1middle2 +// | (string) prefix2 +// | ---------------------------------------------------------------------- +// +// declare let y: `"'${string}'"`; +// y = " +// ^ +// | ---------------------------------------------------------------------- +// | (string) \"' +// | ---------------------------------------------------------------------- +// y = "" +// ^ +// | ---------------------------------------------------------------------- +// | (string) \"' +// | ---------------------------------------------------------------------- +// y = ' +// ^ +// | ---------------------------------------------------------------------- +// | (string) "\' +// | ---------------------------------------------------------------------- +// y = '' +// ^ +// | ---------------------------------------------------------------------- +// | (string) "\' +// | ---------------------------------------------------------------------- +// y = '"\'\\\"\\ +// ^^^^^^^^^ +// | ---------------------------------------------------------------------- +// | (string) "\'\\\"\\\'" +// | ---------------------------------------------------------------------- +// +// declare let z: `${string}suffix`; +// z = " +// ^ +// | ---------------------------------------------------------------------- +// | (string) suffix +// | ---------------------------------------------------------------------- +// z = "" +// ^ +// | ---------------------------------------------------------------------- +// | (string) suffix +// | ---------------------------------------------------------------------- +// z = ' +// ^ +// | ---------------------------------------------------------------------- +// | (string) suffix +// | ---------------------------------------------------------------------- +// z = '' +// ^ +// | ---------------------------------------------------------------------- +// | (string) suffix +// | ---------------------------------------------------------------------- + +[ + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 108, + "name": "1a" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "entries": [ + { + "name": "prefix1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 114, + "name": "1b" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 114, + "length": 0 + }, + "entries": [ + { + "name": "prefix1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 114, + "length": 0 + }, + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 114, + "length": 0 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 126, + "name": "2a" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 121, + "length": 5 + }, + "entries": [ + { + "name": "prefix1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 121, + "length": 5 + }, + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 121, + "length": 5 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 137, + "name": "2b" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 132, + "length": 5 + }, + "entries": [ + { + "name": "prefix1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 132, + "length": 5 + }, + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 132, + "length": 5 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 147, + "name": "3a" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 144, + "length": 3 + }, + "entries": [ + { + "name": "prefix1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 144, + "length": 3 + }, + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 144, + "length": 3 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 156, + "name": "3b" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 153, + "length": 3 + }, + "entries": [ + { + "name": "prefix1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 153, + "length": 3 + }, + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 153, + "length": 3 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 171, + "name": "4a" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 163, + "length": 8 + }, + "entries": [ + { + "name": "prefix1_middle1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 163, + "length": 8 + }, + "commitCharacters": [] + }, + { + "name": "prefix1_middle2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 163, + "length": 8 + }, + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 163, + "length": 8 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 185, + "name": "4b" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 177, + "length": 8 + }, + "entries": [ + { + "name": "prefix1_middle1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 177, + "length": 8 + }, + "commitCharacters": [] + }, + { + "name": "prefix1_middle2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 177, + "length": 8 + }, + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 177, + "length": 8 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 208, + "name": "5a" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 192, + "length": 16 + }, + "entries": [ + { + "name": "prefix1_middle1_suffix1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 192, + "length": 16 + }, + "commitCharacters": [] + }, + { + "name": "prefix1_middle1_suffix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 192, + "length": 16 + }, + "commitCharacters": [] + }, + { + "name": "prefix1_middle1_middle2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 192, + "length": 16 + }, + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 192, + "length": 16 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 230, + "name": "5b" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 214, + "length": 16 + }, + "entries": [ + { + "name": "prefix1_middle1_suffix1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 214, + "length": 16 + }, + "commitCharacters": [] + }, + { + "name": "prefix1_middle1_suffix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 214, + "length": 16 + }, + "commitCharacters": [] + }, + { + "name": "prefix1_middle1_middle2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 214, + "length": 16 + }, + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 214, + "length": 16 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 260, + "name": "6a" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 237, + "length": 23 + }, + "entries": [ + { + "name": "prefix1_middle1_suffix1suffix1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 237, + "length": 23 + }, + "commitCharacters": [] + }, + { + "name": "prefix1_middle1_suffix1suffix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 237, + "length": 23 + }, + "commitCharacters": [] + }, + { + "name": "prefix1_middle1_suffix1middle2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 237, + "length": 23 + }, + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 237, + "length": 23 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 289, + "name": "6b" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 266, + "length": 23 + }, + "entries": [ + { + "name": "prefix1_middle1_suffix1suffix1", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 266, + "length": 23 + }, + "commitCharacters": [] + }, + { + "name": "prefix1_middle1_suffix1suffix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 266, + "length": 23 + }, + "commitCharacters": [] + }, + { + "name": "prefix1_middle1_suffix1middle2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 266, + "length": 23 + }, + "commitCharacters": [] + }, + { + "name": "prefix2", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 266, + "length": 23 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 329, + "name": "7a" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "entries": [ + { + "name": "\\\"'", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 335, + "name": "7b" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 335, + "length": 0 + }, + "entries": [ + { + "name": "\\\"'", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 335, + "length": 0 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 342, + "name": "8a" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "entries": [ + { + "name": "\"\\'", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 348, + "name": "8b" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 348, + "length": 0 + }, + "entries": [ + { + "name": "\"\\'", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 348, + "length": 0 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 364, + "name": "9" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 355, + "length": 9 + }, + "entries": [ + { + "name": "\"\\'\\\\\\\"\\\\\\'\"", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 355, + "length": 9 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 405, + "name": "10a" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "entries": [ + { + "name": "suffix", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 411, + "name": "10b" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 411, + "length": 0 + }, + "entries": [ + { + "name": "suffix", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 411, + "length": 0 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 418, + "name": "11a" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "entries": [ + { + "name": "suffix", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 424, + "name": "11b" + }, + "item": { + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + "optionalReplacementSpan": { + "start": 424, + "length": 0 + }, + "entries": [ + { + "name": "suffix", + "kindModifiers": "", + "kind": "string", + "sortText": "11", + "replacementSpan": { + "start": 424, + "length": 0 + }, + "commitCharacters": [] + } + ], + "defaultCommitCharacters": [ + ".", + ",", + ";" + ] + } + } +] \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTemplateLiterals.ts b/tests/cases/fourslash/completionsTemplateLiterals.ts index 6ba0c3d5329a6..384938802231e 100644 --- a/tests/cases/fourslash/completionsTemplateLiterals.ts +++ b/tests/cases/fourslash/completionsTemplateLiterals.ts @@ -2,9 +2,10 @@ // @Filename: file1.ts ////declare let x: `${"prefix1"|"prefix2"}${string}${"middle1"|"middle2"}${string}${"suffix1"|"suffix2"}`; -//// //x = /*1*/ -////x = "/*2a*/ -////x = "/*2b*/" +////x = "/*1a*/ +////x = "/*1b*/" +////x = "wrong/*2a*/ +////x = "wrong/*2b*/" ////x = "pre/*3a*/ ////x = "pre/*3b*/" ////x = "prefix1_/*4a*/ @@ -19,6 +20,12 @@ ////y = "/*7b*/" ////y = '/*8a*/ ////y = '/*8b*/' - +////y = '"\'\\\"\\/*9*/ +//// +////declare let z: `${string}suffix`; +////z = "/*10a*/ +////z = "/*10b*/" +////z = '/*11a*/ +////z = '/*11b*/' verify.baselineCompletions() From da8c2a9de149db8902aa3f0fc9933c1fe1fcdf22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Mar=C5=A1=C3=A1lek?= Date: Thu, 29 Aug 2024 10:34:25 +0200 Subject: [PATCH 6/7] cleanup --- src/services/stringCompletions.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index dc13a5b8af2ad..0f5608d05d1bd 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -276,7 +276,7 @@ function convertStringLiteralCompletions( const textOfNode = getTextOfNode(contextToken); const quoteChar = contextToken.kind === SyntaxKind.NoSubstitutionTemplateLiteral ? CharacterCodes.backtick - : startsWith(textOfNode, "'") + : textOfNode.charCodeAt(0) === CharacterCodes.singleQuote ? CharacterCodes.singleQuote : CharacterCodes.doubleQuote; let tokenTextContent = textOfNode; @@ -284,7 +284,9 @@ function convertStringLiteralCompletions( if (tokenTextContent.charCodeAt(tokenTextContent.length - 1) === quoteChar) tokenTextContent = tokenTextContent.slice(0, tokenTextContent.length - 1); const uniques = new Map(); const entries = mapDefined(completion.types, type => { - const name = type.isStringLiteral() ? escapeString(type.value, quoteChar) : getStringLiteralCompletionFromTemplateLiteralTypeAndTextOfNode(type, tokenTextContent, quoteChar); + const name = type.isStringLiteral() + ? escapeString(type.value, quoteChar) + : getStringLiteralCompletionFromTemplateLiteralTypeAndTextOfNode(type, tokenTextContent, quoteChar); return addToSeen(uniques, name) ? { name, kindModifiers: ScriptElementKindModifier.none, From 0c89587b290b4e1fc02ea4e1eeec9720f6cacc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Mar=C5=A1=C3=A1lek?= Date: Thu, 29 Aug 2024 11:22:44 +0200 Subject: [PATCH 7/7] fix testcase by retrying with inference if there aren't any string literal completions --- src/services/stringCompletions.ts | 17 +++++++++++++---- .../completionsTemplateLiterals.baseline | 15 +++++++++++++++ .../fourslash/completionsTemplateLiterals.ts | 3 +++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 0f5608d05d1bd..61ec93800f07e 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -105,7 +105,6 @@ import { length, LiteralExpression, LiteralTypeNode, - map, mapDefined, MapLike, moduleExportNameTextEscaped, @@ -135,6 +134,7 @@ import { singleElementArray, skipConstraint, skipParentheses, + some, SourceFile, startsWith, StringLiteralLike, @@ -441,7 +441,11 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL // }); return stringLiteralCompletionsForObjectLiteral(typeChecker, parent.parent); } - return fromContextualType() || fromContextualType(ContextFlags.None); + const fromContextualTypeWithCompletionFlag = fromContextualType(); + if (fromContextualTypeWithCompletionFlag && some(fromContextualTypeWithCompletionFlag.types, t => t.isStringLiteral())) { + return fromContextualTypeWithCompletionFlag; + } + return fromContextualType(ContextFlags.None); case SyntaxKind.ElementAccessExpression: { const { expression, argumentExpression } = parent as ElementAccessExpression; @@ -506,8 +510,13 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL const uniques = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName)); return { kind: StringLiteralCompletionKind.Properties, symbols: uniques, hasIndexSignature: false }; - default: - return fromContextualType() || fromContextualType(ContextFlags.None); + default: { + const fromContextualTypeWithCompletionFlag = fromContextualType(); + if (fromContextualTypeWithCompletionFlag && some(fromContextualTypeWithCompletionFlag.types, t => t.isStringLiteral())) { + return fromContextualTypeWithCompletionFlag; + } + return fromContextualType(ContextFlags.None); + } } function fromUnionableLiteralType(grandParent: Node): StringLiteralCompletionsFromTypes | StringLiteralCompletionsFromProperties | undefined { diff --git a/tests/baselines/reference/completionsTemplateLiterals.baseline b/tests/baselines/reference/completionsTemplateLiterals.baseline index 36b6ac8c00efd..2ad9684b6a0d8 100644 --- a/tests/baselines/reference/completionsTemplateLiterals.baseline +++ b/tests/baselines/reference/completionsTemplateLiterals.baseline @@ -132,6 +132,13 @@ // | ---------------------------------------------------------------------- // | (string) suffix // | ---------------------------------------------------------------------- +// +// declare let w: `prefix${number}suffix`; +// w = "prefix +// ^ +// | ---------------------------------------------------------------------- +// | No completions at /*12*/. +// | ---------------------------------------------------------------------- [ { @@ -1045,5 +1052,13 @@ ";" ] } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/file1.ts", + "position": 478, + "name": "12" + }, + "item": {} } ] \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTemplateLiterals.ts b/tests/cases/fourslash/completionsTemplateLiterals.ts index 384938802231e..9306b04f39180 100644 --- a/tests/cases/fourslash/completionsTemplateLiterals.ts +++ b/tests/cases/fourslash/completionsTemplateLiterals.ts @@ -27,5 +27,8 @@ ////z = "/*10b*/" ////z = '/*11a*/ ////z = '/*11b*/' +//// +////declare let w: `prefix${number}suffix`; +////w = "prefix/*12*/ verify.baselineCompletions()