fix: exempt doc comments from the code-comment budget

Rustdoc, JSDoc, NatSpec and docstrings document a surface for callers and
are read by tooling — NatSpec ends up in contract metadata. The rule read
as an instruction to delete them, and the linter blocked a legitimate
rustdoc block.

Also: plain block comment bodies were never linted (no per-line marker),
and a literal double-star in a code span was flagged as bold.

Merges the landmine and MUST categories, which said the same thing.
This commit is contained in:
naps62
2026-08-01 14:16:48 +00:00
parent aacbdc2c43
commit 2f7f512cec
2 changed files with 47 additions and 4 deletions
+10 -2
View File
@@ -8,10 +8,10 @@ paths:
Budget: 1-3 lines. Write one only when an agent with the repo and thirty
seconds of grep would still get it wrong. That means one of:
- a landmine no test catches
- something that breaks silently — no test catches it, or a caller can
break it from outside
- a fact not in the repo (deployed bytecode, chain quirk, library bug)
- a unit or epoch the type cannot carry (wei, ms, 18-dp)
- a MUST or MUST NOT a caller can break
Good:
@@ -22,3 +22,11 @@ Everything else: delete. Design rationale and rejected alternatives go in
MUST / NEVER.
Deleting a comment is cheap and reversible. When unsure, delete.
### Doc comments are not this
Rustdoc `///`, JSDoc `/** */`, Solidity NatSpec `@notice`/`@dev`, Python
docstrings: different genre, budget does not apply. They document a surface
for callers who cannot see the body, and tooling reads them — NatSpec ends
up in contract metadata. Follow the language's convention, and never strip
them to satisfy the budget above.
+37 -2
View File
@@ -32,6 +32,15 @@ HASH_ONLY_EXT = {".py", ".sh", ".bash", ".zsh", ".rb", ".ex", ".nix",
".yaml", ".yml", ".toml"}
HASH_RE = re.compile(r"^\s*(#+)\s?(.*)$")
# Doc comments are a different genre and the budget does not apply: they
# document a surface for callers, and tooling reads them — NatSpec ends up in
# contract metadata. A two-star opener starts a doc block; a lone slash-star
# does not.
DOC_LINE_RE = re.compile(r"^\s*(///|//!)")
DOC_BLOCK_OPEN_RE = re.compile(r"^\s*/\*\*")
BLOCK_OPEN_RE = re.compile(r"^\s*/\*")
BLOCK_CLOSE_RE = re.compile(r"\*/")
# Design rationale, alternatives, history: capped at 1-2 lines, belongs in docs.
DOC_IN_SOURCE = [
(r"rejected alternative", "rejected alternatives belong in docs/ or an ADR"),
@@ -82,10 +91,34 @@ def added_text(tool_input):
def comment_lines(text, ext=""):
"""[(index, body)] for lines that are wholly a comment."""
"""[(index, body)] for lines that are wholly a non-doc comment."""
pattern = HASH_RE if ext in HASH_ONLY_EXT else COMMENT_RE
hash_only = ext in HASH_ONLY_EXT
out = []
in_doc_block = False
in_block = False
for i, line in enumerate(text.splitlines()):
if not hash_only:
if in_doc_block:
if BLOCK_CLOSE_RE.search(line):
in_doc_block = False
continue
if DOC_BLOCK_OPEN_RE.match(line):
in_doc_block = not BLOCK_CLOSE_RE.search(line)
continue
if DOC_LINE_RE.match(line):
continue
if in_block:
if BLOCK_CLOSE_RE.search(line):
in_block = False
else:
out.append((i, line.strip()))
continue
# A plain block opener carries no per-line marker, so its body is
# invisible to the regexes below unless tracked.
if BLOCK_OPEN_RE.match(line) and not BLOCK_CLOSE_RE.search(line):
in_block = True
continue
m = pattern.match(line)
if m and line.strip() not in ("*/", "/*"):
out.append((i, m.group(2)))
@@ -133,7 +166,9 @@ def lint(text, ext=""):
if hits:
problems.append(f"Superlatives in comments: {', '.join(sorted(set(hits)))}. Cut them.")
if "**" in blob:
# Strip code spans first: a comment naming a doc-block opener or a glob
# carries a literal double-star that is not emphasis.
if "**" in re.sub(r"`[^`]*`", "", blob):
problems.append("Bold inside a comment. No emphasis in comments.")
return problems, notes