mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-10-19 05:57:38 +00:00
feat(lsp): query by symbol_name; document ast_query match limits
Two ast_query/lsp_query ergonomics improvements that share the tool registration and help files: - lsp_query accepts symbol_name (+symbol_kind) and resolves the position via AST (resolveLSPSymbolPosition), so callers need not know line:col; line/column are no longer required. SymbolCandidate gains NameLine/NameColumn for the precise identifier position. Ambiguous/unknown names error with candidates / a 'did you mean' hint. Adds TestResolveLSPSymbolPosition. - ast_query help + tool description now document that matching is keyword/kind-based, not structural: pattern arg/return constraints are not enforced; use name_exact/name_matches/kind_in to filter. Fixes a misleading 'functions returning error' example.
This commit is contained in:
@@ -1,12 +1,25 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/mcp-filepuff/internal/lsp"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
)
|
||||
|
||||
// errorText extracts the text payload from an error CallToolResult.
|
||||
func errorText(res *mcp.CallToolResult) string {
|
||||
if res == nil || len(res.Content) == 0 {
|
||||
return ""
|
||||
}
|
||||
if tc, ok := res.Content[0].(mcp.TextContent); ok {
|
||||
return tc.Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// makeLocation builds an lsp.Location with a file:// URI.
|
||||
func makeLocation(file string, line, col int) lsp.Location {
|
||||
return lsp.Location{
|
||||
@@ -179,3 +192,53 @@ func TestFormatReferencesVerbosePreservesLPrefix(t *testing.T) {
|
||||
t.Errorf("verbose output must contain L3:4, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// resolveLSPSymbolPosition underpins lsp_query's symbol_name affordance: it maps
|
||||
// a symbol name to the line:column of its name identifier, or returns an error
|
||||
// result for not-found / ambiguous names. It does not require a running LSP.
|
||||
func TestResolveLSPSymbolPosition(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
srv := newTestServer(t, tmpDir)
|
||||
|
||||
goSrc := "package main\n" + // 1
|
||||
"\n" + // 2
|
||||
"type A struct{}\n" + // 3
|
||||
"func (a A) Run() {}\n" + // 4
|
||||
"\n" + // 5
|
||||
"type B struct{}\n" + // 6
|
||||
"func (b B) Run() {}\n" + // 7
|
||||
"\n" + // 8
|
||||
"func Unique() {}\n" // 9
|
||||
f := writeFile(t, tmpDir, "main.go", goSrc)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("unique symbol resolves to its name position", func(t *testing.T) {
|
||||
line, col, errRes := srv.resolveLSPSymbolPosition(ctx, f, "Unique", "")
|
||||
if errRes != nil {
|
||||
t.Fatalf("unexpected error result for Unique: %s", errorText(errRes))
|
||||
}
|
||||
if line != 9 || col != 6 {
|
||||
t.Errorf("Unique position = %d:%d, want 9:6", line, col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ambiguous symbol errors", func(t *testing.T) {
|
||||
_, _, errRes := srv.resolveLSPSymbolPosition(ctx, f, "Run", "")
|
||||
if errRes == nil {
|
||||
t.Fatal("expected ambiguity error for Run")
|
||||
}
|
||||
if !strings.Contains(errorText(errRes), "ambiguous") {
|
||||
t.Errorf("expected 'ambiguous' in error, got: %s", errorText(errRes))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing symbol errors", func(t *testing.T) {
|
||||
_, _, errRes := srv.resolveLSPSymbolPosition(ctx, f, "Nope", "")
|
||||
if errRes == nil {
|
||||
t.Fatal("expected not-found error for Nope")
|
||||
}
|
||||
if !strings.Contains(errorText(errRes), "not found") {
|
||||
t.Errorf("expected 'not found' in error, got: %s", errorText(errRes))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user