Repository navigation
feat(analyzer): extend declarative lint matcher vocabulary (scope lists, deep values search, each-match) - #1013
Conversation
Add the #970 vocabulary so the first-party lint package can express the built-in rules the SPEC-309 matcher could not: - `matcher.scope` accepts a list of scopes; each listed scope is enumerated under one rule ID, so the three loop kinds share a rule. - `matcher.values` lists value patterns searched at any depth in every value tree of the scope: rule conditions and live action arguments for `rule`, live body-action arguments for a loop scope (per the owner decision, not the loop's own condition), and the condition list for `if`. Disabled and dead statements stay excluded. - `matcher.report: each-match` anchors one finding per matched node at the node's own span; the default `scope` keeps one finding per scope. - A `values` pattern may carry `within` plus `in-args: { from: N }`, an ancestor constraint limited to ancestor argument positions >= N, so a binder's array-source argument at index 0 stays outside. - A call pattern accepts `names: [...]` as an alternative to `name`; every spelling resolves through the catalog at load time. - First-party package rules may declare `evidence` and `severity` in metadata; the loader rejects both fields for community packages and `--rule-file` rules, as reserved by SPEC-309/ADR-0015. Acceptance checks live in `crates/wright-analyzer/tests/yaml_contract.rs`: span parity of the three expressible rules against the native analyses on `reproducer-bastion-prophet.ws` (16 nested-binder findings — the issue body's "13" predates the fixture's final count recorded in workshop-rs `docs/import-limits.md`) and on lint fixtures, plus the scope-list, each-match, within/in-args, names, and first-party-metadata contract cases. SPEC-309 and ADR-0015 are amended in the same change.
Each-match findings under a non-`rule` scope now address the scope's anchor action (the loop/if), matching the native loop rules instead of the statement that owns the matched value. A `rule` scope has no anchor, so it keeps the owning-statement semantics the native `nested-binder-lambda` rule uses. The native-vs-declarative parity tests now key on (span, action) so the addressed node is asserted, not just the span. Refs #970
|
Review finding addressed: Finding 1 — Fixed in 849418e: the Verification: Ready for follow-up review. |
Teakowa
left a comment
There was a problem hiding this comment.
LGTM after independent review.
What changed
Extends the SPEC-309 declarative lint matcher so the four structural shapes
behind the built-in rules become expressible (#970, owner decisions
2026-10-27 applied: declarative finding format,
ongoing-condition-hot-pathstays native, loop-scope
valuessearches body actions only).matcher.scopeaccepts a list of scopes — one rule ID enumerates alllisted scopes (
[while, for-global-variable, for-player-variable]shares arule). A single scope behaves as before.
matcher.valueslists value patterns searched at any depth in everyvalue tree of the scope: rule conditions + live action arguments for
rule; live body-action arguments for a loop scope (not the loop's owncondition; a nested loop's body is searched once per enclosing loop); the
condition list for
if. Disabled/dead statements stay excluded.matcher.report: each-matchanchors one finding per matched node at thenode's own span; default
scopeis unchanged.valuespattern may carrywithin(+in-args: { from: N }), anancestor constraint limited to ancestor argument positions ≥ N — so
Filtered Array(Sorted Array(x, r), c)is not reported.names: [...]as an alternative toname; everyspelling resolves through the catalog at load time.
evidenceandseverityinmetadata (Support GitHub-based community lint rule discovery and lifecycle management #616 addendum); both fields are rejected with
lint-rule-errorin community packages and
--rule-filerules.declarative.rssplit intodeclarative/{schema,pattern,error}.rs(size-guard), SPEC-309 + ADR-0015 amended, and the
reproducer-bastion-prophet.wsfixture added (verbatim copy fromworkshop-rs
import-limits, SHA-2563a1cb891…, Apache-2.0).Verification
crates/wright-analyzer/tests/yaml_contract.rsgains cases for everyacceptance criterion: scope lists,
each-matchper-node anchoring at anydepth with disabled exclusion,
within/in-argsbinder semantics,namesresolution + unknown-spelling rejection, first-partyevidence/severityacceptance vs. external rejection, and span-parity checks ofnested-binder-lambda/expensive-loop-check/min-wait-loopwritten inthe new vocabulary against the native rules on
reproducer-bastion-prophet.ws(16 findings — see note) and on lintfixtures.
cargo fmt --all -- --check: clean.cargo clippy --workspace --all-targets --all-features -- -D warnings: clean.cargo test --workspace --all-targets --all-features: passing (see runnotes below).
git diff --check: clean.Unverified / notes
findings; the native rule is the acceptance reference and reports 16
on the current fixture, matching the count workshop-rs
docs/import-limits.mdrecords for this file. Span parity holds; the testasserts parity + 16.
expensive-loop-check's native target list isdistance,raycast,isInLoS—raycastis not a workshop-rs catalog id, so the declarativefixture lists the two resolvable spellings (
distance,isInLoS), whichis the effective native set; a YAML
names: [raycast]would be a loaderror by design (unknown spelling).
Refs #970