What
A text rule sees bytes. regexp, comment_regexp, prose_regexp and
require_regexp are the four pattern checks a set can carry, and every one of
them is a regex over a string. A rule that wants to say "no .unwrap() outside
#[cfg(test)]" or "no unsafe block without a SAFETY: comment naming the
invariant" cannot be written: the first needs to know which item the call sits
in and what attributes that item carries, the second needs the block and the
comment that precedes it as two nodes with an order, and neither is a property
of a line. What a consumer does today is approximate it with a regex that
formatting defeats, or leave the rule unwritten.
The binary already links tree-sitter and three grammars (tree-sitter-rust,
tree-sitter-python, tree-sitter-go, Cargo.toml) and walks a tree in two
places: src/comments.rs for comment_regexp and trivial_comments, and
src/evidence/syntax.rs for the function-declaration provider. Both hard-code
the walk. Nothing lets a set declare one.
Why it matters
The fleet is mostly Rust and Go, written mostly by agents, and the two rules
above are the ones a reviewer asks for by name. Each is a structural question
with a mechanical answer, which is exactly the tier ADR 0003 measured at 0.02 s
for three rules and placed under pre-commit. The rule form is the missing
piece, not the parser.
ADR 0003 and ADR 0005 both record "do not build a rule DSL over tree-sitter in
this binary", on two grounds: a structural rule this repository needs is
cheaper as a test beside the code, and a structural rule a consumer needs is
ast-grep's job. This issue asks to revisit the second ground only. A test
beside the code covers one repository; the fleet-wide rules above are the
policy/base/ argument again, one decision transcribed N times. And ast-grep
as a provider leaves the parse-failure rule (ADR 0003's own finding, that a
recovered tree reads exactly like a clean one) to each adopter, where this
binary already refuses a tree with an ERROR or MISSING node in
src/evidence/syntax.rs. If the ADR stands after that argument, close this
with the reason recorded there.
Proposed
A fifth pattern check, one field beside the other four, no discriminant:
[rule.no-unwrap-outside-tests]
query = '''
(call_expression
function: (field_expression field: (field_identifier) @method)
(#eq? @method "unwrap"))
'''
language = "rust"
message = "unwrap outside #[cfg(test)]: return the error or state why it cannot happen"
files.glob = ["*.rs"]
query is a tree-sitter query in its own syntax, with the standard
predicates. language names the grammar. A rule may add regexp, which then
runs over the text of the captures rather than over the file, so a rule can
say "an unsafe block whose preceding comment does not match SAFETY:" as
a query plus a negative pattern over one capture.
- Grammars are the ones already linked, read off the same table
comments.rs
and evidence/syntax.rs use, so a language added there is a language this
check reads. Rust first; Go and Python follow in a separate issue.
- The parse-failure rule from ADR 0003 applies as it does to the evidence
provider: a selected file the grammar recovered rather than read is named,
with the line, and the run is exit 2, never a shorter list of findings.
- Scope, exclusion,
min_selected, the baseline and the allow marker are the
ones every files.* rule already has. The four existing pattern checks do
not change.
Not in scope
- Type or borrow information. "This
unwrap is on an Option that is never
None" is rustc's and clippy's question, and it runs as an external gate at
the rung its cost puts it on.
- Anything that needs a solver.
- Cross-file questions; a query sees one tree.
What would close it
- One bundled set carrying at least one
query rule, with fixtures that are two
sources one construct apart with opposite verdicts, and a third that does not
parse and is refused.
- A
docs/REFERENCE.md section for the field, in the row of the table the other
pattern checks sit in.
cargo test unchanged for every existing text rule.
- ADR 0003's decision amended or a new ADR recording why the second ground no
longer holds.
Related: #13 (the research issue that framed the tiers), #165 (the evidence
model this check reports into), ADR 0003, ADR 0005.
What
A text rule sees bytes.
regexp,comment_regexp,prose_regexpandrequire_regexpare the four pattern checks a set can carry, and every one ofthem is a regex over a string. A rule that wants to say "no
.unwrap()outside#[cfg(test)]" or "nounsafeblock without aSAFETY:comment naming theinvariant" cannot be written: the first needs to know which item the call sits
in and what attributes that item carries, the second needs the block and the
comment that precedes it as two nodes with an order, and neither is a property
of a line. What a consumer does today is approximate it with a regex that
formatting defeats, or leave the rule unwritten.
The binary already links tree-sitter and three grammars (
tree-sitter-rust,tree-sitter-python,tree-sitter-go,Cargo.toml) and walks a tree in twoplaces:
src/comments.rsforcomment_regexpandtrivial_comments, andsrc/evidence/syntax.rsfor the function-declaration provider. Both hard-codethe walk. Nothing lets a set declare one.
Why it matters
The fleet is mostly Rust and Go, written mostly by agents, and the two rules
above are the ones a reviewer asks for by name. Each is a structural question
with a mechanical answer, which is exactly the tier ADR 0003 measured at 0.02 s
for three rules and placed under
pre-commit. The rule form is the missingpiece, not the parser.
ADR 0003 and ADR 0005 both record "do not build a rule DSL over tree-sitter in
this binary", on two grounds: a structural rule this repository needs is
cheaper as a test beside the code, and a structural rule a consumer needs is
ast-grep's job. This issue asks to revisit the second ground only. A testbeside the code covers one repository; the fleet-wide rules above are the
policy/base/argument again, one decision transcribed N times. Andast-grepas a provider leaves the parse-failure rule (ADR 0003's own finding, that a
recovered tree reads exactly like a clean one) to each adopter, where this
binary already refuses a tree with an ERROR or MISSING node in
src/evidence/syntax.rs. If the ADR stands after that argument, close thiswith the reason recorded there.
Proposed
A fifth pattern check, one field beside the other four, no discriminant:
queryis a tree-sitter query in its own syntax, with the standardpredicates.
languagenames the grammar. A rule may addregexp, which thenruns over the text of the captures rather than over the file, so a rule can
say "an
unsafeblock whose preceding comment does not matchSAFETY:" asa query plus a negative pattern over one capture.
comments.rsand
evidence/syntax.rsuse, so a language added there is a language thischeck reads. Rust first; Go and Python follow in a separate issue.
provider: a selected file the grammar recovered rather than read is named,
with the line, and the run is exit 2, never a shorter list of findings.
min_selected, the baseline and the allow marker are theones every
files.*rule already has. The four existing pattern checks donot change.
Not in scope
unwrapis on anOptionthat is neverNone" is rustc's and clippy's question, and it runs as an external gate atthe rung its cost puts it on.
What would close it
queryrule, with fixtures that are twosources one construct apart with opposite verdicts, and a third that does not
parse and is refused.
docs/REFERENCE.mdsection for the field, in the row of the table the otherpattern checks sit in.
cargo testunchanged for every existing text rule.longer holds.
Related: #13 (the research issue that framed the tiers), #165 (the evidence
model this check reports into), ADR 0003, ADR 0005.