mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-10-11 04:47:58 +00:00
fix(parser): name Elixir structs after their enclosing module
extractElixirStruct hardcoded Name="defstruct", so every Elixir struct surfaced as a symbol literally called "defstruct" — useless for navigation and colliding across modules. Walk ancestors to the enclosing defmodule and name the struct %ModuleName{}. Adds TestExtractElixirStructNamedByModule.
This commit is contained in:
@@ -681,17 +681,52 @@ func extractElixirMacro(n *sitter.Node, content []byte, filename string) *protoc
|
||||
}
|
||||
}
|
||||
|
||||
// extractElixirStruct extracts a struct definition.
|
||||
// extractElixirStruct extracts a struct definition. A defstruct has no name of
|
||||
// its own — it defines the struct of its enclosing module — so name it after
|
||||
// that module (%ModuleName{}) rather than the useless literal "defstruct".
|
||||
func extractElixirStruct(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
||||
// defstruct is typically inside a module, the struct name is the module name
|
||||
// We just mark this as a struct symbol
|
||||
name := "defstruct"
|
||||
if mod := enclosingElixirModuleName(n, content); mod != "" {
|
||||
name = "%" + mod + "{}"
|
||||
}
|
||||
return &protocol.Symbol{
|
||||
Name: "defstruct",
|
||||
Name: name,
|
||||
Kind: protocol.SymbolStruct,
|
||||
Location: NodeLocation(n, filename),
|
||||
}
|
||||
}
|
||||
|
||||
// enclosingElixirModuleName walks ancestors of n to the nearest defmodule call
|
||||
// and returns its module alias (e.g. "MyApp.User"), or "" if none is found.
|
||||
func enclosingElixirModuleName(n *sitter.Node, content []byte) string {
|
||||
for p := n.Parent(); p != nil; p = p.Parent() {
|
||||
if p.Type() != "call" || p.NamedChildCount() < 1 {
|
||||
continue
|
||||
}
|
||||
target := p.NamedChild(0)
|
||||
if target == nil || GetNodeText(target, content) != "defmodule" {
|
||||
continue
|
||||
}
|
||||
args := p.ChildByFieldName("arguments")
|
||||
if args == nil && p.NamedChildCount() >= 2 {
|
||||
args = p.NamedChild(1)
|
||||
}
|
||||
if args == nil {
|
||||
return ""
|
||||
}
|
||||
var moduleName string
|
||||
WalkTree(args, func(node *sitter.Node) bool {
|
||||
if node.Type() == "alias" {
|
||||
moduleName = GetNodeText(node, content)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return moduleName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractElixirProtocol extracts a protocol definition.
|
||||
func extractElixirProtocol(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
||||
// defprotocol ProtocolName do ... end
|
||||
|
||||
@@ -125,6 +125,35 @@ var globalVar = "test"
|
||||
|
||||
// Grouped var/const specs declare multiple names on one spec (var a, b = 1, 2);
|
||||
// every name must surface as its own symbol, not just the first.
|
||||
// An Elixir defstruct must be named after its enclosing module (%Mod{}),
|
||||
// not the useless literal "defstruct".
|
||||
func TestExtractElixirStructNamedByModule(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
defer r.Close()
|
||||
|
||||
content := "defmodule MyApp.User do\n defstruct [:name, :email]\nend\n"
|
||||
result, err := r.Parse(context.Background(), "user.ex", []byte(content))
|
||||
if err != nil {
|
||||
t.Fatalf("parse failed: %v", err)
|
||||
}
|
||||
|
||||
symbols := ExtractSymbols(result.Tree, []byte(content), protocol.LangElixir, "user.ex")
|
||||
var structName string
|
||||
found := false
|
||||
for _, s := range symbols {
|
||||
if s.Kind == protocol.SymbolStruct {
|
||||
structName = s.Name
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("no struct symbol extracted, got: %+v", symbols)
|
||||
}
|
||||
if structName != "%MyApp.User{}" {
|
||||
t.Errorf("struct name = %q, want %q", structName, "%MyApp.User{}")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractGoVarConstMultiName(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
defer r.Close()
|
||||
|
||||
Reference in New Issue
Block a user