summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorYuriy Andamasov <yuriy@vyos.io>2026-05-14 00:47:20 +0300
committerMergify <37929162+mergify[bot]@users.noreply.github.com>2026-05-14 05:08:54 +0000
commitf86a67230b63ddb99a75a70d7ba0fed24514f379 (patch)
tree3e1754b28eca3cc15af38cb9fbe90621c70ab2ae /scripts
parent8bdb54adec6a985a698cbaef9df005c77b29ac99 (diff)
downloadvyos-documentation-f86a67230b63ddb99a75a70d7ba0fed24514f379.tar.gz
vyos-documentation-f86a67230b63ddb99a75a70d7ba0fed24514f379.zip
ci(doc-linter): distinguish prose-bearing directive fences from code blocks
The line-length skip was test_line_length = not (in_md_fence or in_rst_codeblock) `in_md_fence` was True for every MyST/Markdown fence regardless of content type. That includes admonition directives like `:::{note}`, `:::{warning}`, `:::{tip}` whose content is normal prose, not preformatted code. Long lines in admonitions were silently skipped, contradicting the documented 80-char rule which exempts code blocks only. Track an `is_code` property on each fence-stack entry. A fence is code-bearing when: - info string is empty (plain ``` per CommonMark), OR - info string doesn't start with `{` (bare language tag like `python`, `bash`, `yaml`), OR - info string is `{<directive>}` and `<directive>` is in the CODE_BEARING_DIRECTIVES set (`code-block`, `code`, `sourcecode`, `cfgcmd`, `opcmd`, `cmdinclude`, `cmdincludemd`, `literalinclude`, `parsed-literal`, `raw`, `command-output`, `eval-rst`). Anything else is prose-bearing (`{note}`, `{warning}`, `{tip}`, `{deprecated}`, `{seealso}`, …) and its content gets line-length checked. `in_md_code_fence` checks the topmost stack entry — the innermost fence wins, so a `{note}` containing an inner `{code-block}` lints the outer prose lines and skips the inner code-block body. The classic `is_suppression_marker()` call still uses `in_md_fence` because suppression markers are about "any fence depth" not "code-bearing depth". `{eval-rst}` is kept in CODE_BEARING_DIRECTIVES to preserve current behavior — its body is RST and any line-length on nested `.. code-block::` is handled by the separate RST tracker. Tightening eval-rst is a separate change if wanted. Test coverage: - `_fence_is_code` classifier: 16 cases (code-like vs prose-like) all pass. - Integration: long line in `{note}` flagged ✓; long line in ```python``` not flagged ✓; long line in `{cfgcmd}` not flagged ✓; nested `{note}` > ```text``` — inner skipped ✓; nested `{note}` > prose — flagged ✓. Sweep over current `docs/` tree: 28 new warnings surface across the existing pages (long prose inside admonition directives that the previous logic had been silently hiding). CI on PR scope is changed files only, so the new findings appear only when contributors touch those pages — they won't break this PR or future infra PRs. Tracked as item 5 of the rolling-side cleanup backlog from PR #2014 / #2019 / #2020 reviews. 🤖 Generated by [robots](https://vyos.io) (cherry picked from commit cd5759f26a1c18abc3c137234cf1ab503e2b26b7)
Diffstat (limited to 'scripts')
-rw-r--r--scripts/doc-linter.py50
1 files changed, 44 insertions, 6 deletions
diff --git a/scripts/doc-linter.py b/scripts/doc-linter.py
index fe262d98..1da215a5 100644
--- a/scripts/doc-linter.py
+++ b/scripts/doc-linter.py
@@ -55,6 +55,38 @@ def is_docs_path(path):
# Same character and length-or-greater closes.
MD_FENCE_RE = re.compile(r'^(\s*)(`{3,}|:{3,})(.*)$')
+# MyST directive names whose body is code-like — line-length exception applies.
+# Anything else with a `{directive}` info string is treated as prose-bearing
+# (`{note}`, `{warning}`, `{tip}`, `{deprecated}`, …); their content is normal
+# Markdown and gets line-length checked. Plain fences with no info string OR a
+# bare language tag (`python`, `bash`, `yaml`, …) are code blocks per
+# CommonMark and always skip line-length.
+CODE_BEARING_DIRECTIVES = frozenset({
+ 'code-block', 'code', 'sourcecode',
+ 'cfgcmd', 'opcmd', 'cmdinclude', 'cmdincludemd',
+ 'literalinclude', 'parsed-literal', 'raw',
+ 'command-output',
+ 'eval-rst', # body is RST; its own line-length is handled via the
+ # `.. code-block::` tracker for nested code blocks.
+})
+
+
+def _fence_is_code(info):
+ """Return True iff the fence opener `info` string designates a code-like block.
+
+ Used to gate the line-length-check skip — only code-like fences should
+ suppress line-length linting; prose-bearing directive fences like
+ `{note}` / `{warning}` must still have their content checked.
+ """
+ info = info.strip()
+ if not info:
+ return True # plain ``` is code per CommonMark
+ if not info.startswith('{'):
+ return True # python, bash, yaml, text, etc.
+ # `{code-block} python` -> `code-block`; `{note}` -> `note`.
+ name = info[1:].split('}', 1)[0].strip()
+ return name in CODE_BEARING_DIRECTIVES
+
SUPPRESSION_MARKER_RE = {
'rst': {
'stop': re.compile(r'^\s*\.\.\s+stop_vyoslinter\s*$'),
@@ -136,10 +168,12 @@ def handle_file_action(filepath):
"""Run all lint checks on one file, respecting fence/code-block and suppression context."""
errors = []
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).
+ # Stack of open MD/MyST fences: (char, min_len, is_code). 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).
+ # `is_code` records whether THAT fence is a code-bearing one — gates the
+ # line-length skip per the innermost (top-of-stack) entry.
md_fence_stack = []
md_fence_is_eval_rst = False
in_rst_codeblock = False
@@ -172,9 +206,10 @@ def handle_file_action(filepath):
else:
if not md_fence_stack:
md_fence_is_eval_rst = info.startswith('{eval-rst}')
- md_fence_stack.append((fence_char, fence_len))
+ md_fence_stack.append((fence_char, fence_len, _fence_is_code(info)))
in_md_fence = bool(md_fence_stack)
+ in_md_code_fence = bool(md_fence_stack) and md_fence_stack[-1][2]
# RST `.. code-block::` tracking (existing semantics for .rst/.txt).
# Each `.. code-block::` directive resets the tracked indent so a
@@ -219,7 +254,10 @@ def handle_file_action(filepath):
if not start_vyoslinter:
continue
- test_line_length = not (in_md_fence or in_rst_codeblock)
+ # Only code-bearing fences (plain ```python```, `{code-block}`,
+ # `{cfgcmd}`, etc.) skip line-length. Prose-bearing directives
+ # like `{note}` and `{warning}` have their content checked.
+ test_line_length = not (in_md_code_fence or in_rst_codeblock)
err_ip4 = lint_ipv4(cnt, line.strip())
err_ip6 = lint_ipv6(cnt, line.strip())