Repository: microsoft/typescript-go (go)

Issue:
[bug] panic when providing auto-complete results on type
The weird thing is that other than the error modal I wouldn't have noticed this panic -- the LSP continues to remain responsive and provide completion results. Let me know if there's anything else I can attach to this bug report that can help ya'll diagnose the issue.

<img width="470" height="104" alt="Image" src="https://github.com/user-attachments/assets/028b3c25-e90f-4db3-a664-817d528cdb2d" />

TS extension verison:

```
Identifier typescriptteam.native-preview
Version 0.20250712.1
```

LSP logs:

```
Info 7494 [16:45:21.874] Finishing updateGraph: Project: /<omitted>/canva/<omitted>/tsconfig.json version: 11 in 307.31µs
panic handling request completionItem/resolve runtime error: invalid memory address or nil pointer dereference goroutine 431935 [running]:
runtime/debug.Stack()
	runtime/debug/stack.go:26 +0x5e
github.com/microsoft/typescript-go/internal/lsp.(*Server).dispatchLoop.func1.1()
	github.com/microsoft/typescript-go/internal/lsp/server.go:351 +0x5d
panic({0xa4c5c0?, 0x1268d90?})
	runtime/panic.go:792 +0x132
github.com/microsoft/typescript-go/internal/ast.IsPropertyAccessExpression(...)
	github.com/microsoft/typescript-go/internal/ast/ast.go:6127
github.com/microsoft/typescript-go/internal/ls.getCallOrNewExpression(0xc148920008?)
	github.com/microsoft/typescript-go/internal/ls/hover.go:280 +0x12
github.com/microsoft/typescript-go/internal/ls.getSignaturesAtLocation(0xc148920008, 0xc147297088?, 0x0, 0xc147297088)
	github.com/microsoft/typescript-go/internal/ls/hover.go:268 +0x65
github.com/microsoft/typescript-go/internal/ls.getQuickInfoAndDeclarationAtLocation(0xc148920008, 0xc148920008?, 0xc147297088)
	github.com/microsoft/typescript-go/internal/ls/hover.go:173 +0x1785
github.com/microsoft/typescript-go/internal/ls.getQuickInfoAndDocumentationForSymbol(0x46fc39?, 0x40?, 0xad6ca0?)
	github.com/microsoft/typescript-go/internal/ls/hover.go:49 +0x1f
github.com/microsoft/typescript-go/internal/ls.createCompletionDetailsForSymbol(0xc0651d3040, 0xc10265a0e8, 0xc148920008, 0xc147297088, {0x0, 0x0, 0xc136fbfe88?})
	github.com/microsoft/typescript-go/internal/ls/completions.go:4831 +0x17a
github.com/microsoft/typescript-go/internal/ls.(*LanguageService).getCompletionItemDetails(0xc071959200, {0xd39eb0, 0xc1494fc730}, 0xc14729d180, 0x55a6, 0xc147297088, 0xc0651d3040, 0xc05cd03f00, 0xc0003b5290, 0xc136fbfe88)
	github.com/microsoft/typescript-go/internal/ls/completions.go:4671 +0x2b1
github.com/microsoft/typescript-go/internal/ls.(*LanguageService).ResolveCompletionItem(0xc071959200, {0xd39eb0, 0xc1494fc730}, 0xc0651d3040, 0xc05cd03f00, 0xc0003b5290, 0xc1312a6e88)
	github.com/microsoft/typescript-go/internal/ls/completions.go:4607 +0xb1
github.com/microsoft/typescript-go/internal/lsp.(*Server).handleCompletionItemResolve(0xc00019ea20, {0xd39eb0, 0xc1494fc730}, 0xc071fda960)
	github.com/microsoft/typescript-go/internal/lsp/server.go:733 +0x145
github.com/microsoft/typescript-go/internal/lsp.(*Server).handleRequestOrNotification(0xc147e16848?, {0xd39eb0?, 0xc1494fc730?}, 0xc03017dbc0?)
	github.com/microsoft/typescript-go/internal/lsp/server.go:496 +0x8f
github.com/microsoft/typescript-go/internal/lsp.(*Server).dispatchLoop.func1()
	github.com/microsoft/typescript-go/internal/lsp/server.go:364 +0x75
created by github.com/microsoft/typescript-go/internal/lsp.(*Server).dispatchLoop in goroutine 8
	github.com/microsoft/typescript-go/internal/lsp/server.go:382 +0x4d2
```

Pull request description:
Fix `completionItem/resolve` keyword and hover crashes
Fixes #1402, a crash in hover code called by completionItem/resolve.
Also fixes another completionItem/resolve crash that resulted from not including data for keyword completion items.

Patch:
diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go
index 0189cba8d..c6824465f 100644
--- a/internal/fourslash/fourslash.go
+++ b/internal/fourslash/fourslash.go
@@ -660,7 +660,7 @@ func (f *FourslashTest) verifyCompletionsItems(t *testing.T, prefix string, actu
 					t.Fatalf("%sLabel '%s' not found in actual items. Actual items: %s", prefix, item.Label, cmp.Diff(actual, nil))
 				}
 				delete(nameToActualItem, item.Label)
-				f.verifyCompletionItem(t, prefix+"Includes completion item mismatch for label "+item.Label, actualItem, item)
+				f.verifyCompletionItem(t, prefix+"Includes completion item mismatch for label "+item.Label+": ", actualItem, item)
 			default:
 				t.Fatalf("%sExpected completion item to be a string or *lsproto.CompletionItem, got %T", prefix, item)
 			}
@@ -684,7 +684,7 @@ func (f *FourslashTest) verifyCompletionsItems(t *testing.T, prefix string, actu
 				if !ok {
 					t.Fatalf("%sLabel '%s' not found in actual items. Actual items: %s", prefix, item.Label, cmp.Diff(actual, nil))
 				}
-				f.verifyCompletionItem(t, prefix+"Includes completion item mismatch for label "+item.Label, actualItem, item)
+				f.verifyCompletionItem(t, prefix+"Includes completion item mismatch for label "+item.Label+": ", actualItem, item)
 			default:
 				t.Fatalf("%sExpected completion item to be a string or *lsproto.CompletionItem, got %T", prefix, item)
 			}
