mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-14 07:59:40 +00:00
fix(dart): link every URI of a configurable import/export (#4002)
A configurable directive such as
export 'service_stub.dart' if (dart.library.io) 'service_io.dart';
was matched only up to its first quoted string, so the `if` alternatives
were dropped. In the usual Flutter facade the stub is an empty placeholder
and the real implementation lives in the conditional branch, which left
the implementation unreachable from the facade and its importers.
Parse the whole directive and emit one edge per URI: the default URI keeps
its plain edge (unchanged), each alternative gets the same relation tagged
context="conditional_uri" with its own source line. A string literal inside
the condition (`if (app.flavor == 'prod')`) is part of the URI test and is
not mistaken for a URI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 2b965f23fc)
This commit is contained in:
1 parent
1dcac128c2
commit
e402b87b73
2 files changed
+63
-11
No files matched your search
+22
-11
@@ -539,17 +539,28 @@ def extract_dart(path: Path) -> dict:
|
||||
line=_line_at(brace_pos + om.start(1)))
|
||||
|
||||
# 6. Imports and Exports
|
||||
for m in re.finditer(r"""^\s*import\s+['"]([^'"]+)['"]""", src_clean, re.MULTILINE):
|
||||
pkg = m.group(1)
|
||||
tgt_nid = _make_id(pkg)
|
||||
add_node(tgt_nid, pkg, source_file=None)
|
||||
add_edge(file_nid, tgt_nid, "imports", line=_line_at(m.start(1)))
|
||||
|
||||
for m in re.finditer(r"""^\s*export\s+['"]([^'"]+)['"]""", src_clean, re.MULTILINE):
|
||||
pkg = m.group(1)
|
||||
tgt_nid = _make_id(pkg)
|
||||
add_node(tgt_nid, pkg, source_file=None)
|
||||
add_edge(file_nid, tgt_nid, "exports", line=_line_at(m.start(1)))
|
||||
# A directive may carry configurable URIs that pick a platform-specific
|
||||
# library: `export 'x_stub.dart' if (dart.library.io) 'x_io.dart';`. The
|
||||
# default URI keeps its plain edge; every `if (...)` alternative is emitted
|
||||
# too, tagged context="conditional_uri", because the real implementation
|
||||
# usually lives there. A string literal inside the condition itself
|
||||
# (`if (dart.library.io == 'true')`) is part of the test, not a URI.
|
||||
quoted = r"""(?:'[^']*'|"[^"]*")"""
|
||||
condition = rf"""if\s*\(\s*[\w.]+\s*(?:==\s*{quoted}\s*)?\)\s*"""
|
||||
branch_re = re.compile(rf"""{condition}(['"])(?P<uri>[^'"]+)\1""")
|
||||
for kind, relation in (("import", "imports"), ("export", "exports")):
|
||||
directive_re = re.compile(
|
||||
rf"""^\s*{kind}\s+(['"])(?P<uri>[^'"]+)\1(?P<branches>(?:\s*{condition}{quoted})*)""",
|
||||
re.MULTILINE,
|
||||
)
|
||||
for m in directive_re.finditer(src_clean):
|
||||
uris: list[tuple[str, int, str | None]] = [(m.group("uri"), m.start("uri"), None)]
|
||||
for bm in branch_re.finditer(m.group("branches")):
|
||||
uris.append((bm.group("uri"), m.start("branches") + bm.start("uri"), "conditional_uri"))
|
||||
for pkg, offset, context in uris:
|
||||
tgt_nid = _make_id(pkg)
|
||||
add_node(tgt_nid, pkg, source_file=None)
|
||||
add_edge(file_nid, tgt_nid, relation, context=context, line=_line_at(offset))
|
||||
|
||||
# 7. Generic Invocations / Type Lookups (Universal Dependency Lookup)
|
||||
# Matches any method call with type parameters: methodName<Type>() or object.methodName<Type>()
|
||||
|
||||
@@ -737,5 +737,46 @@ class TestDart(unittest.TestCase):
|
||||
for nid in defined:
|
||||
self.assertFalse(nid.startswith(part_stem + "_"), nid)
|
||||
|
||||
def test_conditional_uris_emit_every_branch(self):
|
||||
"""A configurable export/import must link every platform branch, not only
|
||||
the default URI; a string literal inside the condition is not a URI."""
|
||||
code = textwrap.dedent("""\
|
||||
import 'conn_stub.dart'
|
||||
if (dart.library.io) 'conn_io.dart'
|
||||
if (dart.library.js_interop) 'conn_web.dart';
|
||||
import 'flags.dart' if (app.flavor == 'prod') 'flags_prod.dart';
|
||||
export 'service_stub.dart' if (dart.library.io) "service_io.dart";
|
||||
""")
|
||||
path = self.temp_path / "facade.dart"
|
||||
path.write_text(code, encoding="utf-8")
|
||||
result = extract_dart(path)
|
||||
file_nid = _make_id(str(path))
|
||||
|
||||
def targets(relation, context):
|
||||
return {
|
||||
e["target"]
|
||||
for e in result["edges"]
|
||||
if e["source"] == file_nid
|
||||
and e["relation"] == relation
|
||||
and e.get("context") == context
|
||||
}
|
||||
|
||||
self.assertEqual(targets("imports", None), {_make_id("conn_stub.dart"), _make_id("flags.dart")})
|
||||
self.assertEqual(
|
||||
targets("imports", "conditional_uri"),
|
||||
{_make_id("conn_io.dart"), _make_id("conn_web.dart"), _make_id("flags_prod.dart")},
|
||||
)
|
||||
self.assertEqual(targets("exports", None), {_make_id("service_stub.dart")})
|
||||
self.assertEqual(targets("exports", "conditional_uri"), {_make_id("service_io.dart")})
|
||||
labels = {n["label"] for n in result["nodes"]}
|
||||
self.assertNotIn("prod", labels)
|
||||
loc = {
|
||||
e["target"]: e["source_location"]
|
||||
for e in result["edges"]
|
||||
if e["relation"] in ("imports", "exports")
|
||||
}
|
||||
self.assertEqual(loc[_make_id("conn_web.dart")], "L3")
|
||||
self.assertEqual(loc[_make_id("service_io.dart")], "L5")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user