Skip to content

Verification tools

None of the tools below overlap perfectly, and none is close to complete on its own — the AI Bug Catalog’s “Caught by” line for each of its 28 entries names exactly which one (if any) of the tools below would have caught it, and most of those 28 name none of them at the point the bug is introduced. Every output below is real, freshly captured for this module against a throwaway scratch project on this Mac (Flutter 3.47.5, Dart 3.13.4), never the course’s own shared probe.

flutter analyze — proves types and syntax, never runtime behavior

Section titled “flutter analyze — proves types and syntax, never runtime behavior”

This is the cheapest, fastest gate in this whole ladder, and it is important to be precise about what it actually rules out. A ConsumerWidget with no ProviderScope above it, a ListView about to overflow its height constraint, and a compute() closure about to fail at runtime all analyze perfectly cleanly:

$ flutter analyze lib/proof/a1_provider_scope.dart lib/proof/c1_unbounded_listview.dart
No issues found!

flutter analyze is a real, useful, zero-cost gate for a genuinely different class of bug (a typo’d parameter name, a wrong argument type, an undefined method) than almost everything runtime-shaped in this module’s catalog — see entries 5, 18, 19, 24, 25, 27, and 28 for the diagnostics it DOES catch.

analysis_options.yaml strict modes — real, current gaps in what’s enabled by default

Section titled “analysis_options.yaml strict modes — real, current gaps in what’s enabled by default”

Two separate findings, both proven fresh for this module:

flutter_lints 6.0.0 does not ship the general prefer_const_constructors rule — confirmed by reading the package’s own flutter.yaml rule list directly (it has prefer_const_constructors_in_immutables only) and by a real before/after flutter analyze run:

$ flutter analyze lib/proof/h1_prefer_const.dart # flutter_lints 6 alone
No issues found!
$ flutter analyze lib/proof/h1_prefer_const.dart # same file, prefer_const_constructors: true added
info • Use 'const' with the constructor to improve performance. Try adding the 'const' keyword to
the constructor invocation • prefer_const_constructors

very_good_analysis 11.0.0, by contrast, DOES include prefer_const_constructors — confirmed by reading its own shipped analysis_options.11.0.0.yaml rule list directly, line 131.

The three language: strict-mode flags — strict-casts, strict-inference, strict-raw-types — are analyzer options, not lint rules, off by default, and are already proven in full with real diagnostics in Project and Tooling; this module does not re-prove them, only cross-references them as part of the same ladder.

flutter_lints / riverpod_lint / custom_lint — a real, current version-resolution finding

Section titled “flutter_lints / riverpod_lint / custom_lint — a real, current version-resolution finding”

This is worth stating precisely, because it directly contradicts the common assumption that riverpod_lint still runs on top of custom_lint: as of this course’s own dependency check (2026-09-29), the latest riverpod_lint (3.1.9) no longer depends on custom_lint at all. Reading its own pubspec.yaml confirms this — its dependencies are analysis_server_plugin, analyzer, and analyzer_plugin, with no custom_lint/custom_lint_builder anywhere. riverpod_lint migrated to Dart’s native analyzer-plugin system somewhere around 3.1.0.

Trying to install custom_lint and riverpod_lint together, at their latest versions, fails to resolve at all:

$ flutter pub get # custom_lint: ^0.8.1, riverpod_lint: ^3.1.9
Because riverpod_lint >=3.1.4-dev.1 depends on analyzer_plugin ^0.14.0 and custom_lint >=0.7.4
depends on analyzer_plugin ^0.13.0, riverpod_lint >=3.1.4-dev.1 is incompatible with custom_lint
>=0.7.4.

Pinning an OLDER riverpod_lint (3.1.0) to dodge that conflict resolves — but only by also pinning riverpod/flutter_riverpod down to 3.1.0, nowhere near this course’s target 3.4.3:

$ flutter pub get # custom_lint: ^0.8.1, riverpod_lint: 3.1.0
+ riverpod 3.1.0 (3.4.3 available)
+ riverpod_lint 3.1.0 (3.1.9 available)
Changed 58 dependencies!

The real, current, correct setup does not use custom_lint for riverpod_lint at all. riverpod_lint’s own README documents the native plugin mechanism directly:

