feat(resolution): add Flutter framework support for Dart projects#420
Open
wolfofalgstreet wants to merge 1 commit into
Open
feat(resolution): add Flutter framework support for Dart projects#420wolfofalgstreet wants to merge 1 commit into
wolfofalgstreet wants to merge 1 commit into
Conversation
Adds three Flutter framework resolvers in src/resolution/frameworks/flutter.ts
that fill the two known gaps in the playbook's `Dart | Flutter` matrix row
(`🔬 Navigator/MaterialApp routes` and `🔬 MVVM Command/ChangeNotifier`),
mirroring the swift.ts pattern of grouping multiple co-existing resolvers in
one file each with its own detect(). Extends — does not replace — the
existing upstream Flutter coverage (`setState→build` synthesizer in
callback-synthesizer.ts and the Dart method-range fix).
- flutterResolver — core widget framework
* Detects via pubspec.yaml `flutter: sdk: flutter` OR any .dart file
importing package:flutter/*.
* Marks package:flutter/*, package:flutter_*, dart:ui, dart:ui_web as
framework-provided (confidence 1.0).
* Short-circuits ~140 common Material/Cupertino/Widgets-library built-in
widget names so the name resolver doesn't waste cycles searching for
user-defined symbols.
* Resolves PascalCase user widgets to their class/component nodes,
preferring same-file, same-directory, then /lib/widgets/ and
/lib/screens/ (0.85).
* Pairs `_FooState extends State<Foo>` to its widget class via the
candidates field (0.9). State<X> pattern is checked BEFORE the built-in
short-circuit so candidates resolve correctly.
* Extracts StatelessWidget/StatefulWidget/ConsumerWidget/HookWidget
subclasses as `component` nodes, pairs State<X> classes, and emits a
class node for the runApp() root widget.
- flutterRouterResolver — Navigator named routes + GoRouter / ShellRoute trees
* Detects via go_router in pubspec.yaml OR inline MaterialApp routes
maps / GoRoute calls.
* Extracts MaterialApp `routes: { '/path': (ctx) => Screen() }` maps.
* Extracts GoRoute(path:, builder:) — nested routes join parent paths
via a stack-based scanner tracking parenthesis depth.
* Handles four real-world router idioms that initial probes against
flutter/samples revealed and a literal-only matcher would miss:
- Block-body builders: `builder: (c, s) { return Screen(); }` (the
dominant form for any non-trivial route — most routes use it).
- Generic-typed page wrappers: `pageBuilder: …{ return
FadeTransitionPage<dynamic>(child: Screen()); }` — without an
optional `<…>` allowance, the regex stops at the bare wrapper
name and backtracks into a deeper `return Screen(`, capturing the
wrong handler.
- Top-level-only param scope: a parent ShellRoute / GoRoute body
contains nested `routes: […]` with their own `path:` /
`pageBuilder:` — the matcher bounds its scan to the params
before the first child `routes: [`, so descendants don't
contaminate the parent's path or handler.
- Constant-reference paths: `path: Routes.login` (common when
apps centralize paths in a `class Routes { static const … }`)
— accepted as identifier expressions; the route node carries
the identifier as its name so the route → handler edge still
materializes.
* Resolves route → handler refs preferring /lib/screens/ and
/lib/pages/ (0.85).
- flutterStateResolver — Provider / Riverpod / Bloc / GetX
* Detects each package independently from pubspec.yaml.
* claimsReference() opts framework dispatch shapes (context.read,
ref.watch, BlocProvider.of, Get.find, etc.) through the pre-filter so
they reach resolve() and short-circuit to framework-provided (1.0)
instead of being fuzzy-matched against unrelated symbols.
* Resolves *Provider (Riverpod), *Bloc/*Cubit (flutter_bloc), and
*Controller (GetX) names preferring their conventional directories.
Also adds 'dart' to CommentLang in src/resolution/strip-comments.ts so the
Flutter extractors can scan content with line/block comments stripped (Dart
syntax is C-style, so it routes through stripCStyle with single-quote
string support enabled).
Validation per CLAUDE.md "Validation methodology (REQUIRED for every new
language/framework)":
DETERMINISTIC (no API spend) — codified as
`scripts/agent-eval/validate-flutter.sh` so it re-runs as a 12-second
quality gate:
| repo | tier | nodes | routes | components | route→handler edges |
|---------------------------------|------|-------|--------|------------|---------------------|
| navigation_and_routing | S | 306 | 10/10 | 15 | 8 |
| compass_app/app | M | 1873 | 7/7 | 50 | (provider/MVVM) |
| expressjs/express (control) | — | 990 | 266 | n/a | n/a (regression-free) |
AGENT A/B (Claude Opus headless via Claude Code OAuth, n=2 per arm,
`scripts/agent-eval/run-all.sh`):
small ("How does navigating to /sign-in show the SignInScreen?"):
with codegraph: Read 0-1 / tool-calls 3-4 / 23-26s
without: Read 2 / tool-calls 5-8 / 34-44s
medium ("Routes.home → BookingScreen + ViewModel via nested GoRoute"):
with codegraph: Read 0-1 / tool-calls 3-4 / 24-25s
without: Read 4 / tool-calls 8 / 35-47s
Aggregate (4 A/Bs, 8 arms): Read 3.0 → 0.5 (-83%), duration 40s → 24.5s
(-39%), tool calls -46%. Total spend $1.80. With-arms reliably picked
`codegraph_context` + `codegraph_explore` / `codegraph_node`; without-arms
ls'd, grep'd, then Read the router + screen + viewmodel files.
CHANGELOG.md updated under [Unreleased] → ### Added per the auto-promote
workflow. .claude/skills/agent-eval/corpus.json gains Dart entries for the
two flutter/samples apps so future /agent-eval picks them up automatically.
docs/design/dynamic-dispatch-coverage-playbook.md matrix row reclassified
from `S + X` to `R + S + X` (resolver added on top of existing synthesizer
+ extraction); previous `🔬 MVVM Command/ChangeNotifier` and `🔬
Navigator/MaterialApp routes` gaps are now ✅; new narrower 🔬s logged
(cross-symbol ChangeNotifier→listener synthesis, inline anonymous
`Navigator.push(MaterialPageRoute(builder:))`).
Test coverage in __tests__/frameworks.test.ts (+25 new cases): detect,
resolve, extract for each of the three resolvers (incl. block-body, generic
wrappers, top-level scope, const-ref paths), registry wiring, and
commented-route regression. The existing
`__tests__/frameworks-integration.test.ts > Flutter end-to-end —
setState→build synthesis` test still passes — no regression to the upstream
synthesizer.
Verification summary:
- npx tsc --noEmit : clean
- npm run build : clean (no new SQL/wasm)
- npx vitest run __tests__/frameworks.test.ts : 123/123 pass
- npx vitest run (full suite) : 1042 pass / 2 skipped
- bash scripts/agent-eval/validate-flutter.sh : 11/11 pass
- agent A/B (4 runs) : Read -83%, time -39%
Closes colbymchenry#420.
c32bbc5 to
4959bdd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds three Flutter framework resolvers in
src/resolution/frameworks/flutter.tsthat fill the two known gaps in the playbook'sDart | Fluttermatrix row —🔬 Navigator/MaterialApp routesand🔬 MVVM Command/ChangeNotifier dispatch. Extends (does not replace) the existing upstream Flutter coverage (setState→buildsynthesizer + Dart method-range fix).flutterResolver— widgets (StatelessWidget/StatefulWidget +State<T>pairing +runApproot),package:flutter/*imports + ~140 Material/Cupertino builtins short-circuit as framework-provided (1.0).flutterRouterResolver—MaterialApp(routes:)+GoRoute/ShellRoutetrees with parent-prefix joining. Handles four real-world idioms that initial probes againstflutter/samplesrevealed: block-body builders ({ return Screen(); }), generic-typed page wrappers (FadeTransitionPage<dynamic>(...)), top-level-only param scope (so descendants don't contaminate parent paths), and constant-reference paths (path: Routes.login).flutterStateResolver— Provider / Riverpod / Bloc / GetX. Per-package detect frompubspec.yaml; dispatch shapes (context.read,ref.watch,BlocProvider.of,Get.find) short-circuit;*Provider/*Bloc/*Cubit/*Controllernames resolve preferring conventional dirs.'dart'toCommentLanginstrip-comments.ts(stripCStylewith single-quote strings).Supersedes the prior revision of this PR (originally one 4-file 1168-line commit, pre-rebase). Now rebased onto current
upstream/main(was 34 commits behind), expanded to 8 files / +1491 / −3, with the validation script + matrix update + A/B evidence below.Diff against upstream/main
https://github.com/colbymchenry/codegraph/compare/main...wolfofalgstreet:codegraph:feat/flutter-framework-supportsrc/resolution/frameworks/flutter.tssrc/resolution/frameworks/index.tsFRAMEWORK_RESOLVERS, re-export)src/resolution/strip-comments.ts'dart'inCommentLang+stripCStylebranch)__tests__/frameworks.test.tsscripts/agent-eval/validate-flutter.sh.claude/skills/agent-eval/corpus.jsonnavigation_and_routingS,compass_appM) so/agent-evalpicks them updocs/design/dynamic-dispatch-coverage-playbook.mdS + X→R + S + X; previous🔬gaps now ✅; new narrower 🔬s loggedCHANGELOG.md[Unreleased] → ### AddedentryQuality gate (CLAUDE.md "REQUIRED for every new language/framework")
npx tsc --noEmitnpm run buildnpx vitest run(full suite)__tests__/frameworks.test.tsbash scripts/agent-eval/validate-flutter.shsetState→buildsynthesizer integration testexpressjs/express)A/B detail (
scripts/agent-eval/run-all.sh)navigation_and_routing(S, 17 .dart)compass_app/app(M, 129 .dart, go_router + provider + MVVM)With-arms reliably picked
codegraph_context+codegraph_explore/codegraph_node; without-armsls/grep'd then Read the router + screen + viewmodel files.Known limitations
ChangeNotifier → listenersynthesizer (compass_app's reactive updates still dynamic-dispatch). Logged as🔬in the matrix.Navigator.push(MaterialPageRoute(builder: …))inline anonymous shape isn't extracted — only namedMaterialApp(routes:)map +GoRouteare.context.read<T>()— theT); the Dart extractor doesn't surface generic args today, so theTis unreachable without extractor changes. Out of scope here.Closes #420 (or "Supersedes #420" if opening as a new PR).