Skip to content

feat(dart): replace regex extractor with tree-sitter-dart - #3651

Open
derekpitts wants to merge 1 commit into
Graphify-Labs:v8from
derekpitts:feat/dart-tree-sitter-extractor
Open

derekpitts wants to merge 1 commit into
Graphify-Labs:v8from
derekpitts:feat/dart-tree-sitter-extractor

Conversation

@derekpitts

Copy link
Copy Markdown

Summary

  • Replace the regex-based Dart extractor with a tree-sitter AST walker using PyPI tree-sitter-dart (UserNobody14 grammar, efrenbl Python packaging).
  • Class/mixin/extension members are now defines-linked from their containing type (not only the file), and a call-graph second pass emits in-file calls edges.
  • Keep Flutter-oriented body heuristics (Bloc / Riverpod / GoRouter-style navigation) with the same context= tags so existing architecture graphs stay rich.
  • Add tree-sitter-dart as a core dependency, tests/fixtures/sample.dart, and tests for class scoping, calls, and missing-grammar errors. Existing tests/test_dart.py cases 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 -q
  • uv run graphify extract <dir-with-sample.dart> --code-only --no-cluster
  • Reviewers: spot-check a small Flutter app for Bloc/Riverpod edges and class→method defines

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.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 extractiongraphify/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 labelsgraphify/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 literalsgraphify/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 stringsgraphify/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 functiongraphify/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-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_external_call_guards.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionextract_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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionemit_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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionhandle_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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionhandle_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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionhandle_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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionhandle_top_level_vars()

fans out to 8 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant