From 93c89766cbbee2ea8ad726ef8aa2550f737e33e1 Mon Sep 17 00:00:00 2001 From: karitham Date: Sat, 8 Aug 2026 00:54:22 +0200 Subject: [PATCH] completion: keep type slots type-only and qualify imported types --- lsp/source/completion_type_slot_test.go | 75 +++++++++++++++++++++++++ lsp/source/context.go | 49 ++++++++++++++-- lsp/source/context_test.go | 6 ++ lsp/source/semantic_completion.go | 28 +++++++-- lsp/source/slot_completion_test.go | 5 +- 5 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 lsp/source/completion_type_slot_test.go diff --git a/lsp/source/completion_type_slot_test.go b/lsp/source/completion_type_slot_test.go new file mode 100644 index 0000000..559ed01 --- /dev/null +++ b/lsp/source/completion_type_slot_test.go @@ -0,0 +1,75 @@ +package source + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/karitham/thrift-ls/lsp/cache" +) + +// TestCompletionTypeSlotNoLeaks pins that type slots only suggest types: +// no enum values, consts, or identifier-token dumps leak in when the +// parser cannot complete the construct (openers like map< and keywords +// like const/typedef). +func TestCompletionTypeSlotNoLeaks(t *testing.T) { + inc := `enum Song { + FUWA_FUWA_TIME = 1 +} + +const i32 TEMPO = 120 + +struct Album { + 1: required string title +}` + + tests := []struct { + name string + marker string + }{ + {name: "after map opener", marker: "1: required map<"}, + {name: "after list opener", marker: "1: required list<"}, + {name: "after set opener", marker: "1: required set<"}, + {name: "after const", marker: "const "}, + {name: "after typedef", marker: "typedef "}, + {name: "map value after comma", marker: "1: required map= 0; i-- { + switch doc.Tokens[i].Kind { + case syntax.TokenGt: + depth++ + case syntax.TokenLt: + if depth == 0 { + return i, true + } + + depth-- + } + } + + return 0, false +} + // braceBodyKind returns the container keyword (struct/union/exception/enum/ // service) of the nearest enclosing brace body at or before from. func braceBodyKind(doc *syntax.Document, from int) (syntax.TokenKind, bool) { diff --git a/lsp/source/context_test.go b/lsp/source/context_test.go index 2c0b5df..112d8fa 100644 --- a/lsp/source/context_test.go +++ b/lsp/source/context_test.go @@ -76,6 +76,12 @@ func TestResolveContext(t *testing.T) { {"field name on identifier", `struct Gundam { 1: required string Na|me }`, CtxFieldName}, {"field type on identifier", `struct Gundam { 1: required Str|ing Name }`, CtxType}, {"map key type", `struct Gundam { 1: map<|i32, string> fields }`, CtxType}, + {"type slot after map opener", `struct Gundam { 1: map<|}`, CtxType}, + {"type slot after list opener", `struct Gundam { 1: list<|}`, CtxType}, + {"type slot after set opener", `struct Gundam { 1: set<|}`, CtxType}, + {"map value type after comma", `struct Gundam { 1: map