package indexer import ( "path/filepath" "strings" "github.com/odvcencio/gotreesitter" "github.com/odvcencio/gotreesitter/grammars" ) // packageQueries maps language names to tree-sitter queries that extract the // package/module declaration. The query must capture the package name as @name. var packageQueries = map[string]string{ "go": `(package_clause (package_identifier) @name)`, "proto": `(package (full_ident) @name)`, "java": `(package_declaration (scoped_identifier) @name)`, "kotlin": `(package_header (identifier) @name)`, "scala": `(package_clause (identifier) @name)`, "rust": `(mod_item name: (identifier) @name)`, "elixir": `(call target: (dot left: (alias) @name))`, // defmodule "erlang": `(module_attribute name: (atom) @name)`, } // ExtractPackage extracts the package/module name from source code. // Falls back to deriving from the file path if no language-specific query exists // or the query finds no match. func ExtractPackage(src []byte, filePath string, entry *grammars.LangEntry) string { if queryStr, ok := packageQueries[entry.Name]; ok { lang := entry.Language() if pkg := runPackageQuery(src, lang, queryStr); pkg != "" { return pkg } } // Fallback: derive from directory name dir := filepath.Dir(filePath) if dir == "." || dir == "" { return "" } return filepath.Base(dir) } func runPackageQuery(src []byte, lang *gotreesitter.Language, queryStr string) string { parser := gotreesitter.NewParser(lang) tree, err := parser.Parse(src) if err != nil || tree == nil || tree.RootNode() == nil { return "" } defer tree.Release() query, err := gotreesitter.NewQuery(queryStr, lang) if err != nil { return "" } cursor := query.Exec(tree.RootNode(), lang, src) for { match, ok := cursor.NextMatch() if !ok { break } for _, cap := range match.Captures { if cap.Name == "name" { return cap.Node.Text(src) } } } return "" } // IsExported determines if a symbol name is exported/public based on language conventions. func IsExported(name string, langName string) bool { if name == "" { return false } switch langName { case "go": // Go: exported if first letter is uppercase return name[0] >= 'A' && name[0] <= 'Z' case "python": // Python: private if starts with underscore return !strings.HasPrefix(name, "_") case "rust": // Rust: pub is in the AST, but we approximate: starts with uppercase for types // For functions, we can't tell without `pub` keyword — default to true return true default: // Most languages (JS/TS/Java/etc): export/public is a modifier in the AST // We can't reliably determine from name alone — default to nil/unknown return true } }