-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commands validate
spm validate checks whether a mutation differs from its base only in widget-tree structure.
Imports, state seeds, rendered content, and compilability must remain unchanged.
This gives generators and benchmark pipelines a static gate before they run a structural variant:
delta(AST structure) → delta(buildSpan)
If a mutation changed a dependency, a seed value, or a displayed string at the same time as the
widget structure, any measured buildSpan difference would be tangled with a hidden data/logic
change. spm validate rejects such pairs before runtime measurement. It is static-only: it
never runs the mutation, and everything operates on the resolved AST.
Status: The current command supports single-pair validation and JSON output. It implements import, seed/member, rendered-content, compile, forbidden-construct, and no-op checks. Value-drift checks, batch validation, and feature-delta audits are planned but not yet implemented.
For a code-level walkthrough of how each check inspects the AST, see validate: AST Internals.
spm validate --base base.dart --mutation mutation.dartFull surface:
spm validate --base <base.dart> --mutation <mutation.dart>
[--deps <dependencies.dart>] [--directive <name>]
[--json] [--strict] [-v]
| Flag | Meaning |
|---|---|
--base, -b
|
Base .dart file (required, must exist). |
--mutation, -m
|
Mutation .dart file (required, must exist). |
--deps, -d
|
Frozen dependency file. Defaults to dependencies.dart next to --base if present; otherwise no deps file is used. |
--directive |
Mutation-operator name (e.g. all_const). Accepted for future feature-delta audits; currently informational. |
--json |
Emit one machine-readable JSON object to stdout. |
--strict |
Soft violations also fail the pair. No soft checks are emitted by the current implementation. |
--verbose, -v
|
Extra progress logging (suppressed in --json mode). |
| Code | Meaning |
|---|---|
0 |
The pair is valid. |
1 |
At least one failing violation, or an internal failure. |
64 |
Usage error (missing/nonexistent file, unknown flag). |
Valid: structure changed and rendered values stayed the same.
// base // mutation
Column( Row(
children: [ children: [
Text("Total"), Text("Total"),
Text("42"), Text("42"),
], ],
) )The pair is rejected if a visible value changed (Text("42") → Text("43")), or an existing label was
duplicated/removed (occurrence count is compared, not just the distinct set).
Each check appends zero or more Violation{code, severity, detail} records. Current checks are
hard (always fail the pair). The detail string is written for a human and for the mutation
generator's retry or repair loop.
| # | Code | What it enforces |
|---|---|---|
| 1 | importsChanged |
The resolved imported-library set (canonical URIs) must match. Reordering or adding an as alias is fine; a genuinely new library is caught even when unreferenced. |
| 2 | seedsChanged |
On the located State class, the initState / dispose / didUpdateWidget bodies and the full field region must be byte-frozen (compared via toSource(), so comments/formatting are invisible). |
| 3 | contentDrift |
Rendered-leaf vocabulary must match: Text / SelectableText / Tooltip(message:) strings (multiset), Icons.* references (set), and AssetImage / Image.asset paths (set). |
| 5 | doesNotCompile |
The mutation must resolve without analyzer error diagnostics. Warnings and lints (e.g. an unused import) do not fail this check. |
| 6 | forbiddenConstruct |
The mutation must not use timing-dependent constructs: async/await, Future/Stream/Timer/Random/Animation*/Ticker*/HttpClient, DateTime.now(), or dart:io/dart:async/dart:isolate/http/dio imports. |
| 7 | noOp |
base.toSource() == mutation.toSource() fails: a mutation must contain a real structural change (comment/reformat-only edits still count as a no-op). |
The valueDrift, featureDeltaTangled, and derivationDrift checks are not implemented yet. Their codes already
exist in the ViolationCode enum so the JSON schema is stable, but no check emits them yet;
featureDelta in the report is currently {}.
Human mode logs each violation through SpmLogger ([severity] code: detail, hard ones to
stderr) and ends with a PASS / FAIL summary line.
--json mode writes exactly one JSON object to raw stdout (bypassing SpmLogger, whose
[spm]: prefix would break json.loads):
{
"base": "/abs/path/base.dart",
"mutation": "/abs/path/mutation.dart",
"baseInstanceId": "494195be",
"mutationInstanceId": "655cc792",
"valid": false,
"violations": [
{ "code": "contentDrift", "severity": "hard",
"detail": "rendered text differs: +[Banner] -[Header]" }
],
"featureDelta": {}
}baseInstanceId / mutationInstanceId are omitted when unknown (e.g. the mutation didn't resolve).
--deps provides a shared dependency environment for analyzer resolution, so base and mutation
resolve against the same mocks, helper classes, and dependency values. The validator does not
audit the internals of dependencies.dart; mock values there are assumed externally frozen, and
editing that file between runs is outside the validator's current responsibility.
spm validate is useful before benchmarking or profiling a structural variant. A generator,
script, or CI job can call spm validate --json --strict, read the violation details, and reject
or repair invalid mutations before runtime measurement. Runtime validity checks such as crashes,
missing frames, or noisy timing distributions remain outside this static validator.
Commands
Reference
Internals
Contributing