Files
filepuff-mcp/internal/parser/symbols.go
T
2026-01-18 18:40:26 +00:00

475 lines
12 KiB
Go

package parser
import (
"github.com/lukaszraczylo/mcp-filepuff/pkg/protocol"
sitter "github.com/smacker/go-tree-sitter"
)
// ExtractSymbols extracts symbols from a parsed tree.
func ExtractSymbols(tree *sitter.Tree, content []byte, lang protocol.Language, filename string) []protocol.Symbol {
if tree == nil {
return nil
}
root := tree.RootNode()
if root == nil {
return nil
}
switch lang {
case protocol.LangGo:
return extractGoSymbols(root, content, filename)
case protocol.LangTypeScript, protocol.LangJavaScript:
return extractJSSymbols(root, content, filename)
case protocol.LangPython:
return extractPythonSymbols(root, content, filename)
case protocol.LangC, protocol.LangCpp:
return extractCSymbols(root, content, filename)
default:
return nil
}
}
// extractGoSymbols extracts symbols from Go code.
func extractGoSymbols(root *sitter.Node, content []byte, filename string) []protocol.Symbol {
var symbols []protocol.Symbol
WalkTree(root, func(n *sitter.Node) bool {
var symbol *protocol.Symbol
switch n.Type() {
case "function_declaration":
symbol = extractGoFunction(n, content, filename)
case "method_declaration":
symbol = extractGoMethod(n, content, filename)
case "type_declaration":
symbol = extractGoType(n, content, filename)
case "const_declaration", "var_declaration":
syms := extractGoVarConst(n, content, filename)
symbols = append(symbols, syms...)
return true
}
if symbol != nil {
if doc := ExtractDocComment(n, content, protocol.LangGo); doc != nil {
symbol.Doc = FormatDocComment(doc)
}
symbols = append(symbols, *symbol)
}
return true
})
return symbols
}
func extractGoFunction(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
nameNode := n.ChildByFieldName("name")
if nameNode == nil {
return nil
}
return &protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: protocol.SymbolFunction,
Location: NodeLocation(n, filename),
}
}
func extractGoMethod(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
nameNode := n.ChildByFieldName("name")
if nameNode == nil {
return nil
}
// Get receiver type
receiver := n.ChildByFieldName("receiver")
receiverType := ""
if receiver != nil {
// Find the type in the receiver
WalkTree(receiver, func(node *sitter.Node) bool {
if node.Type() == "type_identifier" {
receiverType = GetNodeText(node, content)
return false
}
return true
})
}
name := GetNodeText(nameNode, content)
if receiverType != "" {
name = "(" + receiverType + ")." + name
}
return &protocol.Symbol{
Name: name,
Kind: protocol.SymbolMethod,
Location: NodeLocation(n, filename),
}
}
func extractGoType(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
// Find type_spec child
for i := 0; i < int(n.NamedChildCount()); i++ {
child := n.NamedChild(i)
if child != nil && child.Type() == "type_spec" {
nameNode := child.ChildByFieldName("name")
if nameNode == nil {
continue
}
kind := protocol.SymbolType
typeNode := child.ChildByFieldName("type")
if typeNode != nil {
switch typeNode.Type() {
case "struct_type":
kind = protocol.SymbolStruct
case "interface_type":
kind = protocol.SymbolInterface
}
}
return &protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: kind,
Location: NodeLocation(child, filename),
}
}
}
return nil
}
func extractGoVarConst(n *sitter.Node, content []byte, filename string) []protocol.Symbol {
var symbols []protocol.Symbol
kind := protocol.SymbolVariable
if n.Type() == "const_declaration" {
kind = protocol.SymbolConstant
}
WalkTree(n, func(node *sitter.Node) bool {
if node.Type() == "const_spec" || node.Type() == "var_spec" {
nameNode := node.ChildByFieldName("name")
if nameNode != nil {
symbols = append(symbols, protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: kind,
Location: NodeLocation(node, filename),
})
}
}
return true
})
return symbols
}
// extractJSSymbols extracts symbols from JavaScript/TypeScript code.
func extractJSSymbols(root *sitter.Node, content []byte, filename string) []protocol.Symbol {
var symbols []protocol.Symbol
WalkTree(root, func(n *sitter.Node) bool {
var symbol *protocol.Symbol
switch n.Type() {
case "function_declaration":
symbol = extractJSFunction(n, content, filename)
case "class_declaration":
symbol = extractJSClass(n, content, filename)
case "method_definition":
symbol = extractJSMethod(n, content, filename)
case "lexical_declaration", "variable_declaration":
syms := extractJSVariable(n, content, filename)
symbols = append(symbols, syms...)
return true
case "interface_declaration":
symbol = extractTSInterface(n, content, filename)
case "type_alias_declaration":
symbol = extractTSTypeAlias(n, content, filename)
}
if symbol != nil {
if doc := ExtractDocComment(n, content, protocol.LangJavaScript); doc != nil {
symbol.Doc = FormatDocComment(doc)
}
symbols = append(symbols, *symbol)
}
return true
})
return symbols
}
func extractJSFunction(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
nameNode := n.ChildByFieldName("name")
if nameNode == nil {
return nil
}
return &protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: protocol.SymbolFunction,
Location: NodeLocation(n, filename),
}
}
func extractJSClass(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
nameNode := n.ChildByFieldName("name")
if nameNode == nil {
return nil
}
return &protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: protocol.SymbolClass,
Location: NodeLocation(n, filename),
}
}
func extractJSMethod(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
nameNode := n.ChildByFieldName("name")
if nameNode == nil {
return nil
}
return &protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: protocol.SymbolMethod,
Location: NodeLocation(n, filename),
}
}
func extractJSVariable(n *sitter.Node, content []byte, filename string) []protocol.Symbol {
var symbols []protocol.Symbol
WalkTree(n, func(node *sitter.Node) bool {
if node.Type() == "variable_declarator" {
nameNode := node.ChildByFieldName("name")
if nameNode != nil {
symbols = append(symbols, protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: protocol.SymbolVariable,
Location: NodeLocation(node, filename),
})
}
}
return true
})
return symbols
}
func extractTSInterface(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
nameNode := n.ChildByFieldName("name")
if nameNode == nil {
return nil
}
return &protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: protocol.SymbolInterface,
Location: NodeLocation(n, filename),
}
}
func extractTSTypeAlias(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
nameNode := n.ChildByFieldName("name")
if nameNode == nil {
return nil
}
return &protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: protocol.SymbolType,
Location: NodeLocation(n, filename),
}
}
// extractPythonSymbols extracts symbols from Python code.
func extractPythonSymbols(root *sitter.Node, content []byte, filename string) []protocol.Symbol {
var symbols []protocol.Symbol
WalkTree(root, func(n *sitter.Node) bool {
var symbol *protocol.Symbol
switch n.Type() {
case "function_definition":
symbol = extractPythonFunction(n, content, filename)
case "class_definition":
symbol = extractPythonClass(n, content, filename)
}
if symbol != nil {
if doc := ExtractDocComment(n, content, protocol.LangPython); doc != nil {
symbol.Doc = FormatDocComment(doc)
}
symbols = append(symbols, *symbol)
}
return true
})
return symbols
}
func extractPythonFunction(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
nameNode := n.ChildByFieldName("name")
if nameNode == nil {
return nil
}
// Check if this is a method (inside a class)
parent := n.Parent()
kind := protocol.SymbolFunction
if parent != nil && parent.Type() == "block" {
grandparent := parent.Parent()
if grandparent != nil && grandparent.Type() == "class_definition" {
kind = protocol.SymbolMethod
}
}
return &protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: kind,
Location: NodeLocation(n, filename),
}
}
func extractPythonClass(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
nameNode := n.ChildByFieldName("name")
if nameNode == nil {
return nil
}
return &protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: protocol.SymbolClass,
Location: NodeLocation(n, filename),
}
}
// extractCSymbols extracts symbols from C/C++ code.
func extractCSymbols(root *sitter.Node, content []byte, filename string) []protocol.Symbol {
var symbols []protocol.Symbol
WalkTree(root, func(n *sitter.Node) bool {
var symbol *protocol.Symbol
switch n.Type() {
case "function_definition":
symbol = extractCFunction(n, content, filename)
case "struct_specifier":
symbol = extractCStruct(n, content, filename)
case "class_specifier":
symbol = extractCppClass(n, content, filename)
case "declaration":
// Could be function declaration or variable
if hasFunctionDeclarator(n) {
symbol = extractCFunctionDecl(n, content, filename)
}
}
if symbol != nil {
if doc := ExtractDocComment(n, content, protocol.LangC); doc != nil {
symbol.Doc = FormatDocComment(doc)
}
symbols = append(symbols, *symbol)
}
return true
})
return symbols
}
func extractCFunction(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
declarator := n.ChildByFieldName("declarator")
if declarator == nil {
return nil
}
// Find the function name within the declarator
var name string
WalkTree(declarator, func(node *sitter.Node) bool {
if node.Type() == "identifier" {
name = GetNodeText(node, content)
return false
}
return true
})
if name == "" {
return nil
}
return &protocol.Symbol{
Name: name,
Kind: protocol.SymbolFunction,
Location: NodeLocation(n, filename),
}
}
func extractCStruct(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
nameNode := n.ChildByFieldName("name")
if nameNode == nil {
return nil
}
return &protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: protocol.SymbolStruct,
Location: NodeLocation(n, filename),
}
}
func extractCppClass(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
nameNode := n.ChildByFieldName("name")
if nameNode == nil {
return nil
}
return &protocol.Symbol{
Name: GetNodeText(nameNode, content),
Kind: protocol.SymbolClass,
Location: NodeLocation(n, filename),
}
}
func extractCFunctionDecl(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
declarator := n.ChildByFieldName("declarator")
if declarator == nil {
return nil
}
var name string
WalkTree(declarator, func(node *sitter.Node) bool {
if node.Type() == "identifier" {
name = GetNodeText(node, content)
return false
}
return true
})
if name == "" {
return nil
}
return &protocol.Symbol{
Name: name,
Kind: protocol.SymbolFunction,
Location: NodeLocation(n, filename),
}
}
func hasFunctionDeclarator(n *sitter.Node) bool {
found := false
WalkTree(n, func(node *sitter.Node) bool {
if node.Type() == "function_declarator" {
found = true
return false
}
return true
})
return found
}