From 5c771a7a72a3ece705f8f429f0a123dd8fbb5d86 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 18:08:21 +0000 Subject: [PATCH 1/5] Initial plan From 16e29c3b037de01286f749e46c01498d5cf4ddfe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 18:29:25 +0000 Subject: [PATCH 2/5] Preserve original stack traces in cross-project panic handling When a panic occurs in a cross-project goroutine, wrap the recovered panic value and its stack trace in a core.PanicWithStack struct. When re-panicking, use this struct so the server's recover method can extract and report the original stack trace instead of the re-panic site's stack. Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/3e8c5719-fcb6-448b-b817-c628dd2c0bc6 Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/core/panic.go | 15 +++++++++++++++ internal/ls/crossproject.go | 8 +++----- internal/lsp/server.go | 13 ++++++++++--- 3 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 internal/core/panic.go diff --git a/internal/core/panic.go b/internal/core/panic.go new file mode 100644 index 00000000000..b2559efd7a0 --- /dev/null +++ b/internal/core/panic.go @@ -0,0 +1,15 @@ +package core + +import "fmt" + +// PanicWithStack wraps a recovered panic value along with the original stack trace +// captured at the site of the panic. This allows re-panicking while preserving the +// original stack context, rather than losing it at the re-panic site. +type PanicWithStack struct { + Value any + Stack []byte +} + +func (p *PanicWithStack) String() string { + return fmt.Sprintf("%v\n%s", p.Value, string(p.Stack)) +} diff --git a/internal/ls/crossproject.go b/internal/ls/crossproject.go index 9f7fb2bf515..03a652fb3c7 100644 --- a/internal/ls/crossproject.go +++ b/internal/ls/crossproject.go @@ -2,7 +2,6 @@ package ls import ( "context" - "fmt" "iter" "runtime/debug" "sync" @@ -73,7 +72,7 @@ func handleCrossProject[Req lsproto.HasTextDocumentPosition, Resp any]( wg := core.NewWorkGroup(false) var errMu sync.Mutex var enqueueItem func(item projectAndTextDocumentPosition) - var panicsOccured []string + var panicsOccured []*core.PanicWithStack var panicMu sync.Mutex enqueueItem = func(item projectAndTextDocumentPosition) { var response response[Resp] @@ -87,9 +86,8 @@ func handleCrossProject[Req lsproto.HasTextDocumentPosition, Resp any]( defer func() { if r := recover(); r != nil { stack := debug.Stack() - panicOccured := fmt.Sprintf("panic handling request: %v\n%s", r, string(stack)) panicMu.Lock() - panicsOccured = append(panicsOccured, panicOccured) + panicsOccured = append(panicsOccured, &core.PanicWithStack{Value: r, Stack: stack}) panicMu.Unlock() } }() @@ -207,7 +205,7 @@ func handleCrossProject[Req lsproto.HasTextDocumentPosition, Resp any]( wg.RunAndWait() // No need to use mu here since we are not in parallel at this point if panicsOccured != nil { - panic(fmt.Sprintf("Panics occurred during cross-project handling: %v", panicsOccured)) + panic(panicsOccured[0]) } if ctx.Err() != nil { return resp, ctx.Err() diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 70c2a4f8f5f..dfde674e0c9 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -955,9 +955,16 @@ func (s *Server) getLanguageServiceAndCrossProjectOrchestrator(ctx context.Conte func (s *Server) recover(req *lsproto.RequestMessage) { if r := recover(); r != nil { stack := debug.Stack() - s.logger.Errorf("panic handling request %s: %v\n%s", req.Method, r, string(stack)) + // If the panic was wrapped with PanicWithStack (e.g., from cross-project handling), + // use the original stack trace to preserve the actual crash context. + panicValue := r + if pws, ok := r.(*core.PanicWithStack); ok { + stack = pws.Stack + panicValue = pws.Value + } + s.logger.Errorf("panic handling request %s: %v\n%s", req.Method, panicValue, string(stack)) if req.ID != nil { - err := s.sendError(req.ID, fmt.Errorf("%w: panic handling request %s: %v", lsproto.ErrorCodeInternalError, req.Method, r)) + err := s.sendError(req.ID, fmt.Errorf("%w: panic handling request %s: %v", lsproto.ErrorCodeInternalError, req.Method, panicValue)) if err != nil { return } @@ -974,7 +981,7 @@ func (s *Server) recover(req *lsproto.RequestMessage) { }) } } else { - s.logger.Error("unhandled panic in notification", req.Method, r) + s.logger.Error("unhandled panic in notification", req.Method, panicValue) } } } From d18975fb70802c3f26cc657b475ca42081a045cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 18:30:46 +0000 Subject: [PATCH 3/5] Fix spelling: panicsOccured -> panicsOccurred Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/3e8c5719-fcb6-448b-b817-c628dd2c0bc6 Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/ls/crossproject.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/ls/crossproject.go b/internal/ls/crossproject.go index 03a652fb3c7..dc35ef2a496 100644 --- a/internal/ls/crossproject.go +++ b/internal/ls/crossproject.go @@ -72,7 +72,7 @@ func handleCrossProject[Req lsproto.HasTextDocumentPosition, Resp any]( wg := core.NewWorkGroup(false) var errMu sync.Mutex var enqueueItem func(item projectAndTextDocumentPosition) - var panicsOccured []*core.PanicWithStack + var panicsOccurred []*core.PanicWithStack var panicMu sync.Mutex enqueueItem = func(item projectAndTextDocumentPosition) { var response response[Resp] @@ -87,7 +87,7 @@ func handleCrossProject[Req lsproto.HasTextDocumentPosition, Resp any]( if r := recover(); r != nil { stack := debug.Stack() panicMu.Lock() - panicsOccured = append(panicsOccured, &core.PanicWithStack{Value: r, Stack: stack}) + panicsOccurred = append(panicsOccurred, &core.PanicWithStack{Value: r, Stack: stack}) panicMu.Unlock() } }() @@ -204,8 +204,8 @@ func handleCrossProject[Req lsproto.HasTextDocumentPosition, Resp any]( // Process existing known projects first wg.RunAndWait() // No need to use mu here since we are not in parallel at this point - if panicsOccured != nil { - panic(panicsOccured[0]) + if panicsOccurred != nil { + panic(panicsOccurred[0]) } if ctx.Err() != nil { return resp, ctx.Err() From 5cdf72fabe273348c10020fc5cff3cb2ec473c5b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 20:29:41 +0000 Subject: [PATCH 4/5] Add stack sanitizer test for cross-project panic stack traces Adds a test demonstrating what the sanitized telemetry stack trace looks like when a panic occurs in a cross-project worker goroutine and is preserved via PanicWithStack. The sanitized output shows the actual crash site (e.g., checker.checkExpression) and full call chain through provideSymbolsAndEntries and handleCrossProject, rather than just showing the re-panic location. Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/a16961db-d2a6-42e8-b21a-c00c664561b0 Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/lsp/stack_sanitizer_test.go | 32 ++++++++++++++ .../crossProjectPanicStackTrace.md | 44 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 testdata/baselines/reference/lsp/stackSanitizer/crossProjectPanicStackTrace.md diff --git a/internal/lsp/stack_sanitizer_test.go b/internal/lsp/stack_sanitizer_test.go index a66fe631f74..ac38747fc3b 100644 --- a/internal/lsp/stack_sanitizer_test.go +++ b/internal/lsp/stack_sanitizer_test.go @@ -83,6 +83,38 @@ created by github.com/microsoft/typescript-go/internal/lsp.(*Server).dispatchLoo }) } +// This test represents a stack trace captured via PanicWithStack from a cross-project +// worker goroutine. The stack originates from the goroutine where the panic occurred +// (captured by debug.Stack() in the deferred recover), preserving the actual crash site +// rather than showing the re-panic location in handleCrossProject. +func TestSanitizedCrossProjectPanicStackTrace(t *testing.T) { + t.Parallel() + + // This is the stack as captured by debug.Stack() in the cross-project worker goroutine's + // deferred recover. It shows the actual crash context that would otherwise be lost. + input := `goroutine 42 [running]: +runtime/debug.Stack() + runtime/debug/stack.go:26 +0x5e +github.com/microsoft/typescript-go/internal/ls.handleCrossProject[...].func1.1() + github.com/microsoft/typescript-go/internal/ls/crossproject.go:88 +0x70 +panic({0xc323a0?, 0x1780b90?}) + runtime/panic.go:783 +0x132 +github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpression(0xc0045a8000, {0x10f6688, 0xc00c2871d0}, 0xc0001fe008, 0x0) + github.com/microsoft/typescript-go/internal/checker/checker.go:5000 +0x1a0 +github.com/microsoft/typescript-go/internal/ls.(*LanguageService).provideSymbolsAndEntries(0xc008329200, {0x10f6688, 0xc00c2871d0}, {0xc00b472030, 0x28}, {0x2, 0x4}, 0x0, 0x0) + github.com/microsoft/typescript-go/internal/ls/findallreferences.go:100 +0x200 +github.com/microsoft/typescript-go/internal/ls.handleCrossProject[...].func1() + github.com/microsoft/typescript-go/internal/ls/crossproject.go:105 +0x150 +github.com/microsoft/typescript-go/internal/core.(*WorkGroup).worker(0xc000120080) + github.com/microsoft/typescript-go/internal/core/workgroup.go:50 +0x80 +created by github.com/microsoft/typescript-go/internal/core.(*WorkGroup).Queue in goroutine 35 + github.com/microsoft/typescript-go/internal/core/workgroup.go:35 +0x60` + + baseline.Run(t, "crossProjectPanicStackTrace.md", sanitizedStackTraceBaselineContents(t, input, sanitizeStackTrace(input)), baseline.Options{ + Subfolder: "lsp/stackSanitizer/", + }) +} + func sanitizedStackTraceBaselineContents(t *testing.T, input string, output string) string { builder := strings.Builder{} builder.WriteString("Test name: `") diff --git a/testdata/baselines/reference/lsp/stackSanitizer/crossProjectPanicStackTrace.md b/testdata/baselines/reference/lsp/stackSanitizer/crossProjectPanicStackTrace.md new file mode 100644 index 00000000000..3dc2e7c3dad --- /dev/null +++ b/testdata/baselines/reference/lsp/stackSanitizer/crossProjectPanicStackTrace.md @@ -0,0 +1,44 @@ +Test name: `TestSanitizedCrossProjectPanicStackTrace` + +# Unsanitized input: + +```` +goroutine 42 [running]: +runtime/debug.Stack() + runtime/debug/stack.go:26 +0x5e +github.com/microsoft/typescript-go/internal/ls.handleCrossProject[...].func1.1() + github.com/microsoft/typescript-go/internal/ls/crossproject.go:88 +0x70 +panic({0xc323a0?, 0x1780b90?}) + runtime/panic.go:783 +0x132 +github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpression(0xc0045a8000, {0x10f6688, 0xc00c2871d0}, 0xc0001fe008, 0x0) + github.com/microsoft/typescript-go/internal/checker/checker.go:5000 +0x1a0 +github.com/microsoft/typescript-go/internal/ls.(*LanguageService).provideSymbolsAndEntries(0xc008329200, {0x10f6688, 0xc00c2871d0}, {0xc00b472030, 0x28}, {0x2, 0x4}, 0x0, 0x0) + github.com/microsoft/typescript-go/internal/ls/findallreferences.go:100 +0x200 +github.com/microsoft/typescript-go/internal/ls.handleCrossProject[...].func1() + github.com/microsoft/typescript-go/internal/ls/crossproject.go:105 +0x150 +github.com/microsoft/typescript-go/internal/core.(*WorkGroup).worker(0xc000120080) + github.com/microsoft/typescript-go/internal/core/workgroup.go:50 +0x80 +created by github.com/microsoft/typescript-go/internal/core.(*WorkGroup).Queue in goroutine 35 + github.com/microsoft/typescript-go/internal/core/workgroup.go:35 +0x60 +```` + +# Sanitized output: + +```` +(REDACTED FRAME) + (REDACTED FRAME) +typescript-go|>internal|>ls.handleCrossProject[...].func1.1() + typescript-go|>internal|>ls|>crossproject.go:88 +(REDACTED FRAME) + (REDACTED FRAME) +typescript-go|>internal|>checker.(*Checker).checkExpression() + typescript-go|>internal|>checker|>checker.go:5000 +typescript-go|>internal|>ls.(*LanguageService).provideSymbolsAndEntries() + typescript-go|>internal|>ls|>findallreferences.go:100 +typescript-go|>internal|>ls.handleCrossProject[...].func1() + typescript-go|>internal|>ls|>crossproject.go:105 +typescript-go|>internal|>core.(*WorkGroup).worker() + typescript-go|>internal|>core|>workgroup.go:50 +typescript-go|>internal|>core.(*WorkGroup).Queue + typescript-go|>internal|>core|>workgroup.go:35 +```` From 70f8be4d84e51bc33837e41692ea4b0b3d11853f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 21:54:11 +0000 Subject: [PATCH 5/5] Strip recovery infrastructure frames from PanicWithStack to avoid B-A-B duplication NewPanicWithStack now trims debug.Stack()/deferred-recovery/panic() frames from the captured stack so telemetry only shows the actual crash site frames. The sanitizer is updated to handle stacks without the runtime/debug.Stack() marker (since those frames are trimmed). Added unit tests for trimPanicRecoveryFrames. Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/13260bed-9e38-4ff0-8fe9-141e0b8a2228 Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/core/panic.go | 40 ++++++++++++++++- internal/core/panic_test.go | 44 +++++++++++++++++++ internal/ls/crossproject.go | 4 +- internal/lsp/stack_sanitizer.go | 17 +++++-- internal/lsp/stack_sanitizer_test.go | 17 +++---- .../crossProjectPanicStackTrace.md | 13 ------ 6 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 internal/core/panic_test.go diff --git a/internal/core/panic.go b/internal/core/panic.go index b2559efd7a0..8a434e69ad5 100644 --- a/internal/core/panic.go +++ b/internal/core/panic.go @@ -1,6 +1,10 @@ package core -import "fmt" +import ( + "bytes" + "fmt" + "runtime/debug" +) // PanicWithStack wraps a recovered panic value along with the original stack trace // captured at the site of the panic. This allows re-panicking while preserving the @@ -13,3 +17,37 @@ type PanicWithStack struct { func (p *PanicWithStack) String() string { return fmt.Sprintf("%v\n%s", p.Value, string(p.Stack)) } + +// NewPanicWithStack creates a PanicWithStack from a recovered panic value. +// It captures the current stack trace via debug.Stack() and strips the +// recovery infrastructure frames (debug.Stack, the deferred recovery function, +// and the panic runtime frame) so that only the actual crash site frames remain. +func NewPanicWithStack(recovered any) *PanicWithStack { + return &PanicWithStack{ + Value: recovered, + Stack: trimPanicRecoveryFrames(debug.Stack()), + } +} + +// trimPanicRecoveryFrames strips recovery infrastructure frames from a stack +// trace captured by debug.Stack() inside a deferred recovery function. +// It removes everything up to and including the "panic(...)" frame and its +// file/line pair, leaving only the frames from the actual crash site onward. +func trimPanicRecoveryFrames(stack []byte) []byte { + // Find the panic() frame. In Go stack traces, it appears as a line + // starting with "panic(" (possibly with leading whitespace stripped). + lines := bytes.Split(stack, []byte("\n")) + for i, line := range lines { + trimmed := bytes.TrimSpace(line) + if bytes.HasPrefix(trimmed, []byte("panic(")) { + // The panic frame is followed by its file/line pair on the next line. + // Skip both to get to the actual crash site. + startIdx := i + 2 + if startIdx < len(lines) { + return bytes.Join(lines[startIdx:], []byte("\n")) + } + } + } + // If we couldn't find the panic frame, return the original stack. + return stack +} diff --git a/internal/core/panic_test.go b/internal/core/panic_test.go new file mode 100644 index 00000000000..1eea0ee544c --- /dev/null +++ b/internal/core/panic_test.go @@ -0,0 +1,44 @@ +package core + +import ( + "testing" +) + +func TestTrimPanicRecoveryFrames(t *testing.T) { + t.Parallel() + + input := []byte(`goroutine 42 [running]: +runtime/debug.Stack() + runtime/debug/stack.go:26 +0x5e +github.com/microsoft/typescript-go/internal/ls.handleCrossProject[...].func1.1() + github.com/microsoft/typescript-go/internal/ls/crossproject.go:88 +0x70 +panic({0xc323a0?, 0x1780b90?}) + runtime/panic.go:783 +0x132 +github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpression(0xc0045a8000) + github.com/microsoft/typescript-go/internal/checker/checker.go:5000 +0x1a0 +github.com/microsoft/typescript-go/internal/ls.handleCrossProject[...].func1() + github.com/microsoft/typescript-go/internal/ls/crossproject.go:105 +0x150`) + + expected := `github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpression(0xc0045a8000) + github.com/microsoft/typescript-go/internal/checker/checker.go:5000 +0x1a0 +github.com/microsoft/typescript-go/internal/ls.handleCrossProject[...].func1() + github.com/microsoft/typescript-go/internal/ls/crossproject.go:105 +0x150` + + result := string(trimPanicRecoveryFrames(input)) + if result != expected { + t.Errorf("trimPanicRecoveryFrames result mismatch.\nGot:\n%s\n\nExpected:\n%s", result, expected) + } +} + +func TestTrimPanicRecoveryFramesNoPanicFrame(t *testing.T) { + t.Parallel() + + // If no panic() frame exists, the stack should be returned as-is. + input := []byte(`github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpression(0xc0045a8000) + github.com/microsoft/typescript-go/internal/checker/checker.go:5000 +0x1a0`) + + result := string(trimPanicRecoveryFrames(input)) + if result != string(input) { + t.Errorf("expected unchanged output when no panic frame, got:\n%s", result) + } +} diff --git a/internal/ls/crossproject.go b/internal/ls/crossproject.go index dc35ef2a496..3fb8add929d 100644 --- a/internal/ls/crossproject.go +++ b/internal/ls/crossproject.go @@ -3,7 +3,6 @@ package ls import ( "context" "iter" - "runtime/debug" "sync" "github.com/microsoft/typescript-go/internal/collections" @@ -85,9 +84,8 @@ func handleCrossProject[Req lsproto.HasTextDocumentPosition, Resp any]( } defer func() { if r := recover(); r != nil { - stack := debug.Stack() panicMu.Lock() - panicsOccurred = append(panicsOccurred, &core.PanicWithStack{Value: r, Stack: stack}) + panicsOccurred = append(panicsOccurred, core.NewPanicWithStack(r)) panicMu.Unlock() } }() diff --git a/internal/lsp/stack_sanitizer.go b/internal/lsp/stack_sanitizer.go index 2e3cd886f4b..9a89a9984d3 100644 --- a/internal/lsp/stack_sanitizer.go +++ b/internal/lsp/stack_sanitizer.go @@ -24,10 +24,21 @@ func sanitizeStackTrace(stack string) string { // TODO: should we just look for the first '(' and // just strip everything before the prior newline? startIndex := strings.Index(stack, "runtime/debug.Stack()") - if startIndex < 0 { - return "" + if startIndex >= 0 { + stack = stack[startIndex:] + } else { + // For stacks that have already had recovery frames trimmed + // (e.g., from PanicWithStack), find the first line containing our module. + moduleIndex := strings.Index(stack, "typescript-go/internal") + if moduleIndex < 0 { + return "" + } + // Back up to the beginning of the line containing our module. + lineStart := strings.LastIndex(stack[:moduleIndex], "\n") + if lineStart >= 0 { + stack = stack[lineStart+1:] + } } - stack = stack[startIndex:] result := &strings.Builder{} diff --git a/internal/lsp/stack_sanitizer_test.go b/internal/lsp/stack_sanitizer_test.go index ac38747fc3b..944458633fb 100644 --- a/internal/lsp/stack_sanitizer_test.go +++ b/internal/lsp/stack_sanitizer_test.go @@ -85,21 +85,14 @@ created by github.com/microsoft/typescript-go/internal/lsp.(*Server).dispatchLoo // This test represents a stack trace captured via PanicWithStack from a cross-project // worker goroutine. The stack originates from the goroutine where the panic occurred -// (captured by debug.Stack() in the deferred recover), preserving the actual crash site -// rather than showing the re-panic location in handleCrossProject. +// with recovery infrastructure frames (debug.Stack, deferred func, panic) already +// stripped by NewPanicWithStack, leaving only the actual crash site frames. func TestSanitizedCrossProjectPanicStackTrace(t *testing.T) { t.Parallel() - // This is the stack as captured by debug.Stack() in the cross-project worker goroutine's - // deferred recover. It shows the actual crash context that would otherwise be lost. - input := `goroutine 42 [running]: -runtime/debug.Stack() - runtime/debug/stack.go:26 +0x5e -github.com/microsoft/typescript-go/internal/ls.handleCrossProject[...].func1.1() - github.com/microsoft/typescript-go/internal/ls/crossproject.go:88 +0x70 -panic({0xc323a0?, 0x1780b90?}) - runtime/panic.go:783 +0x132 -github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpression(0xc0045a8000, {0x10f6688, 0xc00c2871d0}, 0xc0001fe008, 0x0) + // This is the stack after NewPanicWithStack trims recovery overhead. + // It starts directly at the crash site without debug.Stack/panic frames. + input := `github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpression(0xc0045a8000, {0x10f6688, 0xc00c2871d0}, 0xc0001fe008, 0x0) github.com/microsoft/typescript-go/internal/checker/checker.go:5000 +0x1a0 github.com/microsoft/typescript-go/internal/ls.(*LanguageService).provideSymbolsAndEntries(0xc008329200, {0x10f6688, 0xc00c2871d0}, {0xc00b472030, 0x28}, {0x2, 0x4}, 0x0, 0x0) github.com/microsoft/typescript-go/internal/ls/findallreferences.go:100 +0x200 diff --git a/testdata/baselines/reference/lsp/stackSanitizer/crossProjectPanicStackTrace.md b/testdata/baselines/reference/lsp/stackSanitizer/crossProjectPanicStackTrace.md index 3dc2e7c3dad..afd59099163 100644 --- a/testdata/baselines/reference/lsp/stackSanitizer/crossProjectPanicStackTrace.md +++ b/testdata/baselines/reference/lsp/stackSanitizer/crossProjectPanicStackTrace.md @@ -3,13 +3,6 @@ Test name: `TestSanitizedCrossProjectPanicStackTrace` # Unsanitized input: ```` -goroutine 42 [running]: -runtime/debug.Stack() - runtime/debug/stack.go:26 +0x5e -github.com/microsoft/typescript-go/internal/ls.handleCrossProject[...].func1.1() - github.com/microsoft/typescript-go/internal/ls/crossproject.go:88 +0x70 -panic({0xc323a0?, 0x1780b90?}) - runtime/panic.go:783 +0x132 github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpression(0xc0045a8000, {0x10f6688, 0xc00c2871d0}, 0xc0001fe008, 0x0) github.com/microsoft/typescript-go/internal/checker/checker.go:5000 +0x1a0 github.com/microsoft/typescript-go/internal/ls.(*LanguageService).provideSymbolsAndEntries(0xc008329200, {0x10f6688, 0xc00c2871d0}, {0xc00b472030, 0x28}, {0x2, 0x4}, 0x0, 0x0) @@ -25,12 +18,6 @@ created by github.com/microsoft/typescript-go/internal/core.(*WorkGroup).Queue i # Sanitized output: ```` -(REDACTED FRAME) - (REDACTED FRAME) -typescript-go|>internal|>ls.handleCrossProject[...].func1.1() - typescript-go|>internal|>ls|>crossproject.go:88 -(REDACTED FRAME) - (REDACTED FRAME) typescript-go|>internal|>checker.(*Checker).checkExpression() typescript-go|>internal|>checker|>checker.go:5000 typescript-go|>internal|>ls.(*LanguageService).provideSymbolsAndEntries()