Skip to content

Commit 4ece8db

Browse files
author
Your Name
committed
feat(extraction): C# property extraction with HAS_PROPERTY edges
Extracts property_declaration, indexer_declaration, event_declaration, and event_field_declaration from C# class bodies as 'Property' label nodes. Previously these were completely invisible to the knowledge graph. Creates HAS_PROPERTY edges from Class → Property in both parallel and serial indexing paths (pass_parallel.c, pass_definitions.c). Extracted metadata: property name, qualified name, file path, line range, declared type (from type field), decorators, export status. Tested: C# monolith (26K nodes) gained 3,470 Property nodes and 6,943 new edges including HAS_PROPERTY. trace_call_path now shows 5 properties for a typical service class.
1 parent d7cc2f7 commit 4ece8db

3 files changed

Lines changed: 70 additions & 0 deletions

File tree

internal/cbm/extract_defs.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,6 +1810,61 @@ static void extract_class_methods(CBMExtractCtx *ctx, TSNode class_node, const c
18101810
continue;
18111811
}
18121812

1813+
/* C#/Java property extraction: property_declaration, auto_property_declaration.
1814+
* Creates a "Property" node with parent_class set for DEFINES_METHOD edge. */
1815+
const char *child_type = ts_node_type(child);
1816+
if (child_type &&
1817+
(strcmp(child_type, "property_declaration") == 0 ||
1818+
strcmp(child_type, "indexer_declaration") == 0 ||
1819+
strcmp(child_type, "event_declaration") == 0 ||
1820+
strcmp(child_type, "event_field_declaration") == 0)) {
1821+
TSNode name_node = ts_node_child_by_field_name(child, "name", 4);
1822+
if (ts_node_is_null(name_node)) {
1823+
/* indexer_declaration doesn't have a 'name' field, use "this" */
1824+
if (strcmp(child_type, "indexer_declaration") == 0) {
1825+
CBMDefinition pdef;
1826+
memset(&pdef, 0, sizeof(pdef));
1827+
pdef.name = cbm_arena_strdup(ctx->arena, "this[]");
1828+
pdef.qualified_name = cbm_arena_sprintf(ctx->arena, "%s.this[]", class_qn);
1829+
pdef.label = "Property";
1830+
pdef.file_path = ctx->rel_path;
1831+
pdef.parent_class = class_qn;
1832+
pdef.start_line = ts_node_start_point(child).row + 1;
1833+
pdef.end_line = ts_node_end_point(child).row + 1;
1834+
pdef.lines = (int)(pdef.end_line - pdef.start_line + 1);
1835+
TSNode type_node = ts_node_child_by_field_name(child, "type", 4);
1836+
if (!ts_node_is_null(type_node)) {
1837+
pdef.return_type = cbm_node_text(ctx->arena, type_node, ctx->source);
1838+
}
1839+
cbm_defs_push(&ctx->result->defs, ctx->arena, pdef);
1840+
}
1841+
continue;
1842+
}
1843+
char *pname = cbm_node_text(ctx->arena, name_node, ctx->source);
1844+
if (pname && pname[0]) {
1845+
CBMDefinition pdef;
1846+
memset(&pdef, 0, sizeof(pdef));
1847+
pdef.name = pname;
1848+
pdef.qualified_name = cbm_arena_sprintf(ctx->arena, "%s.%s", class_qn, pname);
1849+
pdef.label = "Property";
1850+
pdef.file_path = ctx->rel_path;
1851+
pdef.parent_class = class_qn;
1852+
pdef.start_line = ts_node_start_point(child).row + 1;
1853+
pdef.end_line = ts_node_end_point(child).row + 1;
1854+
pdef.lines = (int)(pdef.end_line - pdef.start_line + 1);
1855+
pdef.is_exported = cbm_is_exported(pname, ctx->language);
1856+
/* Extract type */
1857+
TSNode type_node = ts_node_child_by_field_name(child, "type", 4);
1858+
if (!ts_node_is_null(type_node)) {
1859+
pdef.return_type = cbm_node_text(ctx->arena, type_node, ctx->source);
1860+
}
1861+
pdef.decorators = extract_decorators(ctx->arena, child, ctx->source,
1862+
ctx->language, spec);
1863+
cbm_defs_push(&ctx->result->defs, ctx->arena, pdef);
1864+
}
1865+
continue;
1866+
}
1867+
18131868
if (!cbm_kind_in_set(child, spec->function_node_types)) {
18141869
continue;
18151870
}

src/pipeline/pass_definitions.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,13 @@ int cbm_pipeline_pass_definitions(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t
271271
cbm_gbuf_insert_edge(ctx->gbuf, parent->id, node_id, "DEFINES_METHOD", "{}");
272272
}
273273
}
274+
/* HAS_PROPERTY edge: Class → Property */
275+
if (def->parent_class && def->label && strcmp(def->label, "Property") == 0) {
276+
const cbm_gbuf_node_t *parent = cbm_gbuf_find_by_qn(ctx->gbuf, def->parent_class);
277+
if (parent && node_id > 0) {
278+
cbm_gbuf_insert_edge(ctx->gbuf, parent->id, node_id, "HAS_PROPERTY", "{}");
279+
}
280+
}
274281

275282
total_defs++;
276283
}

src/pipeline/pass_parallel.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,14 @@ int cbm_build_registry_from_cache(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t
943943
"{}");
944944
}
945945
}
946+
/* HAS_PROPERTY edge: Class → Property */
947+
if (def->parent_class && def->label && strcmp(def->label, "Property") == 0) {
948+
const cbm_gbuf_node_t *parent = cbm_gbuf_find_by_qn(ctx->gbuf, def->parent_class);
949+
if (parent && def_node) {
950+
cbm_gbuf_insert_edge(ctx->gbuf, parent->id, def_node->id, "HAS_PROPERTY",
951+
"{}");
952+
}
953+
}
946954
}
947955

948956
/* IMPORTS edges */

0 commit comments

Comments
 (0)