summaryrefslogtreecommitdiff
path: root/internal/lsp/completion_prefix_strip_test.go
blob: 995371435914a0e90e8d3a02ba2fb17099bd0ba3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package lsp

import (
    "encoding/json"
    "testing"
)

func TestStripDuplicateGeneralPrefix_ExactOverlap(t *testing.T) {
    prefix := "func New "
    sugg := "func New() *CustData"
    got := stripDuplicateGeneralPrefix(prefix, sugg)
    // We expect the already typed prefix to be removed from the suggestion.
    if got == sugg {
        t.Fatalf("expected duplicate prefix to be stripped; got unchanged: %q", got)
    }
    if got != "() *CustData" {
        t.Fatalf("got %q want %q", got, "() *CustData")
    }
}

func TestStripDuplicateGeneralPrefix_TokenBoundarySuffix(t *testing.T) {
    prefix := "db."
    sugg := "db.Query()"
    got := stripDuplicateGeneralPrefix(prefix, sugg)
    if got != "Query()" {
        t.Fatalf("got %q want %q", got, "Query()")
    }
}

func TestStripDuplicateAssignmentPrefix_AssignAndWalrus(t *testing.T) {
    // walrus
    if out := stripDuplicateAssignmentPrefix("name := ", "name := compute()" ); out != "compute()" {
        t.Fatalf(":= expected compute(), got %q", out)
    }
    // equals
    if out := stripDuplicateAssignmentPrefix("x = ", "x = y+1" ); out != "y+1" {
        t.Fatalf("= expected y+1, got %q", out)
    }
}

func TestTryLLMCompletion_ManualInvokeAfterWhitespace_Allows(t *testing.T) {
    s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
    s.llmClient = fakeLLM{resp: "() *CustData"}
    line := "func fib(i int) " // cursor after space
    p := CompletionParams{ Position: Position{ Line: 0, Character: len(line) }, TextDocument: TextDocumentIdentifier{URI: "file://x.go"} }
    // Simulate manual user invocation (TriggerKind=1)
    p.Context = json.RawMessage([]byte(`{"triggerKind":1}`))
    items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
    if !ok { t.Fatalf("expected ok=true for manual invoke after whitespace") }
    if len(items) == 0 { t.Fatalf("expected at least one completion item") }
}

func TestTryLLMCompletion_InlineSemicolonPromptAlwaysTriggers(t *testing.T) {
    s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
    s.llmClient = fakeLLM{resp: "replacement"}
    line := "prefix ;do something; suffix"
    // No trigger char immediately before cursor; place cursor at end
    p := CompletionParams{ Position: Position{ Line: 0, Character: len(line) }, TextDocument: TextDocumentIdentifier{URI: "file://inline.go"} }
    items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
    if !ok || len(items) == 0 { t.Fatalf("expected completion to trigger on inline ;text; prompt") }
}

func TestTryLLMCompletion_DoubleSemicolonEmpty_DoesNotAutoTrigger(t *testing.T) {
    s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
    fake := &countingLLM{}
    s.llmClient = fake
    line := ";;   " // empty content after ';;' should not force-trigger
    p := CompletionParams{ Position: Position{ Line: 0, Character: len(line) }, TextDocument: TextDocumentIdentifier{URI: "file://empty-inline.go"} }
    items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
    if !ok { t.Fatalf("expected ok=true for non-trigger path") }
    if len(items) != 0 { t.Fatalf("expected no items when inline ';;' is empty") }
    if fake.calls != 0 { t.Fatalf("LLM should not be called; calls=%d", fake.calls) }
}

func TestHasDoubleSemicolonTrigger_Variants(t *testing.T) {
    if hasDoubleSemicolonTrigger(";;") { t.Fatalf("bare ';;' should not trigger") }
    if hasDoubleSemicolonTrigger(";; ;") { t.Fatalf("';;' followed by space should not trigger") }
    if hasDoubleSemicolonTrigger(";;;") { t.Fatalf("';;;' should not trigger (no content)") }
    if !hasDoubleSemicolonTrigger(";;x;") { t.Fatalf("expected trigger for ';;x;' pattern") }
}

func TestBareDoubleSemicolonPreventsAutoTriggerEvenWithOtherTriggers(t *testing.T) {
    s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
    fake := &countingLLM{}
    s.llmClient = fake
    // Place a '.' earlier but also include bare ';;' at end; should not auto-trigger
    line := "obj. call ;;"
    p := CompletionParams{ Position: Position{ Line: 0, Character: len(line) }, TextDocument: TextDocumentIdentifier{URI: "file://bare-ds.go"} }
    items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
    if !ok { t.Fatalf("expected ok=true (handled), but not auto-triggering") }
    if len(items) != 0 { t.Fatalf("expected no items due to bare ';;'") }
    if fake.calls != 0 { t.Fatalf("LLM should not be called; calls=%d", fake.calls) }
}

func TestBareDoubleSemicolonOnNextLine_PreventsAutoTrigger(t *testing.T) {
    s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
    fake := &countingLLM{}
    s.llmClient = fake
    current := "expression := flag.String(\"expression\", \"\", \"Expression to evaluate\")"
    below := ";;"
    p := CompletionParams{ Position: Position{ Line: 0, Character: len(current) }, TextDocument: TextDocumentIdentifier{URI: "file://nextline.go"} }
    items, ok := s.tryLLMCompletion(p, "", current, below, "", "", false, "")
    if !ok { t.Fatalf("expected ok=true handled") }
    if len(items) != 0 { t.Fatalf("expected no items due to bare ';;' on next line") }
    if fake.calls != 0 { t.Fatalf("LLM should not be called; calls=%d", fake.calls) }
}

func TestBareDoubleSemicolonPreventsManualInvoke(t *testing.T) {
    s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
    fake := &countingLLM{}
    s.llmClient = fake
    line := ";;"
    p := CompletionParams{ Position: Position{ Line: 0, Character: len(line) }, TextDocument: TextDocumentIdentifier{URI: "file://bare-ds-manual.go"} }
    // Simulate manual invoke
    p.Context = json.RawMessage([]byte(`{"triggerKind":1}`))
    items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
    if !ok { t.Fatalf("expected ok=true (handled)") }
    if len(items) != 0 { t.Fatalf("expected no items for bare ';;' even with manual invoke") }
    if fake.calls != 0 { t.Fatalf("LLM should not be called; calls=%d", fake.calls) }
}