mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-28 07:03:20 +08:00
feat(extract): C# field/property + C# 12 primary-constructor support
Three C# extractor gaps that left modern .NET classes partially invisible
in the graph:
1. lang_specs.c CSHARP entry had `empty_types` for field_node_types, so
walk_defs never descended into field_declaration / property_declaration
nodes. Add cs_field_types and wire it in. Without this, no C# fields
or properties were ever pushed as Field defs.
2. extract_class_fields assumed the field's "type" is on the immediate
child via ts_node_child_by_field_name. Tree-sitter-c_sharp wraps fields
as field_declaration > variable_declaration(type, variable_declarator),
one level deeper than Java/Go/Rust. Detect the nested shape by walking
named children for variable_declaration and resolve type + name from
inside it. Direct shape continues unchanged.
3. C# 12 primary constructors (`class Foo(IBar bar) : Base { ... }`)
declare implicit captured fields on the class line. Tree-sitter wraps
them inside the hidden _class_declaration_initializer node, so the
`parameters` field on class_declaration may not always resolve
directly; iterate top-level children for parameter_list as a fallback.
Emit each primary-ctor parameter as a Field def with parent_class set.
Effect: dependency-injected fields on .NET 8+/9+ controllers and any C#
auto-property (`public IFoo Foo { get; }`) are now reachable as Field
nodes in the graph.
Co-authored-by: sponger94 <45746997+sponger94@users.noreply.github.com>
This commit is contained in:
co-authored by
sponger94
parent
b2a71b80b9
commit
beef06ec6d
@@ -1986,6 +1986,55 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
|
||||
// Extract class-level variables (field declarations)
|
||||
extract_class_variables(ctx, node, spec);
|
||||
|
||||
// C# 12 primary-constructor parameters: declared on the class line
|
||||
// (`class Foo(IBar bar, IBaz baz) : Base { ... }`) and bound to implicit
|
||||
// captured fields accessible from any instance member. Tree-sitter c-sharp
|
||||
// wraps them inside the hidden _class_declaration_initializer node, so the
|
||||
// `parameters` field on class_declaration may not always resolve directly;
|
||||
// iterate top-level children for parameter_list as a robust fallback.
|
||||
if (ctx->language == CBM_LANG_CSHARP) {
|
||||
TSNode primary_params = ts_node_child_by_field_name(node, TS_FIELD("parameters"));
|
||||
if (ts_node_is_null(primary_params)) {
|
||||
uint32_t total = ts_node_child_count(node);
|
||||
for (uint32_t i = 0; i < total; i++) {
|
||||
TSNode c = ts_node_child(node, i);
|
||||
if (!ts_node_is_null(c) && strcmp(ts_node_type(c), "parameter_list") == 0) {
|
||||
primary_params = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ts_node_is_null(primary_params)) {
|
||||
uint32_t pcount = ts_node_child_count(primary_params);
|
||||
for (uint32_t k = 0; k < pcount; k++) {
|
||||
TSNode p = ts_node_child(primary_params, k);
|
||||
if (ts_node_is_null(p) || !ts_node_is_named(p)) {
|
||||
continue;
|
||||
}
|
||||
char *pname = resolve_param_name(a, p, ctx->source);
|
||||
if (!pname || !pname[0]) {
|
||||
continue;
|
||||
}
|
||||
char *ptype = resolve_param_type_text(a, p, ctx->source, ctx->language);
|
||||
if (!ptype || !ptype[0]) {
|
||||
continue;
|
||||
}
|
||||
CBMDefinition pdef;
|
||||
memset(&pdef, 0, sizeof(pdef));
|
||||
pdef.name = pname;
|
||||
pdef.qualified_name = cbm_arena_sprintf(a, "%s.%s", class_qn, pname);
|
||||
pdef.label = "Field";
|
||||
pdef.file_path = ctx->rel_path;
|
||||
pdef.parent_class = class_qn;
|
||||
pdef.return_type = ptype;
|
||||
pdef.start_line = ts_node_start_point(p).row + TS_LINE_OFFSET;
|
||||
pdef.end_line = ts_node_end_point(p).row + TS_LINE_OFFSET;
|
||||
pdef.is_exported = false;
|
||||
cbm_defs_push(&ctx->result->defs, a, pdef);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the body/members node inside a class node
|
||||
@@ -3304,8 +3353,41 @@ static void extract_class_fields(CBMExtractCtx *ctx, TSNode class_node, const ch
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract type from "type" field
|
||||
/* Locate the field's "type" + name node. Two shapes:
|
||||
* - direct (Java/Go/Rust/C/C++):
|
||||
* field_declaration .type=identifier .declarator=variable_declarator(.name)
|
||||
* - nested (C#):
|
||||
* field_declaration > variable_declaration(.type=identifier,
|
||||
* variable_declarator(.name))
|
||||
* For the nested case, the child has no "type" field directly. Detect by
|
||||
* walking named children for a variable_declaration. */
|
||||
TSNode type_node = ts_node_child_by_field_name(child, TS_FIELD("type"));
|
||||
TSNode name_node = ts_node_is_null(type_node) ? (TSNode){0} : resolve_field_name_node(child);
|
||||
|
||||
if (ts_node_is_null(type_node)) {
|
||||
uint32_t cnc = ts_node_named_child_count(child);
|
||||
for (uint32_t k = 0; k < cnc; k++) {
|
||||
TSNode inner = ts_node_named_child(child, k);
|
||||
if (strcmp(ts_node_type(inner), "variable_declaration") != 0) {
|
||||
continue;
|
||||
}
|
||||
type_node = ts_node_child_by_field_name(inner, TS_FIELD("type"));
|
||||
/* Find first variable_declarator child for the name. */
|
||||
uint32_t nc = ts_node_named_child_count(inner);
|
||||
for (uint32_t j = 0; j < nc; j++) {
|
||||
TSNode vd = ts_node_named_child(inner, j);
|
||||
if (strcmp(ts_node_type(vd), "variable_declarator") == 0) {
|
||||
TSNode nm = ts_node_child_by_field_name(vd, TS_FIELD("name"));
|
||||
if (!ts_node_is_null(nm)) {
|
||||
name_node = nm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ts_node_is_null(type_node)) {
|
||||
continue;
|
||||
}
|
||||
@@ -3314,7 +3396,6 @@ static void extract_class_fields(CBMExtractCtx *ctx, TSNode class_node, const ch
|
||||
continue;
|
||||
}
|
||||
|
||||
TSNode name_node = resolve_field_name_node(child);
|
||||
if (ts_node_is_null(name_node)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -333,6 +333,7 @@ static const char *cs_branch_types[] = {"if_statement", "for_statement", "
|
||||
"while_statement", "switch_statement", "case_switch_label",
|
||||
"try_statement", "catch_clause", NULL};
|
||||
static const char *cs_var_types[] = {"field_declaration", "local_declaration_statement", NULL};
|
||||
static const char *cs_field_types[] = {"field_declaration", "property_declaration", NULL};
|
||||
static const char *cs_assign_types[] = {"assignment_expression", NULL};
|
||||
static const char *cs_throw_types[] = {"throw_statement", "throw_expression", NULL};
|
||||
static const char *cs_decorator_types[] = {"attribute", NULL};
|
||||
@@ -1570,7 +1571,7 @@ static const CBMLangSpec lang_specs[CBM_LANG_COUNT] = {
|
||||
empty_types, cpp_env_funcs, NULL, tree_sitter_cpp, NULL},
|
||||
|
||||
// CBM_LANG_CSHARP
|
||||
[CBM_LANG_CSHARP] = {CBM_LANG_CSHARP, cs_func_types, cs_class_types, empty_types,
|
||||
[CBM_LANG_CSHARP] = {CBM_LANG_CSHARP, cs_func_types, cs_class_types, cs_field_types,
|
||||
cs_module_types, cs_call_types, cs_import_types, cs_import_types,
|
||||
cs_branch_types, cs_var_types, cs_assign_types, cs_throw_types, NULL,
|
||||
cs_decorator_types, cs_env_funcs, NULL, tree_sitter_c_sharp, NULL},
|
||||
|
||||
Reference in New Issue
Block a user