yams/plugins/symbol_extractor_treesitter/symbol_extractor.cpp

1599 lines
71 KiB
C++

#include "symbol_extractor.h"
#include "grammar_loader.h"
#include <algorithm>
#include <array>
#include <expected>
#include <ranges>
#include <set>
#include <span>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <spdlog/spdlog.h>
#include <cstring>
#include <functional>
extern "C" {
#include <tree_sitter/api.h>
}
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 <size_t N> struct ConstList {
std::array<std::string_view, N> 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<MaxNodeTypes>;
using QueryList = ConstList<MaxQueries>;
// Language configuration with all extraction settings
struct LanguageConfig {
std::string_view name;
std::array<std::string_view, 4> 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<std::string> parts;
std::unordered_set<std::string> 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 <size_t N, typename... Args> constexpr auto makeList(Args... args) -> ConstList<N> {
ConstList<N> result;
result.count = sizeof...(args);
result.items = {args...};
return result;
}
// Convenience wrappers
template <typename... Args> constexpr auto makeNodeTypes(Args... args) {
return makeList<MaxNodeTypes>(args...);
}
template <typename... Args> constexpr auto makeQueries(Args... args) {
return makeList<MaxQueries>(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<const LanguageConfig*, 18> 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<TSNode(TSNode)> 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<void(TSNode)> 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<SymbolInfo>& output) {
TSQueryError error_type = TSQueryErrorNone;
uint32_t error_offset = 0;
TSLanguage* lang = language_;
TSQuery* query = ts_query_new(lang, query_text.data(), static_cast<uint32_t>(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<TSQuery, decltype(query_deleter)> 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<TSQueryCursor, decltype(cursor_deleter)> 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<SymbolInfo>& symbols) {
ExtractionResult result;
spdlog::info("[CallExtraction] Starting for language={}, symbol_count={}", ctx.language,
symbols.size());
// Collect function symbols with valid byte ranges
std::vector<const SymbolInfo*> 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<CallSite> 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<uint32_t>(pattern.size()),
&error_offset, &error_type);
if (!query)
continue;
auto query_deleter = [](TSQuery* q) {
if (q)
ts_query_delete(q);
};
std::unique_ptr<TSQuery, decltype(query_deleter)> 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<TSQueryCursor, decltype(cursor_deleter)> 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<std::string /*caller*/, std::set<std::string /*callee*/>> 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<uint32_t>(pattern.size()),
&error_offset, &error_type);
if (!query)
continue;
auto query_deleter = [](TSQuery* q) {
if (q)
ts_query_delete(q);
};
std::unique_ptr<TSQuery, decltype(query_deleter)> 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<TSQueryCursor, decltype(cursor_deleter)> 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<SymbolInfo>& symbols) {
ExtractionResult result;
std::string_view lang = ctx.language;
// Build a map of class/struct symbols for matching
std::unordered_map<std::string, const SymbolInfo*> 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<void(TSNode)> 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<std::string(TSNode)> 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<void(TSNode)> 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<void(TSNode)> 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<void(TSNode)> 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<void(TSNode)> 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<void(TSNode)> 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<SymbolInfo>& 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<ClassInfo> 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