fix(extract): nested types get a contains edge from the enclosing type (#2040)
In _extract_generic's class branch the `contains` edge was hard-coded to source from the file node, so a nested class/object/trait attached to the file instead of its enclosing type across ~19 languages (only C# even flagged the node with is_nested_type, and still emitted no edge to the parent). The edge now sources from parent_class_nid when set, else the file node — keeping the containment tree connected (file -> Outer -> Inner). A `!= class_nid` guard avoids a self-loop when same-name nesting collides ids (class ids omit the enclosing name). The C# is_nested_type flag is retained (load-bearing for cross-file resolution). Methods were already parent-sourced and are unaffected.
This commit is contained in:
@@ -2371,7 +2371,19 @@ def _extract_generic(
|
||||
metadata = {"is_nested_type": True}
|
||||
add_node(class_nid, class_name, line, metadata=metadata)
|
||||
callable_def_nids.add(class_nid) # a class is callable (constructor)
|
||||
add_edge(file_nid, class_nid, "contains", line)
|
||||
# A nested class/object/trait is contained by its ENCLOSING type, not
|
||||
# the file (#2040). parent_class_nid is threaded down the walk for
|
||||
# every language and is always a real class-like node (never a
|
||||
# namespace — namespace handlers pass it through unchanged), so it is
|
||||
# a valid edge source. The `!= class_nid` guard avoids a self-loop
|
||||
# when same-name nesting (`class Foo: class Foo`) collides ids, since
|
||||
# class ids omit the enclosing type name. Top-level types (parent
|
||||
# None) still source from the file, keeping the containment tree
|
||||
# connected: file -> Outer -> Inner.
|
||||
if parent_class_nid and parent_class_nid != class_nid:
|
||||
add_edge(parent_class_nid, class_nid, "contains", line)
|
||||
else:
|
||||
add_edge(file_nid, class_nid, "contains", line)
|
||||
|
||||
# TS/JS decorators on the class and its members (@Component, @Injectable,
|
||||
# @Input, @Inject, @Entity, …). Decorators live only in class subtrees.
|
||||
|
||||
@@ -553,6 +553,39 @@ def test_java_enum_and_annotation_declarations_are_type_nodes(tmp_path):
|
||||
assert definitions["Audited"].get("source_file") == str(source)
|
||||
|
||||
|
||||
def test_nested_types_contained_by_enclosing_type(tmp_path):
|
||||
"""#2040: a nested class/object/trait's `contains` edge sources from its
|
||||
ENCLOSING type, not the file node; top-level types still source from the file
|
||||
(keeping the tree connected: file -> Outer -> Inner)."""
|
||||
# Java inner class
|
||||
j = tmp_path / "Outer.java"
|
||||
j.write_text("class Outer {\n class Inner { void m() {} }\n}\n")
|
||||
cj = _edge_labels(extract_java(j), "contains")
|
||||
assert ("Outer.java", "Outer") in cj # top-level still file-sourced
|
||||
assert ("Outer", "Inner") in cj # nested sourced from enclosing type
|
||||
assert ("Outer.java", "Inner") not in cj # NOT from the file (the bug)
|
||||
|
||||
# Scala nested class + object (the issue's repro shape)
|
||||
s = tmp_path / "Outer.scala"
|
||||
s.write_text("class Outer {\n class Inner\n object Obj\n}\n")
|
||||
cs = _edge_labels(extract_scala(s), "contains")
|
||||
assert ("Outer.scala", "Outer") in cs
|
||||
assert ("Outer", "Inner") in cs
|
||||
assert ("Outer", "Obj") in cs
|
||||
assert ("Outer.scala", "Inner") not in cs
|
||||
|
||||
|
||||
def test_csharp_nested_type_gets_containment_edge(tmp_path):
|
||||
"""#2040 for C#: the nested type now gets a real `contains` edge from its
|
||||
enclosing type (the is_nested_type flag it already carried is retained and
|
||||
covered by test_csharp_type_resolution)."""
|
||||
c = tmp_path / "N.cs"
|
||||
c.write_text("namespace N {\n class Outer {\n class Inner {}\n }\n}\n")
|
||||
cc = _edge_labels(extract_csharp(c), "contains")
|
||||
assert ("Outer", "Inner") in cc
|
||||
assert ("N.cs", "Inner") not in cc
|
||||
|
||||
|
||||
def test_csharp_field_type_references_have_field_context():
|
||||
r = extract_csharp(FIXTURES / "sample.cs")
|
||||
refs = _references(r)
|
||||
|
||||
Reference in New Issue
Block a user