mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 15:40:05 +00:00
feat(dart): keep a file node for part files (#4008)
Since #1098 a `part of` file's declarations are redirected to the parent
library so one library has one id namespace. The redirect also dropped
the part's own file node, so a scanned source file could not be found by
name (`explain "<part>.dart"` -> no node).
Keep the redirect and keep the node: the part gets its regular file node,
`library --includes--> part` anchored on the `part of` line, and
`part --contains--> declaration` mirrors every `defines` edge the library
gets from this part. Declaration ids and the library's `defines` edges are
unchanged. test_roadmap_bug_fixes asserted the old "no part node"
behaviour and now asserts the part's own node instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e32f49dbad)
This commit is contained in:
1 parent
1dee8307f9
commit
1dcac128c2
2 files changed
+61
-6
No files matched your search
@@ -45,6 +45,7 @@ def extract_dart(path: Path) -> dict:
|
||||
# Check if this is a part-of file and redirect to parent
|
||||
part_of_match = re.search(r"^\s*part\s+of\s+['\"]([^'\"]+)['\"]", src_clean, re.MULTILINE)
|
||||
is_part = False
|
||||
part_of_line = None
|
||||
if part_of_match:
|
||||
parent_ref = part_of_match.group(1)
|
||||
if parent_ref.endswith(".dart"):
|
||||
@@ -54,13 +55,16 @@ def extract_dart(path: Path) -> dict:
|
||||
stem = _file_stem(parent_path)
|
||||
file_nid = _make_id(str(parent_path))
|
||||
is_part = True
|
||||
part_of_line = _line_at(part_of_match.start(1))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
nodes = []
|
||||
if not is_part:
|
||||
nodes.append({"id": file_nid, "label": path.name, "file_type": "code",
|
||||
"source_file": str(path), "source_location": None})
|
||||
# Every scanned file keeps its own file node. For a part file the symbols
|
||||
# still belong to the library (file_nid above); the part's node is linked
|
||||
# from the library and to its symbols at the end of extraction.
|
||||
own_nid = _make_id(str(path))
|
||||
nodes = [{"id": own_nid, "label": path.name, "file_type": "code",
|
||||
"source_file": str(path), "source_location": None}]
|
||||
edges = []
|
||||
defined: set[str] = set()
|
||||
|
||||
@@ -561,4 +565,14 @@ def extract_dart(path: Path) -> dict:
|
||||
add_edge(file_nid, target_nid, "references", context="type_lookup",
|
||||
line=_line_at(m.start(1)))
|
||||
|
||||
if is_part:
|
||||
# The library includes this part; the part physically contains the
|
||||
# declarations the library defines from it. Ids stay in the library's
|
||||
# namespace, so the library is not split in two.
|
||||
part_edges = [{**e, "source": own_nid, "relation": "contains"}
|
||||
for e in edges
|
||||
if e["source"] == file_nid and e["relation"] == "defines"]
|
||||
add_edge(file_nid, own_nid, "includes", line=part_of_line)
|
||||
edges.extend(part_edges)
|
||||
|
||||
return {"nodes": nodes, "edges": edges}
|
||||
+43
-2
@@ -588,9 +588,11 @@ class TestDart(unittest.TestCase):
|
||||
nodes = result["nodes"]
|
||||
edges = result["edges"]
|
||||
|
||||
# A. Bug D redirect: No child file node should be created in nodes
|
||||
# A. Bug D redirect: the part keeps its own file node (so it can be looked
|
||||
# up by name), but its symbols are redirected to the parent library below.
|
||||
child_node = next((n for n in nodes if n["label"] == "child_part.dart"), None)
|
||||
self.assertIsNone(child_node)
|
||||
self.assertIsNotNone(child_node)
|
||||
self.assertEqual(child_node["id"], _make_id(str(child_file)))
|
||||
|
||||
# B. Check that defines edge source is parent file ID
|
||||
parent_fid = _make_id(str(parent_file.resolve()))
|
||||
@@ -696,5 +698,44 @@ class TestDart(unittest.TestCase):
|
||||
self.assertEqual(inherits["source_location"], "L3")
|
||||
|
||||
|
||||
def test_part_file_keeps_its_own_node(self):
|
||||
"""A `part of` file is a real source file: it must be findable by name and
|
||||
linked to its library and its declarations, while those declarations keep
|
||||
the library's id namespace (Bug D redirect)."""
|
||||
lib_file = self.temp_path / "settlement.dart"
|
||||
lib_file.write_text("part 'derive.dart';\n\nclass Settlement {}\n", encoding="utf-8")
|
||||
part_file = self.temp_path / "derive.dart"
|
||||
part_file.write_text(textwrap.dedent("""\
|
||||
part of 'settlement.dart';
|
||||
|
||||
class DeriveHelper {}
|
||||
|
||||
void deriveAll() {}
|
||||
"""), encoding="utf-8")
|
||||
|
||||
result = extract_dart(part_file)
|
||||
lib_nid = _make_id(str(lib_file.resolve()))
|
||||
part_nid = _make_id(str(part_file))
|
||||
|
||||
part_node = next(n for n in result["nodes"] if n["id"] == part_nid)
|
||||
self.assertEqual(part_node["label"], "derive.dart")
|
||||
self.assertEqual(part_node["source_file"], str(part_file))
|
||||
|
||||
includes = [e for e in result["edges"] if e["relation"] == "includes"]
|
||||
self.assertEqual([(e["source"], e["target"]) for e in includes], [(lib_nid, part_nid)])
|
||||
self.assertEqual(includes[0]["source_location"], "L1")
|
||||
|
||||
defined = {e["target"] for e in result["edges"]
|
||||
if e["source"] == lib_nid and e["relation"] == "defines"}
|
||||
contained = {e["target"] for e in result["edges"]
|
||||
if e["source"] == part_nid and e["relation"] == "contains"}
|
||||
labels = {n["id"]: n["label"] for n in result["nodes"]}
|
||||
self.assertEqual({labels[t] for t in defined}, {"DeriveHelper", "deriveAll"})
|
||||
self.assertEqual(contained, defined)
|
||||
# Ids stay in the library's namespace: nothing is minted under the part's stem.
|
||||
part_stem = _make_id(_file_stem(part_file))
|
||||
for nid in defined:
|
||||
self.assertFalse(nid.startswith(part_stem + "_"), nid)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user