mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-10-18 05:48:00 +00:00
fix(parser): extract C++ in-class methods correctly
extractCFunction/extractCFunctionDecl scanned for the first 'identifier' in the declarator, which for C++ methods grabbed a parameter name (e.g. method setName surfaced as 'n') or, when the name was a field_identifier with no parameter identifier, dropped the symbol entirely. cDeclaratorName now descends the declarator field-chain to the real name (identifier/field_identifier/qualified_identifier/destructor/operator); in-class definitions are kinded as methods and qualified Class::method (out-of-line Class::method definitions detected via '::'). Adds TestExtractCppMethods.
This commit is contained in:
+59
-21
@@ -1,6 +1,8 @@
|
|||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/lukaszraczylo/mcp-filepuff/pkg/protocol"
|
"github.com/lukaszraczylo/mcp-filepuff/pkg/protocol"
|
||||||
sitter "github.com/smacker/go-tree-sitter"
|
sitter "github.com/smacker/go-tree-sitter"
|
||||||
)
|
)
|
||||||
@@ -425,29 +427,65 @@ func extractCSymbols(root *sitter.Node, content []byte, filename string) []proto
|
|||||||
return symbols
|
return symbols
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cDeclaratorName descends the declarator field-chain (pointer/reference/array/
|
||||||
|
// function declarators) to the innermost name node and returns its text. Unlike a
|
||||||
|
// first-identifier scan, this returns the function/method NAME (not a parameter)
|
||||||
|
// and handles C++ method names (field_identifier), out-of-line definitions
|
||||||
|
// (qualified_identifier like User::foo), destructors and operators.
|
||||||
|
func cDeclaratorName(declarator *sitter.Node, content []byte) string {
|
||||||
|
node := declarator
|
||||||
|
for node != nil {
|
||||||
|
switch node.Type() {
|
||||||
|
case "identifier", "field_identifier", "qualified_identifier",
|
||||||
|
"destructor_name", "operator_name":
|
||||||
|
return GetNodeText(node, content)
|
||||||
|
}
|
||||||
|
next := node.ChildByFieldName("declarator")
|
||||||
|
if next == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
node = next
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// enclosingCppClassName returns the name of the nearest class/struct ancestor of
|
||||||
|
// n (for qualifying in-class method names), or "" if n is not inside one.
|
||||||
|
func enclosingCppClassName(n *sitter.Node, content []byte) string {
|
||||||
|
for p := n.Parent(); p != nil; p = p.Parent() {
|
||||||
|
if p.Type() == "class_specifier" || p.Type() == "struct_specifier" {
|
||||||
|
if name := p.ChildByFieldName("name"); name != nil {
|
||||||
|
return GetNodeText(name, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func extractCFunction(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
func extractCFunction(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
||||||
declarator := n.ChildByFieldName("declarator")
|
declarator := n.ChildByFieldName("declarator")
|
||||||
if declarator == nil {
|
if declarator == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the function name within the declarator
|
name := cDeclaratorName(declarator, content)
|
||||||
var name string
|
|
||||||
WalkTree(declarator, func(node *sitter.Node) bool {
|
|
||||||
if node.Type() == "identifier" {
|
|
||||||
name = GetNodeText(node, content)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In-class definitions are methods; qualify the name with the class. Out-of-line
|
||||||
|
// definitions already carry a qualified name (Class::method).
|
||||||
|
kind := protocol.SymbolFunction
|
||||||
|
if cls := enclosingCppClassName(n, content); cls != "" {
|
||||||
|
kind = protocol.SymbolMethod
|
||||||
|
name = cls + "::" + name
|
||||||
|
} else if strings.Contains(name, "::") {
|
||||||
|
kind = protocol.SymbolMethod
|
||||||
|
}
|
||||||
|
|
||||||
return &protocol.Symbol{
|
return &protocol.Symbol{
|
||||||
Name: name,
|
Name: name,
|
||||||
Kind: protocol.SymbolFunction,
|
Kind: kind,
|
||||||
Location: NodeLocation(n, filename),
|
Location: NodeLocation(n, filename),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,22 +522,22 @@ func extractCFunctionDecl(n *sitter.Node, content []byte, filename string) *prot
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var name string
|
name := cDeclaratorName(declarator, content)
|
||||||
WalkTree(declarator, func(node *sitter.Node) bool {
|
|
||||||
if node.Type() == "identifier" {
|
|
||||||
name = GetNodeText(node, content)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kind := protocol.SymbolFunction
|
||||||
|
if cls := enclosingCppClassName(n, content); cls != "" {
|
||||||
|
kind = protocol.SymbolMethod
|
||||||
|
name = cls + "::" + name
|
||||||
|
} else if strings.Contains(name, "::") {
|
||||||
|
kind = protocol.SymbolMethod
|
||||||
|
}
|
||||||
|
|
||||||
return &protocol.Symbol{
|
return &protocol.Symbol{
|
||||||
Name: name,
|
Name: name,
|
||||||
Kind: protocol.SymbolFunction,
|
Kind: kind,
|
||||||
Location: NodeLocation(n, filename),
|
Location: NodeLocation(n, filename),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,36 @@ 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.
|
||||||
|
// C++ in-class methods must be extracted as methods qualified by their class
|
||||||
|
// (Class::method), not misnamed after a parameter or dropped entirely.
|
||||||
|
func TestExtractCppMethods(t *testing.T) {
|
||||||
|
r := NewRegistry()
|
||||||
|
defer r.Close()
|
||||||
|
content := "class User {\npublic:\n void setName(const char* n) { name_ = n; }\n int id() const { return id_; }\nprivate:\n const char* name_;\n int id_;\n};\n\nvoid freeFunc() {}\n"
|
||||||
|
result, err := r.Parse(context.Background(), "u.cpp", []byte(content))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse: %v", err)
|
||||||
|
}
|
||||||
|
syms := ExtractSymbols(result.Tree, []byte(content), protocol.LangCpp, "u.cpp")
|
||||||
|
|
||||||
|
kindByName := make(map[string]protocol.SymbolKind, len(syms))
|
||||||
|
for _, s := range syms {
|
||||||
|
kindByName[s.Name] = s.Kind
|
||||||
|
}
|
||||||
|
if kindByName["User::setName"] != protocol.SymbolMethod {
|
||||||
|
t.Errorf("setName: want method User::setName, got %v", kindByName)
|
||||||
|
}
|
||||||
|
if kindByName["User::id"] != protocol.SymbolMethod {
|
||||||
|
t.Errorf("id: want method User::id, got %v", kindByName)
|
||||||
|
}
|
||||||
|
if kindByName["freeFunc"] != protocol.SymbolFunction {
|
||||||
|
t.Errorf("freeFunc: want function, got %v", kindByName)
|
||||||
|
}
|
||||||
|
if _, leaked := kindByName["n"]; leaked {
|
||||||
|
t.Errorf("parameter 'n' leaked as a symbol: %v", kindByName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function-local var/const declarations must NOT appear as top-level symbols
|
// Function-local var/const declarations must NOT appear as top-level symbols
|
||||||
// (they flood the list and aren't package/module symbols).
|
// (they flood the list and aren't package/module symbols).
|
||||||
func TestExtractSymbolsExcludesLocals(t *testing.T) {
|
func TestExtractSymbolsExcludesLocals(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user