summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorYuriy Andamasov <yuriy@vyos.io>2026-05-10 21:45:26 +0300
committerYuriy Andamasov <yuriy@vyos.io>2026-05-10 21:45:26 +0300
commit4ef7ffeb2b337b14699e7547b43ba2fe59a00422 (patch)
tree7abb2fff20a463be0a8eb4207a66324785eee699 /scripts
parent38d085ece37d571a3ab280ce2fcf9b075c00c535 (diff)
downloadvyos-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.py64
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