diff --git a/internal/ls/completions.go b/internal/ls/completions.go
index 15fecba4c..670b10c44 100644
--- a/internal/ls/completions.go
+++ b/internal/ls/completions.go
@@ -28,7 +28,7 @@ import (
 func (l *LanguageService) ProvideCompletion(
 	ctx context.Context,
 	documentURI lsproto.DocumentUri,
-	position lsproto.Position,
+	LSPPosition lsproto.Position,
 	context *lsproto.CompletionContext,
 	clientOptions *lsproto.CompletionClientCapabilities,
 	preferences *UserPreferences,
@@ -38,15 +38,34 @@ func (l *LanguageService) ProvideCompletion(
 	if context != nil {
 		triggerCharacter = context.TriggerCharacter
 	}
-	return l.getCompletionsAtPosition(
+	position := int(l.converters.LineAndCharacterToPosition(file, LSPPosition))
+	completionList := l.getCompletionsAtPosition(
 		ctx,
 		program,
 		file,
-		int(l.converters.LineAndCharacterToPosition(file, position)),
+		position,
 		triggerCharacter,
 		preferences,
 		clientOptions,
-	), nil
+	)
+	return ensureItemData(file.FileName(), position, completionList), nil
+}
+
+func ensureItemData(fileName string, pos int, list *lsproto.CompletionList) *lsproto.CompletionList {
+	if list == nil {
+		return nil
+	}
+	for _, item := range list.Items {
+		if item.Data == nil {
+			var data any = &itemData{
+				FileName: fileName,
+				Position: pos,
+				Name:     item.Label,
+			}
+			item.Data = &data
+		}
+	}
+	return list
 }
 
 // *completionDataData | *completionDataKeyword
diff --git a/internal/ls/hover.go b/internal/ls/hover.go
index d8490581e..5fa53d06e 100644
--- a/internal/ls/hover.go
+++ b/internal/ls/hover.go
@@ -277,6 +277,9 @@ func getSignaturesAtLocation(c *checker.Checker, symbol *ast.Symbol, kind checke
 }
 
 func getCallOrNewExpression(node *ast.Node) *ast.Node {
+	if ast.IsSourceFile(node) {
+		return nil
+	}
 	if ast.IsPropertyAccessExpression(node.Parent) && node.Parent.Name() == node {
 		node = node.Parent
 	}


Test patch:
diff --git a/internal/fourslash/_scripts/failingTests.txt b/internal/fourslash/_scripts/failingTests.txt
index 3460c3470..5d0dbc1e5 100644
--- a/internal/fourslash/_scripts/failingTests.txt
+++ b/internal/fourslash/_scripts/failingTests.txt
@@ -73,7 +73,6 @@ TestCompletionListAfterStringLiteral1
 TestCompletionListAndMemberListOnCommentedDot
 TestCompletionListAndMemberListOnCommentedLine
 TestCompletionListAndMemberListOnCommentedWhiteSpace
-TestCompletionListAtEndOfWordInArrowFunction03
 TestCompletionListAtInvalidLocations
 TestCompletionListBuilderLocations_VariableDeclarations
 TestCompletionListCladule
diff --git a/internal/fourslash/tests/completionDetailSignature_test.go b/internal/fourslash/tests/completionDetailSignature_test.go
new file mode 100644
index 000000000..4a5d786ee
--- /dev/null
+++ b/internal/fourslash/tests/completionDetailSignature_test.go
@@ -0,0 +1,41 @@
+package fourslash_test
+
+import (
+	"testing"
+
+	"github.com/microsoft/typescript-go/internal/fourslash"
+	"github.com/microsoft/typescript-go/internal/ls"
+	"github.com/microsoft/typescript-go/internal/lsp/lsproto"
+	"github.com/microsoft/typescript-go/internal/testutil"
+)
+
+func TestCompletionDetailSignature(t *testing.T) {
+	t.Parallel()
+	defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+	const content = `
+
+/*a*/
+
+function foo(x: string): string;
+function foo(x: number): number;
+function foo(x: any): any {
+    return x;
+}`
+	f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+	f.VerifyCompletions(t, "a", &fourslash.CompletionsExpectedList{
+		IsIncomplete: false,
+		ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
+			CommitCharacters: &defaultCommitCharacters,
+		},
+		Items: &fourslash.CompletionsExpectedItems{
+			Includes: []fourslash.CompletionsExpectedItem{
+				&lsproto.CompletionItem{
+					Label:    "foo",
+					Kind:     ptrTo(lsproto.CompletionItemKindFunction),
+					SortText: ptrTo(string(ls.SortTextLocationPriority)),
+					Detail:   ptrTo("function foo(x: string): string\nfunction foo(x: number): number"),
+				},
+			},
+		},
+	})
+}
diff --git a/internal/fourslash/tests/completionResolveKeyword_test.go b/internal/fourslash/tests/completionResolveKeyword_test.go
new file mode 100644
index 000000000..61176c75b
--- /dev/null
+++ b/internal/fourslash/tests/completionResolveKeyword_test.go
@@ -0,0 +1,35 @@
+package fourslash_test
+
+import (
+	"testing"
+
+	"github.com/microsoft/typescript-go/internal/fourslash"
+	"github.com/microsoft/typescript-go/internal/ls"
+	"github.com/microsoft/typescript-go/internal/lsp/lsproto"
+	"github.com/microsoft/typescript-go/internal/testutil"
+)
+
+func TestCompletionResolveKeyword(t *testing.T) {
+	t.Parallel()
+	defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+	const content = `class C {
+	/*a*/
+}`
+	f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+	f.VerifyCompletions(t, "a", &fourslash.CompletionsExpectedList{
+		IsIncomplete: false,
+		ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
+			CommitCharacters: &[]string{},
+		},
+		Items: &fourslash.CompletionsExpectedItems{
+			Includes: []fourslash.CompletionsExpectedItem{
+				&lsproto.CompletionItem{
+					Label:    "abstract",
+					Kind:     ptrTo(lsproto.CompletionItemKindKeyword),
+					SortText: ptrTo(string(ls.SortTextGlobalsOrKeywords)),
+					Detail:   ptrTo("abstract"),
+				},
+			},
+		},
+	})
+}
diff --git a/internal/fourslash/tests/gen/completionListAtEndOfWordInArrowFunction03_test.go b/internal/fourslash/tests/gen/completionListAtEndOfWordInArrowFunction03_test.go
index 5f7b1a4f1..e9e9d0fcf 100644
--- a/internal/fourslash/tests/gen/completionListAtEndOfWordInArrowFunction03_test.go
+++ b/internal/fourslash/tests/gen/completionListAtEndOfWordInArrowFunction03_test.go
@@ -11,7 +11,7 @@ import (
 
 func TestCompletionListAtEndOfWordInArrowFunction03(t *testing.T) {
 	t.Parallel()
-	t.Skip()
+
 	defer testutil.RecoverAndFail(t, "Panic on fourslash test")
 	const content = `(d, defaultIsAnInvalidParameterName) => default/*1*/`
 	f := fourslash.NewFourslash(t, nil /*capabilities*/, content)