From 4bb3f7e185135f222d5d8569937f358fcf941785 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 28 Oct 2026 22:00:01 -0700 Subject: [PATCH 1/3] Add failing test --- .../tests/signatureHelpUnclosedParen.go | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tsc/internal/fourslash/tests/signatureHelpUnclosedParen.go diff --git a/tsc/internal/fourslash/tests/signatureHelpUnclosedParen.go b/tsc/internal/fourslash/tests/signatureHelpUnclosedParen.go new file mode 100644 index 0000000000000..6443105a466bd --- /dev/null +++ b/tsc/internal/fourslash/tests/signatureHelpUnclosedParen.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + "time" + + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/testutil" +) + +func TestSignatureHelpUnclosedTypeArgumentHang(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `<()(/**/` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.GoToMarker(t, "") + + finished := make(chan struct{}) + go func() { + defer close(finished) + f.VerifyNoSignatureHelp(t) + }() + select { + case <-finished: + case <-time.After(10 * time.Second): + t.Fatal("signature help request did not complete within 10 seconds") + } +} From 9e0644f3a1f495dcdbc07f38696de05c89e26b82 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 28 Oct 2026 22:30:29 -0700 Subject: [PATCH 2/3] Fix the hang --- tsc/internal/ls/utilities.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tsc/internal/ls/utilities.go b/tsc/internal/ls/utilities.go index a05994440d530..87401f57f56cf 100644 --- a/tsc/internal/ls/utilities.go +++ b/tsc/internal/ls/utilities.go @@ -1161,12 +1161,13 @@ func findPrecedingMatchingToken(token *ast.Node, matchingTokenKind ast.Kind, sou matchingTokenText := scanner.TokenToString(matchingTokenKind) // Text-scan based fast path - can be bamboozled by comments and other trivia, but often provides // a good, fast approximation without too much extra work in the cases where it fails. - bestGuessIndex := strings.LastIndex(sourceFile.Text(), matchingTokenText) + precedingText := sourceFile.Text()[:token.Pos()] + bestGuessIndex := strings.LastIndex(precedingText, matchingTokenText) if bestGuessIndex == -1 { return nil // if the token text doesn't appear in the file, there can't be a match - super fast bail } // we can only use the textual result directly if we didn't have to count any close tokens within the range - if strings.LastIndex(sourceFile.Text(), closeTokenText) < bestGuessIndex { + if strings.LastIndex(precedingText, closeTokenText) < bestGuessIndex { nodeAtGuess := astnav.FindPrecedingToken(sourceFile, bestGuessIndex+1) if nodeAtGuess != nil && nodeAtGuess.Kind == matchingTokenKind { return nodeAtGuess From 3234574e3d2c090fb6696b5ab41bde6afc77b373 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 28 Oct 2026 22:30:50 -0700 Subject: [PATCH 3/3] Rename test and remove unneeded stuff --- ...edParen.go => signatureHelpUnclosedParen_test.go} | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) rename tsc/internal/fourslash/tests/{signatureHelpUnclosedParen.go => signatureHelpUnclosedParen_test.go} (63%) diff --git a/tsc/internal/fourslash/tests/signatureHelpUnclosedParen.go b/tsc/internal/fourslash/tests/signatureHelpUnclosedParen_test.go similarity index 63% rename from tsc/internal/fourslash/tests/signatureHelpUnclosedParen.go rename to tsc/internal/fourslash/tests/signatureHelpUnclosedParen_test.go index 6443105a466bd..6023219fab420 100644 --- a/tsc/internal/fourslash/tests/signatureHelpUnclosedParen.go +++ b/tsc/internal/fourslash/tests/signatureHelpUnclosedParen_test.go @@ -2,7 +2,6 @@ package fourslash_test import ( "testing" - "time" "github.com/microsoft/TypeScript/tsc/internal/fourslash" "github.com/microsoft/TypeScript/tsc/internal/testutil" @@ -16,14 +15,5 @@ func TestSignatureHelpUnclosedTypeArgumentHang(t *testing.T) { defer done() f.GoToMarker(t, "") - finished := make(chan struct{}) - go func() { - defer close(finished) - f.VerifyNoSignatureHelp(t) - }() - select { - case <-finished: - case <-time.After(10 * time.Second): - t.Fatal("signature help request did not complete within 10 seconds") - } + f.VerifyNoSignatureHelp(t) }