analysis_options.yaml
analyzer:
plugins:
- riverpod_lint
plugins:
riverpod_lint: 3.1.9

Set up this way (no custom_lint dependency needed anywhere), dart analyze produces a REAL riverpod_lint finding — captured fresh for this module, on a ConsumerWidget with no ProviderScope:

$ dart analyze
warning - lib/main.dart:4:3 - Flutter applications using Riverpod must contain a ProviderScope at
the root of their widget tree. - missing_provider_scope

dart fix --dry-run — shows what’s mechanically fixable, without applying it

Section titled “dart fix --dry-run — shows what’s mechanically fixable, without applying it”

Real output for the cacheExtent deprecation from the AI Bug Catalog’s entry 24:

$ dart fix --dry-run lib/proof/g3_cache_extent.dart
Computing fixes in g3_cache_extent.dart (dry run)...
1 proposed fix in 1 file.
g3_cache_extent.dart
deprecated_member_use - 1 fix
To fix an individual diagnostic, run one of:
dart fix --apply --code=deprecated_member_use lib/proof/g3_cache_extent.dart

Worth noting precisely: dart fix flags that a deprecated_member_use diagnostic HAS a proposed fix — it does not by itself tell a reader whether that mechanical fix (a straight rename) is actually correct. Entry 24 in the catalog is exactly this case: cacheExtent: 500 → scrollCacheExtent: 500 mechanically “fixes” the deprecation warning but produces a NEW, different error (ScrollCacheExtent is a sealed class, not a double) — dart fix --apply alone does not know that, and a reviewer still has to check.

flutter test — proves behavior, including --coverage and golden tests

Section titled “flutter test — proves behavior, including --coverage and golden tests”

Every dual-assert and fail-then-pass test in the AI Bug Catalog is a flutter test proof. Two further modes worth knowing:

--coverage produces a real coverage/lcov.info, line-by-line:

$ flutter test --coverage test/proof/a4_late_uninitialized_test.dart
00:00 +2: All tests passed!
$ cat coverage/lcov.info
SF:lib/proof/a4_late_uninitialized.dart
DA:6,2
LF:1
LH:1
end_of_record

DA:6,2 means line 6 was hit twice across the test run — real, per-line execution counts, not an estimate.

Golden tests compare a widget’s actual rendered pixels against a saved reference PNG. Generating and then genuinely breaking one, fresh for this module:

$ flutter test --update-goldens test/proof/golden_demo_test.dart
✓ golden: a simple badge matches the reference PNG
00:00 +1: All tests passed!
$ # change the badge's color, then re-run WITHOUT --update-goldens:
$ flutter test test/proof/golden_demo_test.dart
Golden "badge.png": Pixel test failed, 0.83%, 4000px diff detected.
Failure feedback can be found at .../test/proof/failures

A golden test catches ANY visual regression — including ones that no assertion would ever think to check for — but is inherently brittle across different rendering environments (font hinting, OS-level anti-aliasing); this course’s own harness does not generate or check golden files for lesson fences, for exactly that portability reason.

flutter build web --wasm — a real portability gate

Section titled “flutter build web --wasm — a real portability gate”

Proven fresh for this module: the exact dart:io/Platform.isIOS snippet from the AI Bug Catalog’s entry 19 compiles cleanly under BOTH the default JS target and --wasm:

$ flutter build web --release
✓ Built build/web
$ flutter build web --wasm --release
┌─ New feature ──────────────────────────────────────────────────────────────┐
│ WebAssembly compilation is new. Understand the details before deploying │
│ to production. See https://flutter.dev/to/wasm for more information. │
└──────────────────────────────────────────────────────────────────────────┘
✓ Built build/web

Both builds succeed with zero warning about dart:io — --wasm is a portability gate for a DIFFERENT compile target, not a stricter analyzer; it does not catch runtime-only bugs any more than the default web build does.

DevTools — documented, not run (no device in this environment)

Section titled “DevTools — documented, not run (no device in this environment)”

Per this module’s hard constraint (no emulator/device exists in this environment), DevTools’ widget inspector, layout explorer, and performance/memory views are not screenshotted here. What DevTools would add on top of everything above, per its own documentation: a live widget-tree inspector (would show a missing ProviderScope’s actual absence in the tree, not just the resulting crash text), a layout explorer that visualizes constraints directly (would show entry 9’s unbounded height visually, before it throws), and a memory view that would show entry 22’s leaked Ticker accumulating over time, not just the one-time dispose-time assertion this catalog captures.

DartPad — embedded elsewhere in this course, not duplicated here

Section titled “DartPad — embedded elsewhere in this course, not duplicated here”

This course’s DartPad.astro component (embedded in dart-language-tour and widgets-and-the-tree) is maintained separately from this module. Most of this catalog’s snippets need packages outside DartPad’s whitelist (flutter_riverpod, go_router) or a full flutter test run DartPad cannot perform, so this lesson does not attempt its own embed.

This course’s own harness: tools/verify-snippets.mjs

Section titled “This course’s own harness: tools/verify-snippets.mjs”

Every fence in this module that declares a real path on its first line (// lib/ai-bug-catalog/<name>.dart, // test/ai-bug-catalog/<name>_test.dart) is collected and checked by this repository’s own harness — read tools/verify-snippets.mjs’s header comment (its first ~150 lines) before trusting any description of it secondhand, including this one. Real, freshly run against this exact module’s catalog:

$ npm run -s verify
reading-flutter: 91 / 1 / 6 / 19 # collected / skipped-no-path / expect-error / tests
$ npm run -s verify -- --test reading-flutter/ai-bug-catalog
reading-flutter/ai-bug-catalog: 16/16 passed

A fence whose first line contains @expect-error (entries 5, 14, 27, and 28’s buggy snippets in the catalog) is deliberately NOT collected — the harness skips it entirely, and the lesson prose carries the real, separately-captured output instead, exactly like every other module in this course.

Honest, stated limits of this harness, read directly from its own header: it has no --build mode — flutter build web/apk per lesson was assessed as too slow for routine verification, and this is a documented, accepted gap, not an oversight (this lesson’s own flutter build web --release/--wasm proofs for entries 19 and 26 were run by hand in a scratch project for exactly that reason, not through the harness). Codegen output (build_runner, riverpod_generator, .g.dart files) is not generated by the harness either — a lesson that needs generated code ships the .g.dart content as its own real fence and says in prose that it is generated; this module’s catalog has no codegen-dependent entries, so this limit does not apply here directly.

flowchart TD
  subgraph Static["Static (no run needed)"]
    Analyze["flutter analyze<br/>types/syntax only, never runtime behavior"]
    Fix["dart fix --dry-run<br/>mechanically fixable, not necessarily CORRECTLY fixed"]
    Lint["riverpod_lint (native plugin)<br/>missing_provider_scope and more"]
  end
  subgraph Dynamic["Needs a real run"]
    Test["flutter test<br/>proves behavior; --coverage counts real executions"]
    Golden["flutter test --update-goldens<br/>catches ANY visual regression, brittle across environments"]
    Build["flutter build web / --wasm<br/>proves it COMPILES, never that it's safe to RUN"]
  end
  subgraph NotRun["Documented, not run here"]
    DevTools["DevTools<br/>no device in this environment"]
    DartPad["DartPad<br/>most snippets need packages outside its whitelist"]
  end
  subgraph Harness["This course's own harness"]
    Verify["npm run verify / --test<br/>collects real path-commented fences only"]
  end
Static tools prove things from source alone; dynamic tools need a real run
Given this real, current pub resolution failure, what is the actual correct way to run riverpod_lint today, per its own README?
$ flutter pub get   # custom_lint: ^0.8.1, riverpod_lint: ^3.1.9
Because riverpod_lint >=3.1.4-dev.1 depends on analyzer_plugin ^0.14.0 and custom_lint >=0.7.4
depends on analyzer_plugin ^0.13.0, riverpod_lint >=3.1.4-dev.1 is incompatible with custom_lint
>=0.7.4.
Why does this lesson say dart fix --dry-run reporting a fix for deprecated_member_use is not the same as that fix being CORRECT?
Why does this lesson say flutter build web --wasm succeeding proves compilation, never runtime safety, using entry 19's dart:io example specifically?