131 """The result of scoping a rule's waivers over a scanner message.
133 ``surviving`` and ``suppressed`` are ``(lineno, text)`` pairs; a
134 ``lineno`` of ``0`` means the scanner reported no location (e.g. the
135 unparseable-file message), which no line waiver can reach.
137 ``legacy_lines`` lists the bare ``disable=`` waivers that suppressed
138 findings ONLY by falling back to pre-193 whole-file semantics. It is
139 empty whenever the legacy branch was not exercised - including when
140 a stale waiver had nothing to suppress - so it counts exactly the
141 files that would change verdict if the legacy branch were deleted.
144 surviving: tuple[tuple[int, str], ...]
145 suppressed: tuple[tuple[int, str], ...]
146 legacy_lines: tuple[int, ...]
150 """True when at least one finding survived every waiver."""
155 """The surviving findings re-joined in the scanner's own format."""
156 return "; ".join(text
for _lineno, text
in self.
surviving)
160 """Line of the first surviving finding (``0`` when none survive)."""
165 """Return ``(lineno, comment_text)`` for every real comment in *text*.
167 Plan 193 R1 - a waiver is a COMMENT, so only comment tokens are
168 offered to the waiver regex. Waiver-shaped text inside a string
169 literal (a docstring that documents the syntax, a test fixture that
170 exercises it, a rule-registry example) is therefore inert, and can
171 no longer silently waive the file that merely talks about waivers.
173 FAIL CLOSED: if the source cannot be tokenised, this returns no
174 comments at all, so the caller honours NO waivers and every finding
175 survives. That matches the 170/176 doctrine - an unparseable file
176 must never gain suppression it could not earn.
178 comments: list[tuple[int, str]] = []
180 for token
in tokenize.generate_tokens(io.StringIO(text).readline):
181 if token.type == tokenize.COMMENT:
182 comments.append((token.start[0], token.string))
183 except (tokenize.TokenError, SyntaxError, IndentationError,
184 ValueError, UnicodeDecodeError):
261 """Split a scanner message into ``(lineno, text)`` findings.
263 :func:`oct.tools.secret_scanner.check_no_hardcoded_secrets` joins its
264 violations with ``"; "`` and prefixes each with ``L<lineno>: ``. A
265 truncated preview may itself contain ``"; "``, so a fragment without
266 that prefix is folded back into the preceding finding rather than
267 being mistaken for a new one. A leading fragment with no prefix (the
268 unparseable-file message) is reported at line ``0``, which no line
271 findings: list[tuple[int, str]] = []
274 for fragment
in message.split(
"; "):
275 m = _FINDING_PREFIX_RE.match(fragment)
276 if m
is None and findings:
277 lineno, text = findings[-1]
278 findings[-1] = (lineno, f
"{text}; {fragment}")
280 findings.append((int(m.group(
"lineno"))
if m
else 0, fragment))
284def apply_waivers(message: str, waivers: dict[int, dict], rule: str) -> WaiverOutcome:
285 """Scope *rule*'s waivers over the findings in *message*.
287 *waivers* is the mapping returned by :func:`parse_inline_waivers`.
290 1. An explicit ``disable-file=`` waiver for *rule* suppresses every
291 finding (deliberate whole-file blindness).
292 2. Each line waiver suppresses findings on its own line and the next
293 (:data:`WAIVER_LINE_SPAN`).
294 3. If findings survive AND at least one bare waiver matched nothing,
295 that waiver falls back to pre-193 whole-file suppression and is
296 reported in :attr:`WaiverOutcome.legacy_lines`.
298 Reasonless waivers are ignored at every step.
300 func =
"apply_waivers"
301 _dbg(
"OCT-CORE", func, f
"scoping {rule} waivers over the findings", 4)
307 lineno: w
for lineno, w
in waivers.items()
308 if w.get(
"rule") == rule
and w.get(
"reason")
310 if any(w.get(
"scope",
"line") ==
"file" for w
in active.values()):
313 line_waivers = sorted(
314 lineno
for lineno, w
in active.items()
315 if w.get(
"scope",
"line") ==
"line"
318 covered: set[int] = set()
319 matched: set[int] = set()
320 for waiver_line
in line_waivers:
321 for index, (finding_line, _text)
in enumerate(findings):
322 if finding_line
and waiver_line <= finding_line < waiver_line + WAIVER_LINE_SPAN:
324 matched.add(waiver_line)
326 surviving = tuple(f
for i, f
in enumerate(findings)
if i
not in covered)
327 legacy_lines = tuple(ln
for ln
in line_waivers
if ln
not in matched)
332 if surviving
and legacy_lines:
335 suppressed = tuple(f
for i, f
in enumerate(findings)
if i
in covered)