#include "symbol_extractor.h" #include "grammar_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include } namespace yams::plugins::treesitter { // ============================================================================ // Language Configuration - Constexpr Node Type Tables // ============================================================================ // Maximum sizes for constexpr arrays inline constexpr size_t MaxNodeTypes = 8; inline constexpr size_t MaxQueries = 16; // Fixed-size array wrapper for constexpr compatibility template struct ConstList { std::array items{}; size_t count = 0; constexpr bool contains(std::string_view item) const noexcept { for (size_t i = 0; i < count; ++i) { if (items[i] == item) return true; } return false; } constexpr bool matches(const char* cstr) const noexcept { if (!cstr) return false; return contains(std::string_view(cstr)); } constexpr std::string_view operator[](size_t i) const noexcept { return i < count ? items[i] : std::string_view{}; } constexpr auto begin() const noexcept { return items.begin(); } constexpr auto end() const noexcept { return items.begin() + count; } }; // Type aliases for clarity using NodeTypeList = ConstList; using QueryList = ConstList; // Language configuration with all extraction settings struct LanguageConfig { std::string_view name; std::array aliases; size_t alias_count = 0; // Node types for AST traversal fallback NodeTypeList class_types; NodeTypeList field_types; NodeTypeList function_types; NodeTypeList import_types; NodeTypeList identifier_types; // Tree-sitter query patterns QueryList function_queries; QueryList class_queries; QueryList import_queries; QueryList call_queries; }; // Best-effort scope computation for C++ (namespace/class/struct/enum nesting). static std::string compute_cpp_scope(std::string_view content, TSNode node) { auto extract_text = [&](TSNode n) -> std::string { if (ts_node_is_null(n)) return ""; uint32_t start_byte = ts_node_start_byte(n); uint32_t end_byte = ts_node_end_byte(n); if (start_byte >= end_byte || end_byte > content.length()) return ""; return std::string(content.substr(start_byte, end_byte - start_byte)); }; std::vector parts; std::unordered_set seen; TSNode current = node; while (!ts_node_is_null(current)) { const char* type = ts_node_type(current); if (!type) { current = ts_node_parent(current); continue; } if (std::strcmp(type, "namespace_definition") == 0 || std::strcmp(type, "class_specifier") == 0 || std::strcmp(type, "struct_specifier") == 0 || std::strcmp(type, "union_specifier") == 0 || std::strcmp(type, "enum_specifier") == 0) { TSNode name_node = ts_node_child_by_field_name(current, "name", 4); if (!ts_node_is_null(name_node)) { std::string name = extract_text(name_node); if (!name.empty() && seen.insert(name).second) { parts.push_back(std::move(name)); } } } current = ts_node_parent(current); } if (parts.empty()) { return {}; } std::string scope; for (auto it = parts.rbegin(); it != parts.rend(); ++it) { if (!scope.empty()) { scope += "::"; } scope += *it; } return scope; } // Helper to create lists at compile time template constexpr auto makeList(Args... args) -> ConstList { ConstList result; result.count = sizeof...(args); result.items = {args...}; return result; } // Convenience wrappers template constexpr auto makeNodeTypes(Args... args) { return makeList(args...); } template constexpr auto makeQueries(Args... args) { return makeList(args...); } // ============================================================================ // Per-Language Configurations // ============================================================================ inline constexpr LanguageConfig lang_cpp = { .name = "cpp", .aliases = {"c++", "cxx", "cc"}, .alias_count = 3, .class_types = makeNodeTypes("class_specifier", "struct_specifier", "union_specifier", "enum_specifier"), .field_types = makeNodeTypes("field_declaration"), .function_types = makeNodeTypes("function_definition", "function_declarator", "template_declaration"), .import_types = makeNodeTypes("preproc_include", "preproc_import"), .identifier_types = makeNodeTypes("identifier", "type_identifier", "field_identifier"), .function_queries = makeQueries( "(function_definition declarator: (function_declarator declarator: (identifier) @name))", "(function_definition declarator: (function_declarator declarator: (qualified_identifier " "name: (identifier) @name)))", "(function_definition declarator: (function_declarator declarator: (field_identifier) " "@name))", "(field_declaration declarator: (function_declarator declarator: (field_identifier) " "@name))", "(field_declaration declarator: (function_declarator declarator: (identifier) @name))", "(declaration declarator: (function_declarator declarator: (identifier) @name))", "(function_definition declarator: (function_declarator declarator: (destructor_name) " "@name))", "(template_declaration (function_definition declarator: (function_declarator declarator: " "(identifier) @name)))"), .class_queries = makeQueries("(class_specifier name: (type_identifier) @name)", "(struct_specifier name: (type_identifier) @name)", "(union_specifier name: (type_identifier) @name)", "(enum_specifier name: (type_identifier) @name)", "(template_declaration (class_specifier name: (type_identifier) @name))", "(template_declaration (struct_specifier name: (type_identifier) @name))"), .import_queries = makeQueries("(preproc_include path: (string_literal) @path)", "(preproc_include path: (system_lib_string) @path)"), .call_queries = makeQueries( "(call_expression function: (identifier) @callee) @call", "(call_expression function: (field_expression field: (field_identifier) @callee)) @call", "(call_expression function: (qualified_identifier) @callee) @call"), }; inline constexpr LanguageConfig lang_c = { .name = "c", .aliases = {}, .alias_count = 0, .class_types = makeNodeTypes("struct_specifier", "union_specifier", "enum_specifier"), .field_types = makeNodeTypes("field_declaration"), .function_types = makeNodeTypes("function_definition", "function_declarator"), .import_types = makeNodeTypes("preproc_include"), .identifier_types = makeNodeTypes("identifier", "type_identifier", "field_identifier"), .function_queries = makeQueries( "(function_definition declarator: (function_declarator declarator: (identifier) @name))", "(declaration declarator: (function_declarator declarator: (identifier) @name))"), .class_queries = makeQueries("(struct_specifier name: (type_identifier) @name)", "(union_specifier name: (type_identifier) @name)", "(enum_specifier name: (type_identifier) @name)"), .import_queries = makeQueries("(preproc_include path: (string_literal) @path)", "(preproc_include path: (system_lib_string) @path)"), .call_queries = makeQueries( "(call_expression function: (identifier) @callee) @call", "(call_expression function: (field_expression field: (field_identifier) @callee)) @call"), }; inline constexpr LanguageConfig lang_python = { .name = "python", .aliases = {"py"}, .alias_count = 1, .class_types = makeNodeTypes("class_definition"), .field_types = makeNodeTypes("attribute", "assignment"), .function_types = makeNodeTypes("function_definition", "decorated_definition"), .import_types = makeNodeTypes("import_statement", "import_from_statement"), .identifier_types = makeNodeTypes("identifier"), .function_queries = makeQueries("(function_definition name: (identifier) @name)", "(decorated_definition (function_definition name: (identifier) @name))"), .class_queries = makeQueries("(class_definition name: (identifier) @name)", "(decorated_definition (class_definition name: (identifier) @name))"), .import_queries = makeQueries("(import_statement name: (dotted_name) @module)", "(import_from_statement module_name: (dotted_name) @module)", "(import_from_statement module_name: (relative_import) @module)"), .call_queries = makeQueries("(call function: (identifier) @callee) @call", "(call function: (attribute attribute: (identifier) @callee)) @call"), }; inline constexpr LanguageConfig lang_rust = { .name = "rust", .aliases = {"rs"}, .alias_count = 1, .class_types = makeNodeTypes("struct_item", "enum_item", "trait_item", "impl_item"), .field_types = makeNodeTypes("field_declaration", "field_pattern"), .function_types = makeNodeTypes("function_item", "function_signature_item"), .import_types = makeNodeTypes("use_declaration"), .identifier_types = makeNodeTypes("identifier", "type_identifier", "field_identifier"), .function_queries = makeQueries("(function_item name: (identifier) @name)", "(function_signature_item name: (identifier) @name)", "(impl_item (function_item name: (identifier) @name))", "(trait_item (function_signature_item name: (identifier) @name))"), .class_queries = makeQueries("(struct_item name: (type_identifier) @name)", "(enum_item name: (type_identifier) @name)", "(trait_item name: (type_identifier) @name)"), .import_queries = makeQueries("(use_declaration argument: (scoped_identifier) @path)", "(use_declaration argument: (identifier) @path)", "(extern_crate_declaration name: (identifier) @path)"), .call_queries = makeQueries( "(call_expression function: (identifier) @callee) @call", "(call_expression function: (field_expression field: (field_identifier) @callee)) @call"), }; inline constexpr LanguageConfig lang_go = { .name = "go", .aliases = {"golang"}, .alias_count = 1, .class_types = makeNodeTypes("type_spec", "type_declaration"), .field_types = makeNodeTypes("field_declaration"), .function_types = makeNodeTypes("function_declaration", "method_declaration"), .import_types = makeNodeTypes("import_declaration", "import_spec"), .identifier_types = makeNodeTypes("identifier", "type_identifier", "field_identifier"), .function_queries = makeQueries("(function_declaration name: (identifier) @name)", "(method_declaration name: (field_identifier) @name)"), .class_queries = makeQueries("(type_declaration (type_spec name: (type_identifier) @name))"), .import_queries = makeQueries("(import_declaration (import_spec path: (interpreted_string_literal) @path))", "(import_spec path: (interpreted_string_literal) @path)"), .call_queries = makeQueries("(call_expression function: (identifier) @callee) @call", "(call_expression function: (selector_expression field: " "(field_identifier) @callee)) @call"), }; inline constexpr LanguageConfig lang_java = { .name = "java", .aliases = {}, .alias_count = 0, .class_types = makeNodeTypes("class_declaration", "interface_declaration", "enum_declaration", "annotation_type_declaration"), .field_types = makeNodeTypes("field_declaration"), .function_types = makeNodeTypes("method_declaration", "constructor_declaration"), .import_types = makeNodeTypes("import_declaration"), .identifier_types = makeNodeTypes("identifier"), .function_queries = makeQueries("(method_declaration name: (identifier) @name)", "(constructor_declaration name: (identifier) @name)"), .class_queries = makeQueries("(class_declaration name: (identifier) @name)", "(interface_declaration name: (identifier) @name)", "(enum_declaration name: (identifier) @name)", "(annotation_type_declaration name: (identifier) @name)"), .import_queries = makeQueries("(import_declaration (scoped_identifier) @path)"), .call_queries = makeQueries("(method_invocation name: (identifier) @callee) @call"), }; inline constexpr LanguageConfig lang_javascript = { .name = "javascript", .aliases = {"js"}, .alias_count = 1, .class_types = makeNodeTypes("class_declaration", "class", "class_static_block"), .field_types = makeNodeTypes("public_field_definition", "field_definition", "property_signature"), .function_types = makeNodeTypes("function_declaration", "function_expression", "arrow_function", "method_definition", "generator_function", "generator_function_declaration"), .import_types = makeNodeTypes("import_statement", "import_specifier", "namespace_import", "export_statement", "export_specifier"), .identifier_types = makeNodeTypes("identifier", "property_identifier"), .function_queries = makeQueries("(function_declaration name: (identifier) @name)", "(function_expression name: (identifier) @name)", "(method_definition name: (property_identifier) @name)", "(variable_declarator name: (identifier) @name value: (arrow_function))", "(variable_declarator name: (identifier) @name value: (function_expression))", "(generator_function_declaration name: (identifier) @name)", "(generator_function name: (identifier) @name)"), .class_queries = makeQueries("(class_declaration name: (identifier) @name)", "(class name: (identifier) @name)"), .import_queries = makeQueries("(import_statement source: (string) @source)", "(export_statement source: (string) @source)", "(call_expression function: (import) arguments: (arguments (string) @source))"), .call_queries = makeQueries("(call_expression function: (identifier) @callee) @call", "(call_expression function: (member_expression property: " "(property_identifier) @callee)) @call"), }; inline constexpr LanguageConfig lang_typescript = { .name = "typescript", .aliases = {"ts"}, .alias_count = 1, .class_types = makeNodeTypes("class_declaration", "class", "interface_declaration", "type_alias_declaration", "abstract_class_declaration"), .field_types = makeNodeTypes("public_field_definition", "field_definition", "property_signature"), .function_types = makeNodeTypes("function_declaration", "function_expression", "arrow_function", "method_definition", "method_signature", "abstract_method_signature", "generator_function"), .import_types = makeNodeTypes("import_statement", "import_specifier", "namespace_import", "export_statement", "import_alias"), .identifier_types = makeNodeTypes("identifier", "property_identifier", "type_identifier"), .function_queries = makeQueries("(function_declaration name: (identifier) @name)", "(function_expression name: (identifier) @name)", "(method_definition name: (property_identifier) @name)", "(variable_declarator name: (identifier) @name value: (arrow_function))", "(variable_declarator name: (identifier) @name value: (function_expression))", "(method_signature name: (property_identifier) @name)", "(abstract_method_signature name: (property_identifier) @name)"), .class_queries = makeQueries("(class_declaration name: (identifier) @name)", "(class name: (identifier) @name)", "(abstract_class_declaration name: (identifier) @name)", "(interface_declaration name: (type_identifier) @name)", "(type_alias_declaration name: (type_identifier) @name)", "(enum_declaration name: (identifier) @name)"), .import_queries = makeQueries("(import_statement source: (string) @source)", "(export_statement source: (string) @source)", "(call_expression function: (import) arguments: (arguments (string) @source))"), .call_queries = makeQueries("(call_expression function: (identifier) @callee) @call", "(call_expression function: (member_expression property: " "(property_identifier) @callee)) @call"), }; inline constexpr LanguageConfig lang_csharp = { .name = "csharp", .aliases = {"c#", "cs"}, .alias_count = 2, .class_types = makeNodeTypes("class_declaration", "struct_declaration", "interface_declaration", "enum_declaration"), .field_types = makeNodeTypes("field_declaration", "property_declaration"), .function_types = makeNodeTypes("method_declaration", "constructor_declaration", "local_function_statement"), .import_types = makeNodeTypes("using_directive"), .identifier_types = makeNodeTypes("identifier"), .function_queries = makeQueries("(method_declaration name: (identifier) @name)", "(constructor_declaration name: (identifier) @name)", "(destructor_declaration name: (identifier) @name)", "(property_declaration name: (identifier) @name)"), .class_queries = makeQueries("(class_declaration name: (identifier) @name)", "(struct_declaration name: (identifier) @name)", "(interface_declaration name: (identifier) @name)", "(enum_declaration name: (identifier) @name)", "(record_declaration name: (identifier) @name)"), .import_queries = makeQueries("(using_directive (identifier) @path)", "(using_directive (qualified_name) @path)"), .call_queries = makeQueries("(invocation_expression function: (identifier) @callee) @call", "(invocation_expression function: (member_access_expression name: " "(identifier) @callee)) @call"), }; inline constexpr LanguageConfig lang_php = { .name = "php", .aliases = {}, .alias_count = 0, .class_types = makeNodeTypes("class_declaration", "interface_declaration", "trait_declaration"), .field_types = makeNodeTypes("property_declaration", "property_element"), .function_types = makeNodeTypes("function_definition", "method_declaration"), .import_types = makeNodeTypes("namespace_use_declaration", "use_declaration"), .identifier_types = makeNodeTypes("name", "variable_name"), .function_queries = makeQueries("(function_definition name: (name) @name)", "(method_declaration name: (name) @name)"), .class_queries = makeQueries( "(class_declaration name: (name) @name)", "(interface_declaration name: (name) @name)", "(trait_declaration name: (name) @name)", "(enum_declaration name: (name) @name)"), .import_queries = makeQueries("(namespace_use_clause (qualified_name) @path)", "(include_expression (string) @path)", "(require_expression (string) @path)"), .call_queries = makeQueries("(function_call_expression function: (name) @callee) @call", "(member_call_expression name: (name) @callee) @call"), }; inline constexpr LanguageConfig lang_kotlin = { .name = "kotlin", .aliases = {"kt"}, .alias_count = 1, .class_types = makeNodeTypes("class_declaration", "object_declaration", "interface_declaration"), .field_types = makeNodeTypes("property_declaration"), .function_types = makeNodeTypes("function_declaration"), .import_types = makeNodeTypes("import_header", "import_list"), .identifier_types = makeNodeTypes("simple_identifier"), .function_queries = makeQueries("(function_declaration (simple_identifier) @name)"), .class_queries = makeQueries("(class_declaration (type_identifier) @name)", "(object_declaration (type_identifier) @name)", "(interface_declaration (type_identifier) @name)"), .import_queries = makeQueries("(import_header (identifier) @path)"), .call_queries = makeQueries("(call_expression (simple_identifier) @callee) @call", "(call_expression (navigation_expression (simple_identifier) @callee)) @call"), }; inline constexpr LanguageConfig lang_perl = { .name = "perl", .aliases = {"pl"}, .alias_count = 1, .class_types = makeNodeTypes("package_statement"), .field_types = makeNodeTypes(), .function_types = makeNodeTypes("subroutine_declaration_statement", "function_definition"), .import_types = makeNodeTypes("use_statement", "require_statement"), .identifier_types = makeNodeTypes("identifier", "scalar_variable"), .function_queries = makeQueries("(subroutine_declaration_statement name: (identifier) @name)"), .class_queries = makeQueries("(package_statement (package_name) @name)"), .import_queries = makeQueries("(use_statement (package_name) @path)"), .call_queries = makeQueries("(function_call_expression (identifier) @callee) @call"), }; inline constexpr LanguageConfig lang_r = { .name = "r", .aliases = {}, .alias_count = 0, .class_types = makeNodeTypes(), .field_types = makeNodeTypes(), .function_types = makeNodeTypes("function_definition"), .import_types = makeNodeTypes("call"), .identifier_types = makeNodeTypes("identifier"), .function_queries = makeQueries("(binary_operator lhs: (identifier) @name operator: \"<-\")", "(binary_operator lhs: (identifier) @name operator: \"=\")"), .class_queries = makeQueries(), .import_queries = makeQueries(), .call_queries = makeQueries("(call function: (identifier) @callee) @call"), }; inline constexpr LanguageConfig lang_sql = { .name = "sql", .aliases = {}, .alias_count = 0, .class_types = makeNodeTypes("create_table_statement"), .field_types = makeNodeTypes("column_definition"), .function_types = makeNodeTypes("create_function_statement", "create_procedure_statement"), .import_types = makeNodeTypes(), .identifier_types = makeNodeTypes("identifier", "object_reference"), .function_queries = makeQueries("(create_function_statement name: (identifier) @name)", "(create_procedure_statement name: (identifier) @name)"), .class_queries = makeQueries("(create_table_statement name: (identifier) @name)"), .import_queries = makeQueries(), .call_queries = makeQueries(), }; inline constexpr LanguageConfig lang_solidity = { .name = "solidity", .aliases = {"sol"}, .alias_count = 1, .class_types = makeNodeTypes("contract_declaration", "interface_declaration", "library_declaration", "struct_declaration"), .field_types = makeNodeTypes("state_variable_declaration"), .function_types = makeNodeTypes("function_definition", "constructor_definition", "modifier_definition"), .import_types = makeNodeTypes("import_directive"), .identifier_types = makeNodeTypes("identifier"), .function_queries = makeQueries("(function_definition name: (identifier) @name)", "(modifier_definition name: (identifier) @name)"), .class_queries = makeQueries("(contract_declaration name: (identifier) @name)", "(interface_declaration name: (identifier) @name)", "(library_declaration name: (identifier) @name)", "(struct_declaration name: (identifier) @name)", "(enum_declaration name: (identifier) @name)", "(event_definition name: (identifier) @name)"), .import_queries = makeQueries("(import_directive source: (string) @path)"), .call_queries = makeQueries( "(call_expression function: (identifier) @callee) @call", "(call_expression function: (member_expression property: (identifier) @callee)) @call"), }; inline constexpr LanguageConfig lang_dart = { .name = "dart", .aliases = {"flutter"}, .alias_count = 1, .class_types = makeNodeTypes("class_definition", "mixin_declaration", "enum_declaration"), .field_types = makeNodeTypes("declaration", "initialized_variable_definition"), .function_types = makeNodeTypes("function_signature", "method_signature", "function_body"), .import_types = makeNodeTypes("import_or_export"), .identifier_types = makeNodeTypes("identifier"), .function_queries = makeQueries("(function_signature name: (identifier) @name)", "(method_signature name: (identifier) @name)", "(getter_signature name: (identifier) @name)", "(setter_signature name: (identifier) @name)"), .class_queries = makeQueries("(class_definition name: (identifier) @name)", "(mixin_declaration name: (identifier) @name)", "(extension_declaration name: (identifier) @name)", "(enum_declaration name: (identifier) @name)"), .import_queries = makeQueries("(import_or_export (string_literal) @path)"), .call_queries = makeQueries("(function_expression_invocation function: (identifier) @callee) @call", "(cascade_selector (identifier) @callee) @call"), }; // P4 (Programming Protocol-independent Packet Processors) - network data plane language inline constexpr LanguageConfig lang_p4 = { .name = "p4", .aliases = {"p4_16", "p4lang"}, .alias_count = 2, .class_types = makeNodeTypes("headerTypeDeclaration", "structTypeDeclaration", "controlDeclaration", "parserDeclaration"), .field_types = makeNodeTypes("structField", "parameter"), .function_types = makeNodeTypes("actionDeclaration", "functionDeclaration", "externDeclaration", "methodPrototype"), .import_types = makeNodeTypes("includeStatement"), .identifier_types = makeNodeTypes("IDENTIFIER", "nonTypeName", "typeName"), .function_queries = makeQueries("(actionDeclaration name: (IDENTIFIER) @name)", "(functionDeclaration (functionPrototype name: (IDENTIFIER) @name))", "(externDeclaration name: (IDENTIFIER) @name)", "(methodPrototype name: (IDENTIFIER) @name)"), .class_queries = makeQueries("(headerTypeDeclaration name: (IDENTIFIER) @name)", "(structTypeDeclaration name: (IDENTIFIER) @name)", "(controlDeclaration name: (IDENTIFIER) @name)", "(parserDeclaration name: (IDENTIFIER) @name)", "(tableDeclaration name: (IDENTIFIER) @name)", "(typedefDeclaration name: (IDENTIFIER) @name)"), .import_queries = makeQueries("(includeStatement (STRING_LITERAL) @path)"), .call_queries = makeQueries("(functionCall function: (IDENTIFIER) @callee) @call", "(methodCallExpression method: (IDENTIFIER) @callee) @call"), }; // Zig - systems programming language with comptime and safety features inline constexpr LanguageConfig lang_zig = { .name = "zig", .aliases = {}, .alias_count = 0, .class_types = makeNodeTypes("container_decl", "container_decl_auto"), .field_types = makeNodeTypes("container_field", "container_field_init"), .function_types = makeNodeTypes("fn_decl", "extern_fn_decl"), .import_types = makeNodeTypes("builtin_call_expr"), .identifier_types = makeNodeTypes("identifier", "field_access"), .function_queries = makeQueries("(fn_decl (fn_proto name: (identifier) @name))", "(extern_fn_decl (fn_proto name: (identifier) @name))"), .class_queries = makeQueries("(var_decl (identifier) @name (container_decl))", "(var_decl (identifier) @name (container_decl_auto))"), .import_queries = makeQueries("(builtin_call_expr (identifier) @builtin " "(call_args (string_literal) @path)) @call"), .call_queries = makeQueries("(call_expr (identifier) @callee) @call", "(call_expr (field_access (identifier) @callee)) @call"), }; // Master configuration table inline constexpr std::array language_configs = { &lang_cpp, &lang_c, &lang_python, &lang_rust, &lang_go, &lang_java, &lang_javascript, &lang_typescript, &lang_csharp, &lang_php, &lang_kotlin, &lang_perl, &lang_r, &lang_sql, &lang_solidity, &lang_dart, &lang_p4, &lang_zig, }; // Lookup function - returns nullptr if language not found constexpr const LanguageConfig* getLanguageConfig(std::string_view language) noexcept { for (const auto* config : language_configs) { if (config->name == language) return config; for (size_t i = 0; i < config->alias_count; ++i) { if (config->aliases[i] == language) return config; } } return nullptr; } // ============================================================================ // Helper: Extract symbols by node type (fallback when queries don't work) // ============================================================================ SymbolExtractor::Result SymbolExtractor::extractSymbolsByNodeType(const ExtractionContext& ctx, std::string_view symbol_kind) { ExtractionResult result; const auto* config = getLanguageConfig(ctx.language); if (!config) return result; const NodeTypeList* type_list = nullptr; if (symbol_kind == "function") type_list = &config->function_types; else if (symbol_kind == "class") type_list = &config->class_types; else if (symbol_kind == "field") type_list = &config->field_types; if (!type_list || type_list->count == 0) return result; // Find identifier in node auto find_identifier = [&config](TSNode n) -> TSNode { std::function search = [&](TSNode node) -> TSNode { if (ts_node_is_null(node)) return TSNode{}; const char* type = ts_node_type(node); if (config->identifier_types.matches(type)) return node; uint32_t child_count = ts_node_child_count(node); for (uint32_t i = 0; i < child_count; ++i) { TSNode found = search(ts_node_child(node, i)); if (!ts_node_is_null(found)) return found; } return TSNode{}; }; return search(n); }; // Traverse AST std::function traverse = [&](TSNode node) { if (ts_node_is_null(node)) return; const char* node_type = ts_node_type(node); if (type_list->matches(node_type)) { TSNode name_node = find_identifier(node); if (!ts_node_is_null(name_node)) { SymbolInfo sym; sym.name = ctx.extractNodeText(name_node); sym.scope = compute_cpp_scope(ctx.content, node); sym.qualified_name = sym.scope.empty() ? sym.name : sym.scope + "::" + sym.name; sym.kind = std::string(symbol_kind); sym.file_path = std::string(ctx.file_path); TSPoint start = ts_node_start_point(node); TSPoint end = ts_node_end_point(node); sym.start_line = start.row + 1; sym.end_line = end.row + 1; sym.start_offset = ts_node_start_byte(node); sym.end_offset = ts_node_end_byte(node); if (sym.is_valid()) { result.symbols.push_back(std::move(sym)); } } } uint32_t child_count = ts_node_child_count(node); for (uint32_t i = 0; i < child_count; ++i) { traverse(ts_node_child(node, i)); } }; traverse(ctx.root); return result; } // SymbolExtractor Implementation SymbolExtractor::SymbolExtractor(TSLanguage* language) : language_(language), parser_(ts_parser_new(), ts_parser_delete) { if (language_) { ts_parser_set_language(parser_.get(), language_); } } SymbolExtractor::Result SymbolExtractor::extract(std::string_view content, std::string_view language, std::string_view file_path, bool enable_call_graph, std::source_location loc) { if (!language_) { return tl::unexpected(fmt::format("No tree-sitter language loaded for {} (at {}:{})", language, loc.file_name(), loc.line())); } if (content.empty()) { return tl::unexpected("Empty content provided"); } // Defensive: verify parser language set successfully if (ts_parser_language(parser_.get()) == nullptr) { return tl::unexpected("Parser has no language set"); } ExtractionResult result; ExtractionContext ctx{.content = content, .language = language, .file_path = file_path, .enable_call_graph = enable_call_graph}; // Parse the content (guard against null pointers) if (!parser_.get() || content.data() == nullptr) { return tl::unexpected("Invalid parser or content"); } // Use ts_parser_set_language already set in ctor. Extra guard around parse. ctx.tree = ts_parser_parse_string(parser_.get(), nullptr, content.data(), (uint32_t)content.length()); if (!ctx.tree) { return tl::unexpected("Failed to parse content"); } ctx.root = ts_tree_root_node(ctx.tree); try { // Extract symbols by type auto func_result = extractFunctions(ctx); if (!func_result) return func_result; auto class_result = extractClasses(ctx); if (!class_result) return class_result; auto struct_result = extractStructs(ctx); if (!struct_result) return struct_result; // Extract fields/member variables auto field_result = extractFields(ctx); if (!field_result) return field_result; // Combine all symbols result.symbols.reserve(func_result->symbols.size() + class_result->symbols.size() + struct_result->symbols.size() + field_result->symbols.size()); std::ranges::move(func_result->symbols, std::back_inserter(result.symbols)); std::ranges::move(class_result->symbols, std::back_inserter(result.symbols)); std::ranges::move(struct_result->symbols, std::back_inserter(result.symbols)); std::ranges::move(field_result->symbols, std::back_inserter(result.symbols)); // Extract call relationships if enabled if (enable_call_graph) { auto call_result = extractCallRelations(ctx, result.symbols); if (call_result) { std::ranges::move(call_result->relations, std::back_inserter(result.relations)); } } // Extract include/import relationships auto include_result = extractIncludes(ctx); if (include_result) { std::ranges::move(include_result->relations, std::back_inserter(result.relations)); } // Extract inheritance relationships auto inherit_result = extractInheritance(ctx, result.symbols); if (inherit_result) { std::ranges::move(inherit_result->relations, std::back_inserter(result.relations)); } // Extract member containment relationships (class contains methods/fields) auto member_result = extractMemberRelations(ctx, result.symbols); if (member_result) { std::ranges::move(member_result->relations, std::back_inserter(result.relations)); } result.calculate_stats(); } catch (const std::exception& e) { if (ctx.tree) ts_tree_delete(ctx.tree); return tl::unexpected(fmt::format("Extraction exception: {}", e.what())); } if (ctx.tree) ts_tree_delete(ctx.tree); return result; } SymbolExtractor::Result SymbolExtractor::extractFunctions(const ExtractionContext& ctx) { ExtractionResult result; const auto* config = getLanguageConfig(ctx.language); if (!config || config->function_queries.count == 0) { // Fallback for unknown language: use node type traversal return extractSymbolsByNodeType(ctx, "function"); } // Execute queries from config bool any_query_succeeded = false; for (size_t i = 0; i < config->function_queries.count; ++i) { if (executeQuery(ctx, config->function_queries[i], "function", result.symbols)) { any_query_succeeded = true; } } if (any_query_succeeded) { return result; } // Fallback: node type traversal return extractSymbolsByNodeType(ctx, "function"); } SymbolExtractor::Result SymbolExtractor::extractClasses(const ExtractionContext& ctx) { ExtractionResult result; const auto* config = getLanguageConfig(ctx.language); if (!config || config->class_queries.count == 0) { return extractSymbolsByNodeType(ctx, "class"); } // Execute queries from config, inferring kind from query pattern for (size_t i = 0; i < config->class_queries.count; ++i) { std::string_view query = config->class_queries[i]; std::string kind = "class"; if (query.find("struct") != std::string_view::npos) kind = "struct"; else if (query.find("interface") != std::string_view::npos) kind = "interface"; else if (query.find("enum") != std::string_view::npos) kind = "enum"; else if (query.find("trait") != std::string_view::npos) kind = "trait"; executeQuery(ctx, query, kind, result.symbols); } return result; } SymbolExtractor::Result SymbolExtractor::extractStructs(const ExtractionContext& /*ctx*/) { // For C, structs are extracted in the class extraction phase // This provides a separate hook for languages where structs are distinct return ExtractionResult{}; } bool SymbolExtractor::executeQuery(const ExtractionContext& ctx, std::string_view query_text, std::string_view symbol_kind, std::vector& output) { TSQueryError error_type = TSQueryErrorNone; uint32_t error_offset = 0; TSLanguage* lang = language_; TSQuery* query = ts_query_new(lang, query_text.data(), static_cast(query_text.size()), &error_offset, &error_type); if (!query) return false; auto query_deleter = [](TSQuery* q) { if (q) ts_query_delete(q); }; std::unique_ptr query_guard(query, query_deleter); TSQueryCursor* cursor = ts_query_cursor_new(); auto cursor_deleter = [](TSQueryCursor* c) { if (c) ts_query_cursor_delete(c); }; std::unique_ptr cursor_guard(cursor, cursor_deleter); ts_query_cursor_exec(cursor, query, ctx.root); bool found_any = false; TSQueryMatch match; while (ts_query_cursor_next_match(cursor, &match)) { for (uint32_t i = 0; i < match.capture_count; ++i) { TSQueryCapture capture = match.captures[i]; uint32_t name_len = 0; const char* capture_name = ts_query_capture_name_for_id(query, capture.index, &name_len); if (capture_name && std::string_view(capture_name) == "name") { std::string name_text = ctx.extractNodeText(capture.node); if (!name_text.empty()) { SymbolInfo sym; sym.name = std::move(name_text); sym.scope = compute_cpp_scope(ctx.content, capture.node); sym.qualified_name = sym.scope.empty() ? sym.name : sym.scope + "::" + sym.name; sym.kind = std::string(symbol_kind); sym.file_path = std::string(ctx.file_path); TSPoint start = ts_node_start_point(capture.node); TSPoint end = ts_node_end_point(capture.node); sym.start_line = start.row + 1; sym.end_line = end.row + 1; sym.start_offset = ts_node_start_byte(capture.node); sym.end_offset = ts_node_end_byte(capture.node); // Find parent node for full symbol bounds // Look for the actual definition/declaration node, not intermediate nodes TSNode parent_node = ts_node_parent(capture.node); TSNode best_match = {}; while (!ts_node_is_null(parent_node)) { const char* node_type = ts_node_type(parent_node); // For functions, we want function_definition, not function_declarator // For classes/structs, we want class_specifier/struct_specifier bool is_definition = (symbol_kind == "function" && (std::strcmp(node_type, "function_definition") == 0 || std::strcmp(node_type, "template_declaration") == 0)) || (symbol_kind == "class" && (std::strcmp(node_type, "class_specifier") == 0 || std::strcmp(node_type, "template_declaration") == 0)) || (symbol_kind == "struct" && std::strcmp(node_type, "struct_specifier") == 0); if (is_definition) { best_match = parent_node; // Keep going up in case there's a template wrapper if (std::strcmp(node_type, "template_declaration") != 0) { // Check if there's a template_declaration above TSNode grandparent = ts_node_parent(parent_node); if (!ts_node_is_null(grandparent) && std::strcmp(ts_node_type(grandparent), "template_declaration") == 0) { best_match = grandparent; } } break; } parent_node = ts_node_parent(parent_node); } if (!ts_node_is_null(best_match)) { TSPoint parent_start = ts_node_start_point(best_match); TSPoint parent_end = ts_node_end_point(best_match); sym.start_line = parent_start.row + 1; sym.end_line = parent_end.row + 1; sym.start_offset = ts_node_start_byte(best_match); sym.end_offset = ts_node_end_byte(best_match); } if (sym.is_valid()) { output.push_back(std::move(sym)); found_any = true; } } } } } return found_any; } SymbolExtractor::Result SymbolExtractor::extractCallRelations(const ExtractionContext& ctx, const std::vector& symbols) { ExtractionResult result; spdlog::info("[CallExtraction] Starting for language={}, symbol_count={}", ctx.language, symbols.size()); // Collect function symbols with valid byte ranges std::vector function_symbols; for (const auto& sym : symbols) { if (sym.kind == "function" && sym.start_offset < sym.end_offset) { function_symbols.push_back(&sym); } } spdlog::info("[CallExtraction] Found {} function symbols to analyze", function_symbols.size()); if (function_symbols.empty()) { return result; } // Get call patterns from config const auto* config = getLanguageConfig(ctx.language); if (!config || config->call_queries.count == 0) { spdlog::info("[CallExtraction] No call patterns for language={}", ctx.language); return result; } // Find ALL call expressions in the entire file first // We'll match them to containing functions by byte range struct CallSite { std::string callee_name; uint32_t byte_offset; }; std::vector all_calls; for (size_t i = 0; i < config->call_queries.count; ++i) { std::string_view pattern = config->call_queries[i]; uint32_t error_offset = 0; TSQueryError error_type = TSQueryErrorNone; TSQuery* query = ts_query_new(language_, pattern.data(), static_cast(pattern.size()), &error_offset, &error_type); if (!query) continue; auto query_deleter = [](TSQuery* q) { if (q) ts_query_delete(q); }; std::unique_ptr query_guard(query, query_deleter); TSQueryCursor* cursor = ts_query_cursor_new(); auto cursor_deleter = [](TSQueryCursor* c) { if (c) ts_query_cursor_delete(c); }; std::unique_ptr cursor_guard(cursor, cursor_deleter); // Execute on the entire tree root ts_query_cursor_exec(cursor, query, ctx.root); TSQueryMatch match; while (ts_query_cursor_next_match(cursor, &match)) { std::string callee_name; uint32_t call_byte = 0; for (uint32_t i = 0; i < match.capture_count; ++i) { TSQueryCapture capture = match.captures[i]; uint32_t name_len = 0; const char* capture_name = ts_query_capture_name_for_id(query, capture.index, &name_len); if (capture_name) { std::string_view cap_name(capture_name, name_len); if (cap_name == "callee") { callee_name = ctx.extractNodeText(capture.node); } else if (cap_name == "call") { call_byte = ts_node_start_byte(capture.node); } } } if (!callee_name.empty()) { all_calls.push_back({std::move(callee_name), call_byte}); } } } spdlog::info("[CallExtraction] Found {} total call expressions in file", all_calls.size()); // Now match calls to their containing functions by byte range std::unordered_map> call_sites; for (const auto& call : all_calls) { // Find which function contains this call for (const auto* func : function_symbols) { if (call.byte_offset >= func->start_offset && call.byte_offset < func->end_offset) { const std::string& caller_name = func->qualified_name.empty() ? func->name : func->qualified_name; // This call is inside this function // Skip self-recursive calls if (call.callee_name != caller_name) { call_sites[caller_name].insert(call.callee_name); } break; // A call can only be in one function } } } spdlog::info("[CallExtraction] Matched calls to {} unique caller functions", call_sites.size()); // Log some sample calls for debugging size_t logged = 0; for (const auto& [caller, callees] : call_sites) { if (logged++ < 5) { spdlog::info("[CallExtraction] {} calls: {}", caller, [&]() { std::string s; size_t count = 0; for (const auto& c : callees) { if (count++ > 0) s += ", "; s += c; if (count >= 5) { s += "..."; break; } } return s; }()); } } // Convert to relations - include ALL calls, not just to known symbols // This allows cross-file relationships to be resolved later for (const auto& [caller, callees] : call_sites) { for (const auto& callee : callees) { result.relations.push_back(SymbolRelation{ .src_symbol = caller, .dst_symbol = callee, .kind = "calls", .weight = 1.0}); } } spdlog::info("[CallExtraction] Extracted {} call relations", result.relations.size()); return result; } SymbolExtractor::Result SymbolExtractor::extractIncludes(const ExtractionContext& ctx) { ExtractionResult result; const auto* config = getLanguageConfig(ctx.language); if (!config || config->import_queries.count == 0) { return result; } std::string file_path_str = std::string(ctx.file_path); size_t extracted_count = 0; for (size_t i = 0; i < config->import_queries.count; ++i) { std::string_view pattern = config->import_queries[i]; uint32_t error_offset = 0; TSQueryError error_type = TSQueryErrorNone; TSQuery* query = ts_query_new(language_, pattern.data(), static_cast(pattern.size()), &error_offset, &error_type); if (!query) continue; auto query_deleter = [](TSQuery* q) { if (q) ts_query_delete(q); }; std::unique_ptr query_guard(query, query_deleter); TSQueryCursor* cursor = ts_query_cursor_new(); auto cursor_deleter = [](TSQueryCursor* c) { if (c) ts_query_cursor_delete(c); }; std::unique_ptr cursor_guard(cursor, cursor_deleter); ts_query_cursor_exec(cursor, query, ctx.root); TSQueryMatch match; while (ts_query_cursor_next_match(cursor, &match)) { // Take the first capture (queries are structured with path/module first) if (match.capture_count > 0) { TSQueryCapture capture = match.captures[0]; std::string include_path = ctx.extractNodeText(capture.node); // Clean up quotes from string literals if (!include_path.empty() && (include_path.front() == '"' || include_path.front() == '\'' || include_path.front() == '<')) { include_path = include_path.substr(1); } if (!include_path.empty() && (include_path.back() == '"' || include_path.back() == '\'' || include_path.back() == '>')) { include_path.pop_back(); } if (!include_path.empty()) { SymbolRelation rel; rel.src_symbol = file_path_str; rel.dst_symbol = include_path; rel.kind = "includes"; rel.weight = 1.0; result.relations.push_back(std::move(rel)); ++extracted_count; } } } } if (extracted_count > 0) { spdlog::info("[IncludeExtraction] Extracted {} include/import relations from {}", extracted_count, ctx.file_path); } return result; } SymbolExtractor::Result SymbolExtractor::extractInheritance(const ExtractionContext& ctx, const std::vector& symbols) { ExtractionResult result; std::string_view lang = ctx.language; // Build a map of class/struct symbols for matching std::unordered_map class_map; for (const auto& sym : symbols) { if (sym.kind == "class" || sym.kind == "struct" || sym.kind == "interface") { class_map[sym.name] = &sym; } } if (class_map.empty()) { return result; } // Query patterns for inheritance - language specific // The strategy: find class definitions with base classes and extract the relationship if (lang == "cpp" || lang == "c++" || lang == "c") { // C++ inheritance: class Derived : public Base { ... } // We need to traverse the AST to find base_class_clause nodes std::function traverse; traverse = [&](TSNode node) { if (ts_node_is_null(node)) return; const char* node_type = ts_node_type(node); // Look for class_specifier or struct_specifier if (std::strcmp(node_type, "class_specifier") == 0 || std::strcmp(node_type, "struct_specifier") == 0) { // Find the class name std::string class_name; TSNode name_node = ts_node_child_by_field_name(node, "name", 4); if (!ts_node_is_null(name_node)) { class_name = ctx.extractNodeText(name_node); } // Look for base_class_clause child uint32_t child_count = ts_node_child_count(node); for (uint32_t i = 0; i < child_count; ++i) { TSNode child = ts_node_child(node, i); if (std::strcmp(ts_node_type(child), "base_class_clause") == 0) { // Find all base_class_clause children (base types) uint32_t base_count = ts_node_child_count(child); for (uint32_t j = 0; j < base_count; ++j) { TSNode base_spec = ts_node_child(child, j); // Look for type_identifier in the base specifier std::function find_base_type; find_base_type = [&ctx, &find_base_type](TSNode n) -> std::string { if (ts_node_is_null(n)) return ""; const char* type = ts_node_type(n); if (std::strcmp(type, "type_identifier") == 0 || std::strcmp(type, "qualified_identifier") == 0) { return ctx.extractNodeText(n); } uint32_t cc = ts_node_child_count(n); for (uint32_t k = 0; k < cc; ++k) { std::string found = find_base_type(ts_node_child(n, k)); if (!found.empty()) return found; } return ""; }; std::string base_name = find_base_type(base_spec); if (!base_name.empty() && !class_name.empty() && base_name != class_name) { SymbolRelation rel; rel.src_symbol.append(class_name); rel.dst_symbol = std::move(base_name); rel.kind = "inherits"; rel.weight = 1.0; result.relations.push_back(std::move(rel)); } } } } } // Recurse to children uint32_t child_count = ts_node_child_count(node); for (uint32_t i = 0; i < child_count; ++i) { traverse(ts_node_child(node, i)); } }; traverse(ctx.root); } else if (lang == "python") { // Python: class Foo(Bar, Baz): ... std::function traverse; traverse = [&](TSNode node) { if (ts_node_is_null(node)) return; if (std::strcmp(ts_node_type(node), "class_definition") == 0) { // Get class name std::string class_name; TSNode name_node = ts_node_child_by_field_name(node, "name", 4); if (!ts_node_is_null(name_node)) { class_name = ctx.extractNodeText(name_node); } // Get superclasses from argument_list TSNode superclasses = ts_node_child_by_field_name(node, "superclasses", 12); if (!ts_node_is_null(superclasses)) { uint32_t count = ts_node_child_count(superclasses); for (uint32_t i = 0; i < count; ++i) { TSNode child = ts_node_child(superclasses, i); const char* type = ts_node_type(child); // Look for identifier or attribute if (std::strcmp(type, "identifier") == 0 || std::strcmp(type, "attribute") == 0) { std::string base_name = ctx.extractNodeText(child); if (!base_name.empty() && !class_name.empty() && base_name != class_name) { SymbolRelation rel; rel.src_symbol.append(class_name); rel.dst_symbol = std::move(base_name); rel.kind = "inherits"; rel.weight = 1.0; result.relations.push_back(std::move(rel)); } } } } } uint32_t child_count = ts_node_child_count(node); for (uint32_t i = 0; i < child_count; ++i) { traverse(ts_node_child(node, i)); } }; traverse(ctx.root); } else if (lang == "java") { // Java: class Foo extends Bar implements Baz std::function traverse; traverse = [&](TSNode node) { if (ts_node_is_null(node)) return; if (std::strcmp(ts_node_type(node), "class_declaration") == 0) { std::string class_name; TSNode name_node = ts_node_child_by_field_name(node, "name", 4); if (!ts_node_is_null(name_node)) { class_name = ctx.extractNodeText(name_node); } // Find superclass TSNode superclass = ts_node_child_by_field_name(node, "superclass", 10); if (!ts_node_is_null(superclass)) { std::string base_name = ctx.extractNodeText(superclass); if (!base_name.empty() && !class_name.empty()) { SymbolRelation rel; rel.src_symbol.append(class_name); rel.dst_symbol = std::move(base_name); rel.kind = "inherits"; rel.weight = 1.0; result.relations.push_back(std::move(rel)); } } // Find interfaces TSNode interfaces = ts_node_child_by_field_name(node, "interfaces", 10); if (!ts_node_is_null(interfaces)) { uint32_t count = ts_node_child_count(interfaces); for (uint32_t i = 0; i < count; ++i) { TSNode child = ts_node_child(interfaces, i); if (std::strcmp(ts_node_type(child), "type_identifier") == 0 || std::strcmp(ts_node_type(child), "generic_type") == 0) { std::string iface_name = ctx.extractNodeText(child); if (!iface_name.empty() && !class_name.empty()) { SymbolRelation rel; rel.src_symbol.append(class_name); rel.dst_symbol = std::move(iface_name); rel.kind = "implements"; rel.weight = 1.0; result.relations.push_back(std::move(rel)); } } } } } uint32_t child_count = ts_node_child_count(node); for (uint32_t i = 0; i < child_count; ++i) { traverse(ts_node_child(node, i)); } }; traverse(ctx.root); } else if (lang == "javascript" || lang == "js" || lang == "typescript" || lang == "ts") { // JS/TS: class Foo extends Bar { ... } std::function traverse; traverse = [&](TSNode node) { if (ts_node_is_null(node)) return; if (std::strcmp(ts_node_type(node), "class_declaration") == 0 || std::strcmp(ts_node_type(node), "class") == 0) { std::string class_name; TSNode name_node = ts_node_child_by_field_name(node, "name", 4); if (!ts_node_is_null(name_node)) { class_name = ctx.extractNodeText(name_node); } // Find class_heritage for 'extends' uint32_t child_count = ts_node_child_count(node); for (uint32_t i = 0; i < child_count; ++i) { TSNode child = ts_node_child(node, i); if (std::strcmp(ts_node_type(child), "class_heritage") == 0) { // Look for extends_clause uint32_t hcount = ts_node_child_count(child); for (uint32_t j = 0; j < hcount; ++j) { TSNode hchild = ts_node_child(child, j); if (std::strcmp(ts_node_type(hchild), "extends_clause") == 0) { // Get the base class identifier uint32_t ecount = ts_node_child_count(hchild); for (uint32_t k = 0; k < ecount; ++k) { TSNode echild = ts_node_child(hchild, k); if (std::strcmp(ts_node_type(echild), "identifier") == 0) { std::string base_name = ctx.extractNodeText(echild); if (!base_name.empty() && !class_name.empty()) { SymbolRelation rel; rel.src_symbol.append(class_name); rel.dst_symbol = std::move(base_name); rel.kind = "inherits"; rel.weight = 1.0; result.relations.push_back(std::move(rel)); } } } } } } } } uint32_t child_count = ts_node_child_count(node); for (uint32_t i = 0; i < child_count; ++i) { traverse(ts_node_child(node, i)); } }; traverse(ctx.root); } else if (lang == "rust") { // Rust: impl Trait for Struct // Rust doesn't have traditional inheritance, but we can extract trait implementations std::function traverse; traverse = [&](TSNode node) { if (ts_node_is_null(node)) return; if (std::strcmp(ts_node_type(node), "impl_item") == 0) { // Look for "impl Trait for Type" pattern std::string trait_name; std::string type_name; TSNode trait_node = ts_node_child_by_field_name(node, "trait", 5); TSNode type_node = ts_node_child_by_field_name(node, "type", 4); if (!ts_node_is_null(trait_node)) { trait_name = ctx.extractNodeText(trait_node); } if (!ts_node_is_null(type_node)) { type_name = ctx.extractNodeText(type_node); } if (!trait_name.empty() && !type_name.empty()) { SymbolRelation rel; rel.src_symbol = std::move(type_name); rel.dst_symbol = std::move(trait_name); rel.kind = "implements"; rel.weight = 1.0; result.relations.push_back(std::move(rel)); } } uint32_t child_count = ts_node_child_count(node); for (uint32_t i = 0; i < child_count; ++i) { traverse(ts_node_child(node, i)); } }; traverse(ctx.root); } else if (lang == "go") { // Go uses implicit interface implementation, can't easily extract from AST // Struct embedding could be extracted but is less common } else if (lang == "sol" || lang == "solidity") { // Solidity: contract Foo is Bar, Baz { ... } std::function traverse; traverse = [&](TSNode node) { if (ts_node_is_null(node)) return; if (std::strcmp(ts_node_type(node), "contract_declaration") == 0) { std::string class_name; TSNode name_node = ts_node_child_by_field_name(node, "name", 4); if (!ts_node_is_null(name_node)) { class_name = ctx.extractNodeText(name_node); } // Find inheritance_specifier uint32_t child_count = ts_node_child_count(node); for (uint32_t i = 0; i < child_count; ++i) { TSNode child = ts_node_child(node, i); if (std::strcmp(ts_node_type(child), "class_heritage") == 0 || std::strcmp(ts_node_type(child), "inheritance_specifier") == 0) { uint32_t hcount = ts_node_child_count(child); for (uint32_t j = 0; j < hcount; ++j) { TSNode hchild = ts_node_child(child, j); if (std::strcmp(ts_node_type(hchild), "identifier") == 0 || std::strcmp(ts_node_type(hchild), "user_defined_type") == 0) { std::string base_name = ctx.extractNodeText(hchild); if (!base_name.empty() && !class_name.empty()) { SymbolRelation rel; rel.src_symbol.append(class_name); rel.dst_symbol = std::move(base_name); rel.kind = "inherits"; rel.weight = 1.0; result.relations.push_back(std::move(rel)); } } } } } } uint32_t child_count = ts_node_child_count(node); for (uint32_t i = 0; i < child_count; ++i) { traverse(ts_node_child(node, i)); } }; traverse(ctx.root); } if (!result.relations.empty()) { spdlog::info("[InheritanceExtraction] Extracted {} inheritance relations from {}", result.relations.size(), ctx.file_path); } return result; } SymbolExtractor::Result SymbolExtractor::extractFields(const ExtractionContext& ctx) { // Use the generic node type traversal with field types return extractSymbolsByNodeType(ctx, "field"); } SymbolExtractor::Result SymbolExtractor::extractMemberRelations(const ExtractionContext& ctx, const std::vector& symbols) { ExtractionResult result; // Build map of classes/structs by name with their byte ranges struct ClassInfo { std::string name; uint32_t start_offset; uint32_t end_offset; }; std::vector classes; for (const auto& sym : symbols) { if (sym.kind == "class" || sym.kind == "struct" || sym.kind == "trait" || sym.kind == "interface") { classes.push_back({sym.name, sym.start_offset, sym.end_offset}); } } if (classes.empty()) { return result; } // For each function/method/field, check if it's within a class boundary for (const auto& sym : symbols) { if (sym.kind == "function" || sym.kind == "method" || sym.kind == "field") { // Find containing class by byte range for (const auto& cls : classes) { if (sym.start_offset >= cls.start_offset && sym.end_offset <= cls.end_offset) { // This symbol is contained within this class if (sym.name != cls.name) { // Don't create self-contains SymbolRelation rel; rel.src_symbol = cls.name; rel.dst_symbol = sym.name; rel.kind = "contains"; rel.weight = 1.0; result.relations.push_back(std::move(rel)); } break; // A symbol can only be in one class } } } } if (!result.relations.empty()) { spdlog::info("[MemberRelations] Extracted {} contains relations from {}", result.relations.size(), ctx.file_path); } return result; } } // namespace yams::plugins::treesitter