mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 22:02:49 +08:00
fix(csharp): namespaces, usings and nested types follow the language's scopes (#2220)
A block namespace qualifies only its own body (a nested one is Outer.Inner), mirrored in the kernel; a using names the project's namespace, never another file's using; a global using is its own project's; a nested type is reached by bare name only from inside its owner or a type deriving from it, through any partial part; visible C# types are filtered before ranking. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -205,34 +205,21 @@ pub fn extract(file_path: &str, source: &str) -> Result<EmitOut, String> {
|
||||
w.node_ids.push(ids::file_node_id(file_path));
|
||||
w.stack.push(Scope { row: 0, kind: "file", name: base_name.to_string() });
|
||||
|
||||
// extractFilePackage: the FIRST top-level namespace declaration mints ONE
|
||||
// `namespace` node that stays pushed for the ENTIRE file — a second
|
||||
// top-level namespace's types nest under the first's node/QN, nested
|
||||
// namespaces leave no trace, and every import ref in a namespaced file
|
||||
// hangs off this node (checklist §namespace).
|
||||
// extractFilePackage: a top-level file-scoped `namespace X;` mints ONE
|
||||
// `namespace` node that stays pushed for the ENTIRE file, so every import
|
||||
// ref in the file hangs off it (checklist §namespace). A block namespace
|
||||
// scopes only its own body — visit_node.
|
||||
let root = tree.root_node();
|
||||
let mut pkg_pushed = false;
|
||||
for i in 0..root.named_child_count() {
|
||||
let Some(child) = root.named_child(i) else { continue };
|
||||
if child.kind() != "namespace_declaration"
|
||||
&& child.kind() != "file_scoped_namespace_declaration"
|
||||
{
|
||||
if child.kind() != "file_scoped_namespace_declaration" {
|
||||
continue;
|
||||
}
|
||||
// csharpExtractor.extractPackage: `name` field ?? first
|
||||
// qualified_name/identifier named child. No trim.
|
||||
let name_node = child.child_by_field_name("name").or_else(|| {
|
||||
(0..child.named_child_count())
|
||||
.filter_map(|j| child.named_child(j))
|
||||
.find(|c| matches!(c.kind(), "qualified_name" | "identifier"))
|
||||
});
|
||||
if let Some(name_node) = name_node {
|
||||
let pkg = w.text(name_node).to_string();
|
||||
if !pkg.is_empty() {
|
||||
if let Some(row) = w.create_node("namespace", &pkg, child, Extra::default()) {
|
||||
w.stack.push(Scope { row, kind: "namespace", name: pkg });
|
||||
pkg_pushed = true;
|
||||
}
|
||||
if let Some(pkg) = namespace_name(&w, child) {
|
||||
if let Some(row) = w.create_node("namespace", &pkg, child, Extra::default()) {
|
||||
w.stack.push(Scope { row, kind: "namespace", name: pkg });
|
||||
pkg_pushed = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -265,6 +252,18 @@ fn record_is_struct(node: Node) -> bool {
|
||||
.any(|c| c.kind() == "struct")
|
||||
}
|
||||
|
||||
/// csharpExtractor.extractPackage: `name` field ?? first qualified_name /
|
||||
/// identifier named child. No trim; None when empty.
|
||||
fn namespace_name(w: &Walker, node: Node) -> Option<String> {
|
||||
let name_node = node.child_by_field_name("name").or_else(|| {
|
||||
(0..node.named_child_count())
|
||||
.filter_map(|j| node.named_child(j))
|
||||
.find(|c| matches!(c.kind(), "qualified_name" | "identifier"))
|
||||
})?;
|
||||
let name = w.text(name_node);
|
||||
(!name.is_empty()).then(|| name.to_string())
|
||||
}
|
||||
|
||||
impl<'t> Walker<'t> {
|
||||
fn text(&self, node: Node) -> &'t str {
|
||||
&self.src[node.byte_range()]
|
||||
@@ -518,6 +517,38 @@ impl<'t> Walker<'t> {
|
||||
let kind = node.kind();
|
||||
let mut skip_children = false;
|
||||
|
||||
// A block namespace scopes only its body (tree-sitter.ts visitNode):
|
||||
// one written inside another is `Outer.Inner`, taking the outer's
|
||||
// place on the stack while its body is walked.
|
||||
if kind == "namespace_declaration" {
|
||||
if let Some(ns_name) = namespace_name(self, node) {
|
||||
let outer = match self.stack.last() {
|
||||
Some(top) if top.kind == "namespace" => self.stack.pop(),
|
||||
_ => None,
|
||||
};
|
||||
let full = match &outer {
|
||||
Some(o) => format!("{}.{}", o.name, ns_name),
|
||||
None => ns_name,
|
||||
};
|
||||
let row = self.create_node("namespace", &full, node, Extra::default());
|
||||
if let Some(row) = row {
|
||||
self.stack.push(Scope { row, kind: "namespace", name: full });
|
||||
}
|
||||
for i in 0..node.named_child_count() {
|
||||
if let Some(c) = node.named_child(i) {
|
||||
self.visit_node(c);
|
||||
}
|
||||
}
|
||||
if row.is_some() {
|
||||
self.stack.pop();
|
||||
}
|
||||
if let Some(o) = outer {
|
||||
self.stack.push(o);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.maybe_capture_fn_refs(node);
|
||||
|
||||
if kind == "class_declaration" || kind == "record_declaration" {
|
||||
|
||||
Reference in New Issue
Block a user