3"""The checks that need only the source text, not a parse.
5Two of this gate's seven findings are answerable lexically, and answering them
6without libclang is deliberate: DOC004 (two doc blocks in a row with nothing
7between them) and DOC007 (banned definition-site boilerplate) are statements
8about the *comment stream*, which a parse discards.
10Everything here therefore works on text that has had comments and literals
11blanked out but line structure preserved, so a brace inside a string or a
12declaration-looking line inside a comment cannot be mistaken for code.
15from __future__
import annotations
20from docattach_model
import (
22 BANNED_BOILERPLATE_RE,
35def _blank_comments_and_literals(text: str) -> str:
36 """Blank every comment and string/char literal, preserving line structure.
38 Length and newline positions are preserved so the result can be indexed by
39 the same line numbers as the original. Blanking (rather than deleting) is
40 what lets the DOC004 "is there code between these two blocks" test look at
41 code only -- an earlier version advanced past comments without clearing
42 them, so a member's own trailing ``/**< ... */`` counted as intervening
43 code and the check silently never fired on real member runs.
47 def blank(lo: int, hi: int) ->
None:
48 for k
in range(lo,
min(hi, len(text))):
60 if text[i] ==
"\\" and i + 1 < n:
69 if ch ==
"/" and i + 1 < n
and text[i + 1] ==
"/":
71 while i < n
and text[i] !=
"\n":
75 if ch ==
"/" and i + 1 < n
and text[i + 1] ==
"*":
78 while i + 1 < n
and not (text[i] ==
"*" and text[i + 1] ==
"/"):
87def _skip_literal(text: str, i: int, line: int) -> tuple[int, int]:
88 """Advance past the string/char literal starting at ``i``."""
93 if text[i] ==
"\\" and i + 1 < n:
105def _scan_block_comment(text: str, i: int, line: int) -> tuple[int, int, bool]:
106 """Advance past the ``/* ... */`` at ``i``; report whether it is a doc block."""
108 is_doc = i + 2 < n
and text[i + 2]
in {
"*",
"!"}
110 if is_doc
and i + 3 < n
and text[i + 2] ==
"*" and text[i + 3] ==
"/":
113 while j + 1 < n
and not (text[j] ==
"*" and text[j + 1] ==
"/"):
117 return j + 2, line, is_doc
120def extract_doc_blocks(text: str) -> list[DocBlock]:
121 """Return every ``/**`` / ``/*!`` block in ``text``, in source order.
123 Blocks inside string literals are skipped. Single-line ``///`` and ``//!``
124 comments are captured too so the duplicate check sees them.
126 blocks: list[DocBlock] = []
136 i, line = _skip_literal(text, i, line)
138 if ch ==
"/" and i + 1 < n
and text[i + 1] ==
"*":
140 end, line, is_doc = _scan_block_comment(text, i, line)
142 blocks.append(DocBlock(start_line, line, text[i:end]))
145 if ch ==
"/" and i + 1 < n
and text[i + 1] ==
"/":
146 is_doc = i + 2 < n
and text[i + 2]
in {
"/",
"!"}
148 while i < n
and text[i] !=
"\n":
151 blocks.append(DocBlock(line, line, text[chunk_start:i]))
155 b.standalone = bool(STANDALONE_TAG_RE.search(b.text)
or GROUP_MARKER_RE.search(b.text))
156 b.trailing = bool(re.match(
r"/(?:\*[*!]|//|/!)<", b.text))
160def check_consecutive_blocks(path: str, text: str) -> list[Finding]:
161 """DOC004 -- two doc blocks in a row with nothing declared between them.
163 This is the owner's literal complaint and the shape that makes a presence
164 gate *reward* the defect. A ``@file`` / ``@defgroup`` / ``@{`` block
165 followed by a real block is normal and is exempted via ``standalone``.
167 findings: list[Finding] = []
168 blocks = extract_doc_blocks(text)
169 lines = _blank_comments_and_literals(text).splitlines()
170 raw_lines = text.splitlines()
171 for prev, cur
in itertools.pairwise(blocks):
172 if prev.standalone
or prev.trailing
or cur.trailing:
184 refs = [ref
for _, ref
in EXPLICIT_REF_RE.findall(prev.text)]
187 (ln
for ln
in raw_lines[prev.end_line : cur.start_line - 1]
if ln.strip()),
189 )
or next((ln
for ln
in raw_lines[prev.end_line :]
if ln.strip()),
"")
190 if any(re.search(
r"\b" + re.escape(r) +
r"\b", following)
for r
in refs):
202 raw_between = raw_lines[prev.end_line : cur.start_line - 1]
203 if any(COMMENTED_DEFINE_RE.match(seg)
for seg
in raw_between):
207 between = lines[prev.end_line : cur.start_line - 1]
208 if any(seg.strip()
for seg
in between):
216 f
"doc block is followed by another doc block at line {cur.start_line} "
217 "with no declaration between them; the first documents nothing",
223def check_banned_boilerplate(path: str, text: str) -> list[Finding]:
224 """DOC007 -- CLAUDE.md's banned pointer-only definition-site comment.
226 Only fires on blocks whose *entire* informational content is the pointer.
227 A block that says "see the header for the full contract" and then adds a
228 real implementation note is allowed by CLAUDE.md, so the presence of any
229 other Doxygen tag or a ``--`` note clause clears it.
231 findings: list[Finding] = []
232 for block
in extract_doc_blocks(text):
233 if not BANNED_BOILERPLATE_RE.search(block.text):
235 body = re.sub(
r"^\s*/\*+<?|\*+/\s*$",
" ", block.text)
236 body = re.sub(
r"^\s*\*",
" ", body, flags=re.MULTILINE)
237 tags = set(re.findall(_T +
r"([a-z]+)", body))
246 residue = BANNED_BOILERPLATE_RE.sub(
" ", body)
247 residue = re.sub(_T +
r"brief",
" ", residue)
249 r"[Ii]mplementation of\s+`?[A-Za-z_][A-Za-z_0-9]*`?(?:\s*\(\))?`?",
" ", residue
251 residue = re.sub(
r"[`(){}.,;:*/\s-]",
"", residue)
260 "pointer-only definition-site boilerplate (CLAUDE.md bans it): the comment "
261 "carries no information the signature does not. Delete it, or replace it "
262 "with the single-line form carrying a real implementation note",
#define min(x, y)
Untyped minimum shim used by the SOUP's buffer clamping.