mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-10-18 05:48:00 +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 {
|
func extractElixirStruct(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
||||||
// defstruct is typically inside a module, the struct name is the module name
|
name := "defstruct"
|
||||||
// We just mark this as a struct symbol
|
if mod := enclosingElixirModuleName(n, content); mod != "" {
|
||||||
|
name = "%" + mod + "{}"
|
||||||
|
}
|
||||||
return &protocol.Symbol{
|
return &protocol.Symbol{
|
||||||
Name: "defstruct",
|
Name: name,
|
||||||
Kind: protocol.SymbolStruct,
|
Kind: protocol.SymbolStruct,
|
||||||
Location: NodeLocation(n, filename),
|
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.
|
// extractElixirProtocol extracts a protocol definition.
|
||||||
func extractElixirProtocol(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
func extractElixirProtocol(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
||||||
// defprotocol ProtocolName do ... end
|
// 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);
|
// 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.
|
// 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) {
|
func TestExtractGoVarConstMultiName(t *testing.T) {
|
||||||
r := NewRegistry()
|
r := NewRegistry()
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|||||||
Reference in New Issue
Block a user