feat(dart): replace regex extractor with tree-sitter-dart - #3651
derekpitts wants to merge 1 commit into
Conversation
Upgrade .dart extraction to an AST walker for class-scoped members and in-file call edges, while retaining Flutter Bloc/Riverpod/navigation heuristics. Add tree-sitter-dart as a core dependency with fixtures and tests.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Rewrites the Dart extractor to walk the tree-sitter AST instead of scanning with regex, emitting class/mixin/function/import nodes and extends/implements/with heritage edges with source locations on the edges themselves. Adds _apply_flutter_heuristics to layer Bloc, Riverpod, and navigation edges (on<Event>, emit, context.read().add, ref.watch, BlocBuilder<...>, route pushes) parsed from method-body text, plus preserved generic type-lookup references for backward compatibility. Introduces helper support for qualified type names via _collect_top_type_names and _qualified_type_before, and filters out Dart builtin types so heuristic edges point only at user-defined symbols.
Worth a look
- extract_dart now hard-requires tree-sitter-dart for any extraction —
graphify/extractors/dart.py· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Call resolution conflates duplicate method labels —
graphify/extractors/dart.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Flutter heuristics emit edges from comments and string literals —
graphify/extractors/dart.py:176· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Generic type lookup scans comments and strings —
graphify/extractors/dart.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Nested local function bodies are attributed to the enclosing function —
graphify/extractors/dart.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 68 functions depend on the 67 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract_dart()— 13 callers, 14 callees - new:
emit_annotation_edges()— 4 callers, 5 callees - new:
handle_type_decl()— 1 callers, 10 callees - new:
handle_class_body()— 1 callers, 9 callees - new:
handle_top_level_vars()— 1 callers, 8 callees - new:
handle_top_function()— 1 callers, 7 callees
Verification — 68 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 68 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
286 of 286 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.
tests/test_affected_cli.py— full-run-safetytests/test_affected_member_seed.py— full-run-safetytests/test_agents_platform.py— full-run-safetytests/test_analyze.py— full-run-safetytests/test_anthropic_custom_endpoint.py— full-run-safetytests/test_antigravity_install.py— full-run-safetytests/test_apm_fallback_version.py— full-run-safetytests/test_architecture_doc.py— full-run-safetytests/test_astro_extraction.py— full-run-safetytests/test_astro_import_ids.py— full-run-safetytests/test_atomic_canvas_export.py— full-run-safetytests/test_atomic_version_stamp.py— full-run-safetytests/test_atomic_writes.py— full-run-safetytests/test_backend_env_isolation.py— full-run-safetytests/test_backend_extras.py— full-run-safetytests/test_benchmark.py— full-run-safetytests/test_benchmark_raw_graph.py— full-run-safetytests/test_build.py— full-run-safetytests/test_build_merge_dedup_scope.py— full-run-safetytests/test_build_merge_hyperedges_and_prune.py— full-run-safetytests/test_build_merge_shrink_guard.py— full-run-safetytests/test_builtin_global_type_refs.py— full-run-safetytests/test_cache.py— full-run-safetytests/test_callflow_html.py— full-run-safetytests/test_cargo_introspect.py— full-run-safetytests/test_carried_hyperedge_remap.py— full-run-safetytests/test_case_sensitive_resolution.py— full-run-safetytests/test_charmap_encoding.py— full-run-safetytests/test_chunking.py— full-run-safetytests/test_cjs_module_extension.py— full-run-safetytests/test_claude_cli_backend.py— full-run-safetytests/test_claude_md.py— full-run-safetytests/test_cli_broken_pipe.py— full-run-safetytests/test_cli_export.py— full-run-safetytests/test_cli_help.py— full-run-safetytests/test_cluster.py— full-run-safetytests/test_codebuddy.py— full-run-safetytests/test_community_hub_labels.py— full-run-safetytests/test_community_labels_skill.py— full-run-safetytests/test_confidence.py— full-run-safetytests/test_corrupt_graph_json.py— full-run-safetytests/test_cpp_nested_and_cli.py— full-run-safetytests/test_cpp_objc_cross_file_calls.py— full-run-safetytests/test_cpp_preprocess.py— full-run-safetytests/test_cross_extension_reexport_self_cycle.py— full-run-safetytests/test_cross_language_call_resolution.py— full-run-safetytests/test_cross_repo_external_call_guards.py— full-run-safetytests/test_cross_repo_member_calls.py— full-run-safetytests/test_cross_repo_shared_types.py— full-run-safetytests/test_csharp_call_site_generic_args.py— full-run-safety- … and 236 more
non-code file(s) changed (
pyproject.toml,uv.lock) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
pyproject.toml,tests/fixtures/sample.dart) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify extract\_dart.
The verifier did not have enough to check extract\_dart, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 6 grounded finding(s) anchored inline below.
| ) | ||
|
|
||
|
|
||
| def extract_dart(path: Path) -> dict: |
There was a problem hiding this comment.
extract_dart()
fans out to 14 callees (efferent coupling); 13 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| if not is_part: | ||
| add_node(file_nid, path.name, source_file=str_path, line=None) | ||
|
|
||
| def emit_annotation_edges(ann_node, target_nid: str, target_name: str, target_kind: str) -> None: |
There was a problem hiding this comment.
emit_annotation_edges()
high coupling complexity (Ca·Ce = 20).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| base_nid = ensure_external(base) | ||
| add_edge(type_nid, base_nid, "inherits", line=line) | ||
|
|
||
| def handle_class_body(body_node, class_nid: str, class_name: str) -> None: |
There was a problem hiding this comment.
handle_class_body()
fans out to 9 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| attribute_generic_lookups_to=file_nid, | ||
| ) | ||
|
|
||
| def handle_type_decl(node) -> None: |
There was a problem hiding this comment.
handle_type_decl()
fans out to 10 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| add_edge(type_nid, tid, "extends", line=line) | ||
| break | ||
|
|
||
| def handle_top_function(sig_node, body_node, pending_annotations: list) -> None: |
There was a problem hiding this comment.
handle_top_function()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| break | ||
| add_edge(file_nid, tgt, "exports" if is_export else "imports", line=line) | ||
|
|
||
| def handle_top_level_vars(program) -> None: |
There was a problem hiding this comment.
handle_top_level_vars()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Summary
tree-sitter-dart(UserNobody14 grammar, efrenbl Python packaging).defines-linked from their containing type (not only the file), and a call-graph second pass emits in-filecallsedges.context=tags so existing architecture graphs stay rich.tree-sitter-dartas a core dependency,tests/fixtures/sample.dart, and tests for class scoping, calls, and missing-grammar errors. Existingtests/test_dart.pycases still pass (invalid#“comments” in the advanced fixture updated to//).Test plan
uv run python -m pytest tests/test_dart.py tests/test_extract.py::test_dart_child_node_ids_are_stem_based -quv run graphify extract <dir-with-sample.dart> --code-only --no-clusterdefines