Purpose
-------
OI-540 - shared inline-waiver parsing for OCT-LINT comments.
Plan 193 - waiver SCOPE: a waiver excuses the finding beside it, not the
whole file.
Extracted from :mod:`oct.linter.oct_lint` so that both the linter and
the quality gate (:mod:`oct.git.quality_gate`) can import the same
parser without the quality gate depending on the linter.
Plan 193 (sec) closed a hole this module sat at the centre of. The
parser always returned line numbers, but BOTH consumers threw them away
and treated any waiver as whole-file suppression - so a real credential
pasted anywhere into one of the ~200 files carrying a
``no_hardcoded_secrets`` waiver committed clean. Scope is now:
- ``# OCT-LINT: disable=<rule> reason="…"`` - LINE scope. Covers its own
line and the immediately following line (:data:`WAIVER_LINE_SPAN`), so
both trailing-comment and comment-above placements work and the waiver
travels with the code it excuses.
- ``# OCT-LINT: disable-file=<rule> reason="…"`` - FILE scope. Whole-file
blindness stays available, but as a deliberate, greppable choice.
- A bare ``disable=`` waiver adjacent to NO finding of its rule keeps its
historical file-wide power (so the pre-193 tree does not storm the gate
in one landing) and reports its line via
:attr:`WaiverOutcome.legacy_lines`, which the linter surfaces as the
advisory ``legacy_file_scoped_waiver`` burn-down finding. When that
count reaches zero the legacy branch can be deleted as a no-op.
Reasonless waivers remain inert in both forms (pre-existing rule).
Responsibilities
----------------
- Host :data:`_WAIVER_RE` - the canonical regex for
``# OCT-LINT: disable[-file]=<rule> [reason="..."]`` comments.
- Provide :func:`_comment_tokens` - tokenise source so only real
comments can carry a waiver.
- Provide :func:`parse_inline_waivers` - returns a mapping of line
numbers to ``{"rule": str, "reason": str|None, "scope": str}`` dicts.
- Provide :func:`inline_disabled_rules` - the FILE-WIDE answer the
linter reaches from that mapping, written once so every consumer
reaches it by the same route (plan 277).
- Provide :func:`split_scanner_findings` - recover per-finding line
numbers from the secret scanner's ``"; "``-joined message.
- Provide :func:`apply_waivers` - scope waivers over those findings and
return a :class:`WaiverOutcome`.
Diagnostics
-----------
Domain: OCT-CORE
Levels:
L2 - (none) pure parsing helper; it reports no lifecycle of its own
L3 - (none) callers report waiver outcomes in their own domains
L4 - parse and scope entry: input size, and the rule being scoped
Contracts
---------
- Must not import :mod:`oct.linter.oct_lint` (no circular dependency).
- Return shape is stable and additive - both linter and quality gate
depend on it. Plan 193 added the ``"scope"`` key; existing readers of
``"rule"`` / ``"reason"`` are unaffected.
- This module decides waiver SCOPE only. It contains no threshold, no
heuristic, and no secret pattern, and it never changes what the
scanner detects - only which detected findings are excused.
- :func:`inline_disabled_rules` can never return a member of
:data:`LINE_SCOPED_RULES`, by construction. Those rules are scoped per
finding by :func:`apply_waivers` and must never be switched off
wholesale by a consumer that reads this set (plan 277 - the reason
G-D4 is untouched by the format pass honouring waivers).
- A waiver is only honoured when it appears in a real ``COMMENT``
token (193 R1). Waiver-shaped text inside a string literal is inert,
so a file that documents or tests the waiver syntax cannot waive
itself. Unparseable input yields NO waivers (fail closed).
- :func:`apply_waivers` is fail-closed: any finding whose line cannot be
recovered (line ``0``) is unreachable by line scope and survives
unless an explicit file-scope waiver covers it.
Dependencies
------------
- re (waiver + finding-prefix pattern matching)
- tokenize (comment-token extraction - 193 R1)
- io (StringIO line reader fed to tokenize - 193 R1)
- dataclasses (the frozen WaiverOutcome record)
- oct.core.diagnostics (_dbg structured logger)
| list[tuple[int, str]] waivers._comment_tokens |
( |
str | text | ) |
|
|
protected |
Return ``(lineno, comment_text)`` for every real comment in *text*.
Plan 193 R1 - a waiver is a COMMENT, so only comment tokens are
offered to the waiver regex. Waiver-shaped text inside a string
literal (a docstring that documents the syntax, a test fixture that
exercises it, a rule-registry example) is therefore inert, and can
no longer silently waive the file that merely talks about waivers.
FAIL CLOSED: if the source cannot be tokenised, this returns no
comments at all, so the caller honours NO waivers and every finding
survives. That matches the 170/176 doctrine - an unparseable file
must never gain suppression it could not earn.
Definition at line 164 of file waivers.py.
| frozenset[str] waivers.inline_disabled_rules |
( |
str | text | ) |
|
Return the rules *text*'s inline waivers switch off for the whole file.
This is the linter's own set, and the ONE place it is written. It is
:func:`oct.linter.oct_lint._lint_single_file`'s ``waived_rules``
verbatim - a waiver counts when it carries a reason and names a rule
outside :data:`LINE_SCOPED_RULES`, and its ``scope`` is not consulted,
because for a rule whose check reports a verdict rather than located
findings there is nothing to scope against. That is why the bare
``disable=`` form disables its rule file-wide here exactly as the
linter's ``_rule_active`` gate has always let it.
Plan 277 added this so the quality gate's format pass
(:func:`oct.git.quality_gate._run_format_pass`) and the health
dashboard (:mod:`oct.health.oct_health`) can reach the linter's
answer without restating it. Before, each pass knew a different
subset of what excuses a file, and a module the operator had ruled
exempt from ``dbg_import`` linted clean and was refused by the format
pass on the same rule, in the same commit.
A member of :data:`LINE_SCOPED_RULES` can never appear in the result.
Those rules are excused per finding by :func:`apply_waivers` and a
consumer that switched one off file-wide from here would be handing
back exactly the whole-file blindness plan 193 (sec) closed.
FAIL CLOSED on unparseable input: :func:`parse_inline_waivers` yields
no waivers when the source cannot be tokenised, so this yields no
disabled rules and every check runs.
Definition at line 219 of file waivers.py.
| list[tuple[int, str]] waivers.split_scanner_findings |
( |
str | message | ) |
|
Split a scanner message into ``(lineno, text)`` findings.
:func:`oct.tools.secret_scanner.check_no_hardcoded_secrets` joins its
violations with ``"; "`` and prefixes each with ``L<lineno>: ``. A
truncated preview may itself contain ``"; "``, so a fragment without
that prefix is folded back into the preceding finding rather than
being mistaken for a new one. A leading fragment with no prefix (the
unparseable-file message) is reported at line ``0``, which no line
waiver can reach.
Definition at line 260 of file waivers.py.