Skip to content

A text rule sees bytes, so no bundled rule can say "no unwrap outside cfg(test)"; a rule form over the parse tree the binary already builds #212

Description

@HackingGate

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions