diff options
| author | Yuriy Andamasov <yuriy@vyos.io> | 2026-05-10 21:45:26 +0300 |
|---|---|---|
| committer | Yuriy Andamasov <yuriy@vyos.io> | 2026-05-10 21:45:26 +0300 |
| commit | 4ef7ffeb2b337b14699e7547b43ba2fe59a00422 (patch) | |
| tree | 7abb2fff20a463be0a8eb4207a66324785eee699 /scripts | |
| parent | 38d085ece37d571a3ab280ce2fcf9b075c00c535 (diff) | |
| download | vyos-documentation-4ef7ffeb2b337b14699e7547b43ba2fe59a00422.tar.gz vyos-documentation-4ef7ffeb2b337b14699e7547b43ba2fe59a00422.zip | |
ci: stack-based fence tracking + file-ext-aware suppression markers
Two issues from PR review:
1. MD/MyST fence tracking treated any longer same-char fence as a
closer, which would close `:::{note}` (3 cols) when seeing a
nested `::::{code-block}` (4 cols) opener inside it. Real bug
in `docs/configuration/interfaces/wireless.md:198–209` (currently
unobservable because inner code lines are <80 chars).
The "opener has info string / closer has none" heuristic is not
sufficient on its own: there are 2,826 bare-fence opens in the
tree, so info-string presence cannot distinguish opener from
closer.
Fix: stack-based tracking. A fence is treated as a closer only
when (a) the stack is non-empty, (b) char and length match the
top, AND (c) no info string follows. Anything else opens a new
(possibly nested) fence. The outermost fence's info string still
determines the `md_fence_is_eval_rst` flag.
2. `is_suppression_marker()` accepted `% stop_vyoslinter` in any
file outside an MD fence. Per AGENTS.md and the doc-linter
instructions, MyST `% ...` markers are only valid in `.md`
files; a stray `% stop_vyoslinter` in `.rst`/`.txt` should not
silently disable linting. Pass `file_ext` and gate the marker
forms accordingly: `% ...` only in `.md` outside fences;
`.. ...` in `.rst`/`.txt` outside RST code-blocks, or in `.md`
inside an `{eval-rst}` fence.
3. Drop the `not in_rst_codeblock` guard on `.. code-block::`
detection. Each occurrence resets the tracked indent (matches
`origin/rolling` baseline). Without this, code-block-inside-
code-block kept the outer indent and broke dedent detection
(verified regression: `_rst_legacy/configuration/system/
rst-syslog.rst:216` long-line warning was lost; restored).
Verified:
- All 7 original synthetic fixtures pass.
- New fixture `nested.md` (3-col outer wraps 4-col inner with long
line in between fences) produces exactly one warning at the line
outside both fences.
- New fixture `wrongmarker.rst` (`%` in `.rst`) — IP error fires
(marker correctly ignored).
- Full-tree run vs origin/rolling baseline: zero regressions on
pre-existing `.rst`/`.txt` warnings; all new output is `.md`.
🤖 Generated by [robots](https://vyos.io)
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/doc-linter.py | 64 |
1 files changed, 46 insertions, 18 deletions
diff --git a/scripts/doc-linter.py b/scripts/doc-linter.py index 81c9baca..5ccff488 100644 --- a/scripts/doc-linter.py +++ b/scripts/doc-linter.py @@ -45,17 +45,25 @@ SUPPRESSION_MARKER_RE = { } -def is_suppression_marker(line, kind, in_md_fence, in_rst_codeblock, md_fence_is_eval_rst): - """Detect valid stop/start markers in the parser context where they apply.""" +def is_suppression_marker(line, kind, in_md_fence, in_rst_codeblock, + md_fence_is_eval_rst, file_ext): + """Detect valid stop/start markers in the parser context where they apply. + + `% ...` is only valid in MyST Markdown (`.md`) at top level (outside any fence). + `.. ...` is valid in `.rst`/`.txt` at top level (outside RST code-block) and + in `.md` only when inside an `{eval-rst}` fence. + """ if SUPPRESSION_MARKER_RE['md'][kind].match(line): - return not in_md_fence + return file_ext == '.md' and not in_md_fence if not SUPPRESSION_MARKER_RE['rst'][kind].match(line): return False if in_rst_codeblock: return False + if file_ext in ('.rst', '.txt'): + return True if in_md_fence: return md_fence_is_eval_rst - return True + return False def lint_mac(cnt, line): @@ -109,10 +117,13 @@ def lint_linelen(cnt, line): def handle_file_action(filepath): errors = [] - in_md_fence = False + file_ext = os.path.splitext(filepath)[1].lower() + # Stack of open MD/MyST fences: (char, min_len). Supports nesting like + # `:::{note}` containing `::::{code-block}`, where the inner opener does + # not close the outer note (CommonMark closing rule: matching closer must + # have no info string after the fence chars). + md_fence_stack = [] md_fence_is_eval_rst = False - md_fence_char = None - md_fence_min_len = 0 in_rst_codeblock = False rst_codeblock_indent = 0 start_vyoslinter = True @@ -126,20 +137,35 @@ def handle_file_action(filepath): fence = fence_match.group(2) fence_char = fence[0] fence_len = len(fence) - if not in_md_fence: - in_md_fence = True - md_fence_char = fence_char - md_fence_min_len = fence_len - md_fence_is_eval_rst = fence_match.group(3).strip().startswith('{eval-rst}') - elif fence_char == md_fence_char and fence_len >= md_fence_min_len: - in_md_fence = False - md_fence_is_eval_rst = False + info = fence_match.group(3).strip() + # A closer matches the top of the stack (same char, len >= + # opener) and has no info string. Anything else opens a new + # (possibly nested) fence. + is_closer = ( + md_fence_stack + and not info + and fence_char == md_fence_stack[-1][0] + and fence_len >= md_fence_stack[-1][1] + ) + if is_closer: + md_fence_stack.pop() + if not md_fence_stack: + md_fence_is_eval_rst = False + else: + if not md_fence_stack: + md_fence_is_eval_rst = info.startswith('{eval-rst}') + md_fence_stack.append((fence_char, fence_len)) + + in_md_fence = bool(md_fence_stack) # RST `.. code-block::` tracking (existing semantics for .rst/.txt). + # Each `.. code-block::` directive resets the tracked indent so a + # later dedent past that column exits the block — even when the + # directive itself appears inside an already-open outer block. if in_rst_codeblock: if len(line) > rst_codeblock_indent and not line[rst_codeblock_indent].isspace(): in_rst_codeblock = False - if not in_rst_codeblock and ".. code-block::" in line: + if ".. code-block::" in line: in_rst_codeblock = True rst_codeblock_indent = 0 for ch in line: @@ -149,11 +175,13 @@ def handle_file_action(filepath): break if is_suppression_marker( - line, 'stop', in_md_fence, in_rst_codeblock, md_fence_is_eval_rst + line, 'stop', in_md_fence, in_rst_codeblock, + md_fence_is_eval_rst, file_ext, ): start_vyoslinter = False if is_suppression_marker( - line, 'start', in_md_fence, in_rst_codeblock, md_fence_is_eval_rst + line, 'start', in_md_fence, in_rst_codeblock, + md_fence_is_eval_rst, file_ext, ): start_vyoslinter = True |
