From 943060ca0af433e0dbca544c76da478f67ba4c7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 23:52:41 +0000 Subject: [PATCH 1/6] Initial plan From 4dfa0b55e1b3e53573f962c7634611daf74320ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 00:26:28 +0000 Subject: [PATCH 2/6] Port TypeScript PR #56182: Include source node inferences in string literal completions deeper in arguments Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/fourslash/_scripts/failingTests.txt | 2 -- ...AfterStringCompletionsInNestedCall_test.go | 2 +- internal/ls/string_completions.go | 27 ++++++++++++++----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/internal/fourslash/_scripts/failingTests.txt b/internal/fourslash/_scripts/failingTests.txt index 7265c6c74dd..8faafe38424 100644 --- a/internal/fourslash/_scripts/failingTests.txt +++ b/internal/fourslash/_scripts/failingTests.txt @@ -557,7 +557,6 @@ TestSignatureHelpCallExpressionJs TestSpaceAfterReturn TestStringCompletionsImportOrExportSpecifier TestStringCompletionsVsEscaping -TestStringLiteralCompletionsWithinInferredObjectWhenItsKeysAreUsedOutsideOfIt TestSuggestionOfUnusedVariableWithExternalModule TestSymbolCompletionLowerPriority TestSyntheticImportFromBabelGeneratedFile1 @@ -571,7 +570,6 @@ TestTsxQuickInfo5 TestTsxQuickInfo6 TestTsxQuickInfo7 TestTypeCheckAfterResolve -TestTypeErrorAfterStringCompletionsInNestedCall TestTypeOperatorNodeBuilding TestUnclosedStringLiteralAutoformating TestWhiteSpaceTrimming diff --git a/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go b/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go index b9f21b3672f..5492273d38b 100644 --- a/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go +++ b/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go @@ -49,8 +49,8 @@ createMachine({ }, Items: &fourslash.CompletionsExpectedItems{ Exact: []fourslash.CompletionsExpectedItem{ - "ALOHAx", "ALOHA", + "ALOHAx", "LUNCH_TIME", "MORNING", }, diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go index 76fdd624aac..a0e79693311 100644 --- a/internal/ls/string_completions.go +++ b/internal/ls/string_completions.go @@ -281,11 +281,13 @@ func (l *LanguageService) getStringLiteralCompletionEntries( fromProperties: stringLiteralCompletionsForObjectLiteral(typeChecker, parent.Parent), } } - result := fromContextualType(checker.ContextFlagsCompletions, node, typeChecker) - if result != nil { - return &stringLiteralCompletions{ - fromTypes: result, - } + if ast.FindAncestor(parent.Parent, ast.IsCallLikeExpression) != nil { + uniques := &collections.Set[string]{} + stringLiteralTypes := append( + getStringLiteralTypes(typeChecker.GetContextualType(node, checker.ContextFlagsNone), uniques, typeChecker), + getStringLiteralTypes(typeChecker.GetContextualType(node, checker.ContextFlagsCompletions), uniques, typeChecker)..., + ) + return toStringLiteralCompletionsFromTypes(stringLiteralTypes) } return &stringLiteralCompletions{ fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker), @@ -421,7 +423,10 @@ func (l *LanguageService) getStringLiteralCompletionEntries( func fromContextualType(contextFlags checker.ContextFlags, node *ast.Node, typeChecker *checker.Checker) *completionsFromTypes { // Get completion for string literal from string literal type // i.e. var x: "hi" | "hello" = "/*completion position*/" - types := getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker, contextFlags), nil, typeChecker) + return toCompletionsFromTypes(getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker, contextFlags), nil, typeChecker)) +} + +func toCompletionsFromTypes(types []*checker.StringLiteralType) *completionsFromTypes { if len(types) == 0 { return nil } @@ -431,6 +436,16 @@ func fromContextualType(contextFlags checker.ContextFlags, node *ast.Node, typeC } } +func toStringLiteralCompletionsFromTypes(types []*checker.StringLiteralType) *stringLiteralCompletions { + result := toCompletionsFromTypes(types) + if result == nil { + return nil + } + return &stringLiteralCompletions{ + fromTypes: result, + } +} + func fromUnionableLiteralType( grandparent *ast.Node, parent *ast.Node, From 93a0c28a6dc692d028431b8a4cc6253c0945eb51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 01:18:13 +0000 Subject: [PATCH 3/6] Fix source node inference sort ordering in string literal completions Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- ...AfterStringCompletionsInNestedCall_test.go | 2 +- internal/ls/string_completions.go | 44 ++++++++++--------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go b/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go index 5492273d38b..b9f21b3672f 100644 --- a/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go +++ b/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go @@ -49,8 +49,8 @@ createMachine({ }, Items: &fourslash.CompletionsExpectedItems{ Exact: []fourslash.CompletionsExpectedItem{ - "ALOHA", "ALOHAx", + "ALOHA", "LUNCH_TIME", "MORNING", }, diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go index a0e79693311..229d7acc362 100644 --- a/internal/ls/string_completions.go +++ b/internal/ls/string_completions.go @@ -28,8 +28,9 @@ import ( ) type completionsFromTypes struct { - types []*checker.StringLiteralType - isNewIdentifier bool + types []*checker.StringLiteralType + isNewIdentifier bool + numberOfInferences int // number of types at the beginning of 'types' from source node inference } type completionsFromProperties struct { @@ -146,14 +147,19 @@ func (l *LanguageService) convertStringLiteralCompletions( } else { quoteChar = printer.QuoteCharDoubleQuote } - items := core.Map(completion.types, func(t *checker.StringLiteralType) *lsproto.CompletionItem { + items := make([]*lsproto.CompletionItem, len(completion.types)) + for i, t := range completion.types { name := printer.EscapeString(t.AsLiteralType().Value().(string), quoteChar) - return l.createLSPCompletionItem( + sortText := SortTextLocationPriority + if i < completion.numberOfInferences { + sortText = SortTextLocalDeclarationPriority + } + items[i] = l.createLSPCompletionItem( ctx, name, "", /*insertText*/ "", /*filterText*/ - SortTextLocationPriority, + sortText, lsutil.ScriptElementKindString, collections.Set[lsutil.ScriptElementKindModifier]{}, l.getReplacementRangeForContextToken(file, contextToken, position), @@ -169,7 +175,7 @@ func (l *LanguageService) convertStringLiteralCompletions( nil, /*autoImportEntryData*/ nil, /*detail*/ ) - }) + } defaultCommitCharacters := getDefaultCommitCharacters(completion.isNewIdentifier) itemDefaults := l.setItemDefaults( ctx, @@ -283,11 +289,17 @@ func (l *LanguageService) getStringLiteralCompletionEntries( } if ast.FindAncestor(parent.Parent, ast.IsCallLikeExpression) != nil { uniques := &collections.Set[string]{} - stringLiteralTypes := append( - getStringLiteralTypes(typeChecker.GetContextualType(node, checker.ContextFlagsNone), uniques, typeChecker), - getStringLiteralTypes(typeChecker.GetContextualType(node, checker.ContextFlagsCompletions), uniques, typeChecker)..., - ) - return toStringLiteralCompletionsFromTypes(stringLiteralTypes) + noneTypes := getStringLiteralTypes(typeChecker.GetContextualType(node, checker.ContextFlagsNone), uniques, typeChecker) + completionsTypes := getStringLiteralTypes(typeChecker.GetContextualType(node, checker.ContextFlagsCompletions), uniques, typeChecker) + stringLiteralTypes := append(noneTypes, completionsTypes...) + result := toCompletionsFromTypes(stringLiteralTypes) + if result != nil { + result.numberOfInferences = len(noneTypes) + return &stringLiteralCompletions{ + fromTypes: result, + } + } + return nil } return &stringLiteralCompletions{ fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker), @@ -436,16 +448,6 @@ func toCompletionsFromTypes(types []*checker.StringLiteralType) *completionsFrom } } -func toStringLiteralCompletionsFromTypes(types []*checker.StringLiteralType) *stringLiteralCompletions { - result := toCompletionsFromTypes(types) - if result == nil { - return nil - } - return &stringLiteralCompletions{ - fromTypes: result, - } -} - func fromUnionableLiteralType( grandparent *ast.Node, parent *ast.Node, From a615107e16ad6e6d45bfa297e580a66c08bbe6c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:19:56 +0000 Subject: [PATCH 4/6] Remove numberOfInferences hack; match original TS PR exactly Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- ...AfterStringCompletionsInNestedCall_test.go | 2 +- internal/ls/string_completions.go | 44 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go b/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go index b9f21b3672f..5492273d38b 100644 --- a/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go +++ b/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go @@ -49,8 +49,8 @@ createMachine({ }, Items: &fourslash.CompletionsExpectedItems{ Exact: []fourslash.CompletionsExpectedItem{ - "ALOHAx", "ALOHA", + "ALOHAx", "LUNCH_TIME", "MORNING", }, diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go index 229d7acc362..a0e79693311 100644 --- a/internal/ls/string_completions.go +++ b/internal/ls/string_completions.go @@ -28,9 +28,8 @@ import ( ) type completionsFromTypes struct { - types []*checker.StringLiteralType - isNewIdentifier bool - numberOfInferences int // number of types at the beginning of 'types' from source node inference + types []*checker.StringLiteralType + isNewIdentifier bool } type completionsFromProperties struct { @@ -147,19 +146,14 @@ func (l *LanguageService) convertStringLiteralCompletions( } else { quoteChar = printer.QuoteCharDoubleQuote } - items := make([]*lsproto.CompletionItem, len(completion.types)) - for i, t := range completion.types { + items := core.Map(completion.types, func(t *checker.StringLiteralType) *lsproto.CompletionItem { name := printer.EscapeString(t.AsLiteralType().Value().(string), quoteChar) - sortText := SortTextLocationPriority - if i < completion.numberOfInferences { - sortText = SortTextLocalDeclarationPriority - } - items[i] = l.createLSPCompletionItem( + return l.createLSPCompletionItem( ctx, name, "", /*insertText*/ "", /*filterText*/ - sortText, + SortTextLocationPriority, lsutil.ScriptElementKindString, collections.Set[lsutil.ScriptElementKindModifier]{}, l.getReplacementRangeForContextToken(file, contextToken, position), @@ -175,7 +169,7 @@ func (l *LanguageService) convertStringLiteralCompletions( nil, /*autoImportEntryData*/ nil, /*detail*/ ) - } + }) defaultCommitCharacters := getDefaultCommitCharacters(completion.isNewIdentifier) itemDefaults := l.setItemDefaults( ctx, @@ -289,17 +283,11 @@ func (l *LanguageService) getStringLiteralCompletionEntries( } if ast.FindAncestor(parent.Parent, ast.IsCallLikeExpression) != nil { uniques := &collections.Set[string]{} - noneTypes := getStringLiteralTypes(typeChecker.GetContextualType(node, checker.ContextFlagsNone), uniques, typeChecker) - completionsTypes := getStringLiteralTypes(typeChecker.GetContextualType(node, checker.ContextFlagsCompletions), uniques, typeChecker) - stringLiteralTypes := append(noneTypes, completionsTypes...) - result := toCompletionsFromTypes(stringLiteralTypes) - if result != nil { - result.numberOfInferences = len(noneTypes) - return &stringLiteralCompletions{ - fromTypes: result, - } - } - return nil + stringLiteralTypes := append( + getStringLiteralTypes(typeChecker.GetContextualType(node, checker.ContextFlagsNone), uniques, typeChecker), + getStringLiteralTypes(typeChecker.GetContextualType(node, checker.ContextFlagsCompletions), uniques, typeChecker)..., + ) + return toStringLiteralCompletionsFromTypes(stringLiteralTypes) } return &stringLiteralCompletions{ fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker), @@ -448,6 +436,16 @@ func toCompletionsFromTypes(types []*checker.StringLiteralType) *completionsFrom } } +func toStringLiteralCompletionsFromTypes(types []*checker.StringLiteralType) *stringLiteralCompletions { + result := toCompletionsFromTypes(types) + if result == nil { + return nil + } + return &stringLiteralCompletions{ + fromTypes: result, + } +} + func fromUnionableLiteralType( grandparent *ast.Node, parent *ast.Node, From a8bdf70ac93594a334299962aeb689b15c087b7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:48:48 +0000 Subject: [PATCH 5/6] Move typeErrorAfterStringCompletionsInNestedCall to manual test via makemanual Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/fourslash/_scripts/manualTests.txt | 1 + .../typeErrorAfterStringCompletionsInNestedCall_test.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename internal/fourslash/tests/{gen => manual}/typeErrorAfterStringCompletionsInNestedCall_test.go (98%) diff --git a/internal/fourslash/_scripts/manualTests.txt b/internal/fourslash/_scripts/manualTests.txt index 4b7241b890a..a9b25dda2ae 100644 --- a/internal/fourslash/_scripts/manualTests.txt +++ b/internal/fourslash/_scripts/manualTests.txt @@ -124,3 +124,4 @@ stringLiteralCompletionsInPositionTypedUsingRest tripleSlashRefPathCompletionAbsolutePaths tripleSlashRefPathCompletionHiddenFile tsxCompletion12 +typeErrorAfterStringCompletionsInNestedCall diff --git a/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go b/internal/fourslash/tests/manual/typeErrorAfterStringCompletionsInNestedCall_test.go similarity index 98% rename from internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go rename to internal/fourslash/tests/manual/typeErrorAfterStringCompletionsInNestedCall_test.go index 5492273d38b..cf70038dc81 100644 --- a/internal/fourslash/tests/gen/typeErrorAfterStringCompletionsInNestedCall_test.go +++ b/internal/fourslash/tests/manual/typeErrorAfterStringCompletionsInNestedCall_test.go @@ -10,7 +10,7 @@ import ( ) func TestTypeErrorAfterStringCompletionsInNestedCall(t *testing.T) { - fourslash.SkipIfFailing(t) + t.Parallel() defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @stableTypeOrdering: true From 7ce7dcbb5dfda16694f233bf0e11391c7a14ddb4 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:59:00 -0800 Subject: [PATCH 6/6] fmt --- .../manual/typeErrorAfterStringCompletionsInNestedCall_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/fourslash/tests/manual/typeErrorAfterStringCompletionsInNestedCall_test.go b/internal/fourslash/tests/manual/typeErrorAfterStringCompletionsInNestedCall_test.go index 3e4d73a1a39..78d03acaeb3 100644 --- a/internal/fourslash/tests/manual/typeErrorAfterStringCompletionsInNestedCall_test.go +++ b/internal/fourslash/tests/manual/typeErrorAfterStringCompletionsInNestedCall_test.go @@ -10,7 +10,6 @@ import ( ) func TestTypeErrorAfterStringCompletionsInNestedCall(t *testing.T) { - t.Parallel() defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @stableTypeOrdering: true