Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ repos:
# a second copy of it to go stale.

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.2
rev: v0.16.3
hooks:
- id: ruff-check
args: [--fix]
Expand Down
91 changes: 91 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ unicode_names2 = "3.1"
# `parameterize-do-not-enumerate` names.
encoding_rs = "0.8"
serde_yaml_ng = "0.10.0"
# A parser, for the one question a regex over file contents cannot answer:
# whether the text it matched is a comment. `grep-regex` reads bytes, so
# `let s = "// TODO";` is a hit and a rule about comments is a rule about
# anything that spells one. These give the comment its own node kind -- and
# `///` a different kind from `//`, which is the distinction a line-prefix test
# gets wrong by construction: `///` starts with `//`.
tree-sitter = "0.26"
tree-sitter-rust = "0.24"
tree-sitter-python = "0.25"

[profile.release]
strip = true
Expand Down
49 changes: 49 additions & 0 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ anything else (`[rule."my rule"]`).
| field | fails when |
|---|---|
| `regexp` | the regex matches anywhere in the selected files |
| `comment_regexp` | the regex matches a **comment** in a selected Rust or Python file |
| `trivial_comments` | a comment contributes no word the code beneath it already names |
| `path_regexp` | a tracked path matches the regex |
| `require_regexp` | a selected file does **not** contain the regex |
| `max_lines` | a selected file is longer than that, or grew past its baseline |
Expand Down Expand Up @@ -213,6 +215,53 @@ built-in is added unless one of the declared rules is itself
`forbidden_literals = "running-os-identity"`. Declaring a rule about something
else is not a decision to stop checking this.

### `comment_regexp` and `trivial_comments` — the comment, not the line

Both parse the file rather than searching it, in Rust and Python. That is the
whole reason they are separate checks: `regexp` reads bytes, so
`let marker = "// TODO";` is a hit for a rule about `// TODO` and there is no
way to write the difference down. These read comment nodes, so a marker inside a
string literal is a string literal.

```toml
[rule.no-before-after-narrative-in-source]
message = "State what holds, not what it replaced."
comment_regexp = '(?i)\bused to be\b'
files.include = ["src"]
files.glob = ["*.rs", "*.py"]

[rule.no-trivial-comment]
message = "This comment says only what the code beneath it already says."
trivial_comments = true
files.include = ["src"]
files.glob = ["*.rs", "*.py"]
```

**Documentation comments are excluded from both.** `///` and `//!` are published
output, not remarks to the next reader, and a check that cannot tell them apart
from `//` is one whose findings, acted on, delete a public item's documentation.
The grammar marks them; nothing here matches on the prefix, which is what a
prefix test gets wrong by construction — `///` starts with `//`.

`trivial_comments` is a subset test and carries no list of boring verbs: a
comment fails when every word it contributes is a word the statements beneath it
already name, counting their string literals. `// Stop and disable dnsmasq` over
`systemd::stop("dnsmasq")` and `systemd::disable("dnsmasq")` fails; the same
comment with a reason attached does not, and no list had to be edited for that to
be true. The code it is judged against runs from the comment to the next blank
line or the next comment — where a reader stops attributing it.

Five shapes are left alone, each because its words restate the code by design
while the comment is doing something else: a trailing comment on the same line as
code, one line of a multi-line comment run, a separator (`---`, `===`, box
drawing), a worked example (containing `=` or `→`), and a parenthesised aside.
A tree that wants its separators gone writes a `comment_regexp` saying so; this
check does not reach that verdict on its own.

There is no fixer, and that is a decision rather than a gap. A comment worth
deleting is usually worth replacing with the reason the code is that way, and
that is not an edit a checker can make.

### `forbidden_literals` — what must appear nowhere

```toml
Expand Down
53 changes: 53 additions & 0 deletions policy/principles.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,59 @@ regexp = '(?i)\b(?:became|was renamed|renamed to|formerly|previously (?:called|n
files.glob = ["*.md"]
files.exclude = ["CHANGELOG*", "**/tests/**", "**/test/**"]

# The same sentence, in the files the *.md rule was never able to reach.
#
# A `regexp` over source finds the phrase in a string literal as readily as in a
# comment, and this repository has both -- the message a rule prints is a string
# containing the prose the rule is about. So the subject is the comment, read off
# the parse, and `///` is excluded because a doc comment is published output
# rather than a remark to the next reader.
#
# The pattern is not the *.md one. `used to be` is shared, and `no longer
# needed` and `previously, this` are added because those are the tenses a
# comment reaches for when it explains the diff to whoever arrives next.
#
# `was renamed`, `renamed to` and `formerly` are deliberately NOT here, though
# the *.md rule carries all three. Each was measured against this tree and each
# fired on domain prose rather than on history: `guard/names.rs` is about
# repositories that get renamed, and `selection.rs:309` about a root renamed
# away underneath a walk. A document does not talk about renaming; the code that
# handles it does nothing else.
[rule.no-before-after-narrative-in-source]
message = """
State what holds, not what it replaced. A comment that explains what the code
used to do goes stale the day either version moves again, and the story of the
change belongs in the commit that made it.
"""
comment_regexp = '(?i)\b(?:used to be|used to (?:use|call|return|do|have|rely)|previously[,:]?\s+(?:this|we|it|the)|no longer (?:needed|used|required|necessary))\b'
files.include = ["src", "scripts", "uphold_check.py"]
files.glob = ["*.rs", "*.py"]
files.exclude = ["**/tests/**", "**/test/**"]

# A comment carrying no word its own code does not already name.
#
# The test is a subset and not a list of boring verbs: every word the comment
# contributes has to be a word the statements under it already say, counting
# their string literals. `// Stop and disable dnsmasq` over `systemd::stop(...)`
# and `systemd::disable(...)` is trivial because all three words are in the code;
# the same comment with a reason attached is not, and no list had to be edited
# to know that.
#
# Tests are excluded because a test's comment names the case rather than the
# code -- `// Delete the applied local IPv6` above an assertion is what tells a
# reader which of nine assertions this one is.
[rule.no-trivial-comment]
message = """
This comment says only what the code beneath it already says. Delete it, or
replace it with the reason the code is that way -- a comment that repeats an
identifier goes stale the moment the identifier changes and helps nobody until
it does.
"""
trivial_comments = true
files.include = ["src", "scripts", "uphold_check.py"]
files.glob = ["*.rs", "*.py"]
files.exclude = ["**/tests/**", "**/test/**"]

# ---------------------------------------------------------------------------
# Guards -- what git is about to do.
#
Expand Down
Loading
Loading