main gained six PRs (#1-#6) while the audit fixes and the state-map seam were in progress. Four of main's commits fixed the same bugs this branch did, independently; where they overlap, the more complete version wins: - Fingerprints: main's (a0844bb) - catchSite (`member#index` among all catches), union/owner as identity, a legacy fingerprint so version-1 baselines still match, baseline "version": 2. This branch's `catch` ordinal is dropped; its fingerprint_test still guards the result. - verify --rule: main's verifyAnalysis (cf9e0b7), which also reports findings in skipped files as NOT SCANNED. - Unreadable SDK: both kept - this branch's up-front probe gives the early, specific error; main's catch around the analyzer (d6b6bcb) is the backstop. - Riverpod: main's "a ref.listen is not a render" (f6411dc) and this branch's AsyncError patterns / stored-value tracking compose; the fixture keeps both sets of cases. Ported onto the model-only rules (the seam), since main edited code this branch had moved into front-end facts: - excerpt.dart's literal-free, bounded quoting (a prompt-injection guard) now also covers front-end facts: SwallowedFailure.handler and NonSubscribingRead.excerpt, and the C++ front end elides string literals in handler evidence the same way. - ScanCoverage: runChecks stays source-free; the model carries coverage (`scanCoverage` in the state JSON, written by both front ends), so `check --model` states coverage like a source check. - main's softened wording (rendered-not-emitted "probably unreachable", the remove-policy dead-handler text, notifier-field-unread) and the backticked flag message. Docs: main's README grew the Firestore query inventory and the output contract; the README stays short, the inventory moves to docs/queries.md, and the contract, exit codes, verify and baseline details join docs/usage.md. Checked against origin/main on resolved mirrors of the dogfood apps: nuwayze (102), dex (0) and word_alive (0) identical; tick differs only by the intended riverpod stored-value fix. On the fixture every difference is one of this branch's fixes. 214 tests pass.
98 lines
4.7 KiB
Markdown
98 lines
4.7 KiB
Markdown
# Design
|
|
|
|
## Motivation
|
|
|
|
The state bugs users feel (a spinner that never stops, an error never shown, a
|
|
tap that does nothing) are whole-program facts: a state emitted in one file and
|
|
never matched in any other, an event dispatched with no handler anywhere. A
|
|
per-file linter cannot express them, so pipe resolves the whole project and
|
|
reasons over all of it at once.
|
|
|
|
pipe is a CLI on `package:analyzer`, not a `custom_lint` or analyzer plugin:
|
|
plugins have no project-wide index, `flutter analyze` does not report
|
|
new-system plugin diagnostics, and a CLI can pin its own analyzer version.
|
|
|
|
## Architecture
|
|
|
|
```text
|
|
bin/pipe.dart entrypoint
|
|
lib/src/cli.dart commands and flags
|
|
lib/src/errors.dart PipeException (message + remedy)
|
|
lib/src/project.dart package validation, source discovery
|
|
lib/src/sdk.dart SDK discovery (Flutter-aware)
|
|
lib/src/analysis.dart analysis contexts, per-file resolution, scan
|
|
coverage, unresolved references
|
|
lib/src/pool.dart bounded-concurrency map over files
|
|
lib/src/excerpt.dart bounded, literal-free source excerpts
|
|
lib/src/index.dart resolution health, timing, diagnostics
|
|
lib/src/model.dart declaration model (`graph`)
|
|
lib/src/state_flow.dart Dart front end: the state model and its JSON
|
|
lib/src/dart_facts.dart Dart front end: loading and construction facts
|
|
lib/src/dart_provider_facts.dart Dart front end: ChangeNotifier / provider facts
|
|
lib/src/facts.dart language-neutral facts (JSON round-trip)
|
|
lib/src/rules.dart rules, findings, baselines (model only)
|
|
lib/src/provider_rules.dart ChangeNotifier / provider rules (model only)
|
|
lib/src/pipeline.dart analyse -> model -> rules
|
|
lib/src/verify.dart baseline verification
|
|
lib/src/suggest.dart fix order
|
|
lib/src/guide.dart agent brief, markdown report, yams emission
|
|
lib/src/recall.dart labelled-bug recall
|
|
lib/src/firestore_facts.dart Firestore query inventory (no rules)
|
|
lib/src/gateway_facts.dart adapters for declarative query APIs
|
|
tool/adjudicate.py precision adjudicator
|
|
tool/cpp_front.py experimental C++ front end
|
|
tool/build_release.sh native AOT build
|
|
```
|
|
|
|
Resolution mirrors Flutter's own tooling: the context collection is rebuilt
|
|
when `package_config.json` changes, file changes go through `changeFile` and
|
|
`applyPendingFileChanges`, and an `InconsistentAnalysisException` gets a fresh
|
|
session.
|
|
|
|
## The state model
|
|
|
|
Rules never read a syntax tree. A front end turns code into a state model:
|
|
unions and variants, where each is emitted and matched, events dispatched and
|
|
handled, busy paths whose failure is swallowed, and observable objects with
|
|
whether their mutations are announced. `runChecks` takes only that model.
|
|
|
|
The model is plain JSON, and a model read back from JSON reports exactly what
|
|
the in-process one does (`test/state_map_roundtrip_test.dart`):
|
|
|
|
```bash
|
|
pipe state . --format json > model.json # or any front end that writes it
|
|
pipe check --model model.json # every rule, baseline and gate
|
|
```
|
|
|
|
A front end for another language only has to write that JSON.
|
|
|
|
## C++ front end (experimental)
|
|
|
|
`tool/cpp_front.py` reads a compile database with libclang (Homebrew LLVM) and
|
|
maps each project `enum class` to a union:
|
|
|
|
- **emission**: a reference that produces the value (assignment, atomic
|
|
`store`, argument, return, initialiser); a cast from an integer is an
|
|
indirect emission, which makes "never emitted" unprovable for that enum;
|
|
- **observation**: a `case`, `==` or `!=`;
|
|
- **swallowed failure**: a busy enumerator written to the method's own state,
|
|
then a top-level `catch` that neither writes state nor rethrows.
|
|
|
|
Identity comes from clang's resolved AST, so two enums with the same name are
|
|
never confused. Sources the build does not compile are recorded as unresolved.
|
|
|
|
```bash
|
|
tool/cpp_front.py <repo> --build-dir <repo>/build/debug -o model.json
|
|
pipe check --model model.json
|
|
```
|
|
|
|
For C++, read `rendered-not-emitted` as "branched on but never produced" (a
|
|
dead branch, or a state a state machine never enters) and `emitted-not-rendered`
|
|
as "produced but never branched on" (computed and ignored, or reaching output
|
|
only through a name table or reflection). Rule messages still say "UI".
|
|
|
|
On yams (375 translation units, about two minutes) a hand check of 13 findings
|
|
found 12 factually correct and 1 false positive, which the front end now
|
|
excludes. That sample bounds the false-positive rate; it does not measure it.
|
|
Controls live in `test/fixtures/cpp_state` and `test/cpp_front_test.dart`,
|
|
skipped when LLVM is absent.
|