fix(csharp): extract members inside #if preprocessor blocks (#2634)

This commit is contained in:
rohit-jsfreaky
2026-08-11 20:09:59 +01:00
committed by safishamsi
parent bedf32e07c
commit cd04bf1bf3
2 changed files with 58 additions and 1 deletions
+8 -1
View File
@@ -2162,7 +2162,7 @@ def _csharp_extra_walk(node, source: bytes, file_nid: str, stem: str, str_path:
nodes: list, edges: list, seen_ids: set, function_bodies: list,
parent_class_nid: str | None, add_node_fn, add_edge_fn,
walk_fn, namespace_stack: list[str], scope_stack: list[str]) -> bool:
"""Handle namespace declarations for C#. Returns True if handled."""
"""Handle C# namespaces and transparent class-member wrappers."""
if node.type == "namespace_declaration":
ns_name = _csharp_namespace_name(node, source)
pushed = False
@@ -2199,6 +2199,13 @@ def _csharp_extra_walk(node, source: bytes, file_nid: str, stem: str, str_path:
add_node_fn(ns_nid, ns_label, line, node_type="namespace", metadata={"kind": "csharp_namespace"})
add_edge_fn(file_nid, ns_nid, "contains", line)
return True
if parent_class_nid and node.type.startswith("preproc_"):
# tree-sitter wraps members in #if/#else/#elif directives in preproc_*
# nodes. They are conditional containers, not ownership scopes: dropping
# parent_class_nid here makes guarded methods look file-level (#2631).
for child in node.children:
walk_fn(child, parent_class_nid)
return True
return False
def _swift_extra_walk(node, source: bytes, file_nid: str, stem: str, str_path: str,
+50
View File
@@ -519,6 +519,56 @@ def test_xaml_viewmodel_with_non_utf8_codebehind_does_not_crash(tmp_path):
assert nodes[edges[0]["target"]]["label"] == "SettingsViewModel"
def test_csharp_members_in_preprocessor_blocks_are_extracted_and_resolved(tmp_path):
"""C# preprocessor wrappers must preserve class ownership for members (#2631)."""
helper = tmp_path / "Helper.cs"
helper.write_text(
"""namespace Probe.Lib;
public static class Gated
{
public static void Outside(string a) { }
#if NET8_0_OR_GREATER
public static void InsideIf(string a) { }
#else
public static void InsideElse(string a) { }
#endif
#if DEBUG
public static void InsideDebug(string a) { }
#endif
}
"""
)
caller = tmp_path / "Caller.cs"
caller.write_text(
"""namespace Probe.Lib;
public static class Caller
{
public static void Drive()
{
Gated.Outside(\"x\");
Gated.InsideIf(\"x\");
Gated.InsideDebug(\"x\");
}
}
"""
)
result = extract([helper, caller], cache_root=tmp_path)
by_label = {node["label"]: node["id"] for node in result["nodes"]}
for label in (".Outside()", ".InsideIf()", ".InsideElse()", ".InsideDebug()"):
assert label in by_label
calls = {
(edge["source"], edge["target"])
for edge in result["edges"]
if edge["relation"] == "calls"
}
assert (by_label[".Drive()"], by_label[".Outside()"]) in calls
assert (by_label[".Drive()"], by_label[".InsideIf()"]) in calls
assert (by_label[".Drive()"], by_label[".InsideDebug()"]) in calls
# ── .razor ───────────────────────────────────────────────────────────────────
def test_razor_using_and_inject():