From 7a3aa29fc010c2604b84d6437ed87053947f3f27 Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Sun, 10 May 2026 19:11:24 +0300 Subject: ci: extend doc-linter to MyST Markdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Active docs are now MyST `.md`; the linter previously only inspected `.rst` and `.txt`, so ~250 active pages were unchecked for IP usage and line length on every PR. scripts/doc-linter.py: - Add `.md` to the extension filter (use `endswith` for correctness; the prior 4-char slice silently skipped `.md` files). - Track MyST/Markdown fenced code blocks (```` ``` ```` and `:::`) for line-length exemption — same semantics as `.. code-block::` for RST. - Recognize both suppression marker forms: `.. stop_vyoslinter` / `.. start_vyoslinter` (RST and `.txt` includes) and `% stop_vyoslinter` / `% start_vyoslinter` (MyST). Both work in either context; pick the form that matches the surrounding parser. - Replace the brittle `try/finally: fp.close()` with a `with` block — the previous form raised `UnboundLocalError` if `open()` itself failed. - Fix typo `forgett` → `forget`. .github/instructions/rst-linter.instructions.md → doc-linter.instructions.md: - Broaden `applyTo` from `**/*.rst` to `**/*.md,**/*.rst,**/*.txt`. - Document MyST suppression syntax and fenced-code line-length exemption. - Note the parser-form rule for `{eval-rst}` blocks. No regression on `.txt` includes: identical lint output verified against the origin/rolling baseline on a sample of files. Pre-existing IP violations exist in 14 `.md` files (e.g. `configexamples/lac-lns.md` line 95 — a `8.8.8.8` already wrapped in `% stop_vyoslinter`/`% start_vyoslinter`, correctly suppressed). PRs touching unsuppressed violations will start failing CI; this is the intent of enabling the check. 🤖 Generated by [robots](https://vyos.io) --- .github/instructions/doc-linter.instructions.md | 90 +++++++++++++++++++++++++ .github/instructions/rst-linter.instructions.md | 70 ------------------- 2 files changed, 90 insertions(+), 70 deletions(-) create mode 100644 .github/instructions/doc-linter.instructions.md delete mode 100644 .github/instructions/rst-linter.instructions.md (limited to '.github') diff --git a/.github/instructions/doc-linter.instructions.md b/.github/instructions/doc-linter.instructions.md new file mode 100644 index 00000000..5e7d359e --- /dev/null +++ b/.github/instructions/doc-linter.instructions.md @@ -0,0 +1,90 @@ +--- +applyTo: "**/*.md,**/*.rst,**/*.txt" +--- + +## Doc Linter Rules + +Every pull request is automatically linted by `scripts/doc-linter.py`. It checks **only changed files** for two things: IP address usage and line length. MyST Markdown (`.md`), legacy RST (`.rst`), and `.txt` includes are all linted. + +### IP Address Rules + +The linter rejects public IP addresses that are not reserved for documentation. + +**Use these documentation-reserved addresses (no suppression needed):** + +| Type | Range | RFC | +|------|-------|-----| +| IPv4 | `192.0.2.0/24` (TEST-NET-1) | RFC 5737 | +| IPv4 | `198.51.100.0/24` (TEST-NET-2) | RFC 5737 | +| IPv4 | `203.0.113.0/24` (TEST-NET-3) | RFC 5737 | +| IPv6 | `2001:db8::/32` | RFC 3849 | + +**These are allowed without suppression (not flagged by linter):** + +| Type | Range | Why | +|------|-------|-----| +| RFC 1918 private | `10.0.0.0/8` | Private address space | +| RFC 1918 private | `172.16.0.0/12` | Private address space | +| RFC 1918 private | `192.168.0.0/16` | Private address space | +| Loopback | `127.0.0.0/8` | Loopback range | +| Link-local | `169.254.0.0/16` | Link-local | + +**These require `stop/start_vyoslinter` suppression:** + +| Example | Why suppression is needed | +|---------|--------------------------| +| `8.8.8.8` (Google DNS) | Real public IP, not documentation-reserved | +| `64:ff9b::/96` (NAT64) | Well-known prefix, not in doc ranges | +| Real provider IPs in examples | Authenticity matters for the example | + +### Line Length Rules + +Maximum 80 characters per line. The line-length check is **skipped** inside fenced code: + +- MyST / Markdown: ` ```...``` ` and `:::...:::` fenced blocks (any info string, including ` ```{eval-rst} `, ` ```{cfgcmd} `, etc.). +- RST: lines indented under `.. code-block::` directives. + +### Suppression Syntax + +When real public IPs or long lines are unavoidable, wrap the block. The marker form depends on the surrounding parser: + +**MyST Markdown (`.md`)** — use the MyST comment form: + +````md +% stop_vyoslinter + +``` +set system name-server '8.8.8.8' +``` + +% start_vyoslinter +```` + +**RST (`.rst`) and `.txt` includes** — use the RST comment form: + +```rst +.. stop_vyoslinter + +.. code-block:: none + + set system name-server '8.8.8.8' + +.. start_vyoslinter +``` + +**Inside a `{eval-rst}` block in MyST** — the embedded content is parsed as RST, so the RST form (`.. stop_vyoslinter`) matches the surrounding parser. The linter recognizes both forms regardless of context, but use the form that fits the parser of the enclosing block. + +**Rules for suppression markers:** + +- Place the stop marker on its own line, immediately before the content. +- Place the start marker on its own line, immediately after the content. +- Always re-enable the linter — never leave it stopped for the rest of the file. +- Suppress the smallest possible region. +- Do NOT use suppression for content that can use documentation-reserved addresses instead. + +### Common Mistakes + +- Removing `stop/start_vyoslinter` markers without fixing the underlying issue (exposes the line to the linter and fails CI). +- Using real public IPs when documentation addresses would work just as well. +- Adding suppression markers around content that only has private (RFC 1918) addresses or `0.0.0.0/0` — these are allowed and don't need suppression. +- Mixing marker forms (using `% stop_vyoslinter` inside an `{eval-rst}` block, or `.. stop_vyoslinter` in plain MyST prose). diff --git a/.github/instructions/rst-linter.instructions.md b/.github/instructions/rst-linter.instructions.md deleted file mode 100644 index 419f094a..00000000 --- a/.github/instructions/rst-linter.instructions.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -applyTo: "**/*.rst" ---- - -## RST Linter Rules - -Every pull request is automatically linted by `doc-linter.py` (from the `vyos/.github` repository). It checks **only changed files** for two things: IP address usage and line length. - -### IP Address Rules - -The linter rejects public IP addresses that are not reserved for documentation. - -**Use these documentation-reserved addresses (no suppression needed):** - -| Type | Range | RFC | -|------|-------|-----| -| IPv4 | `192.0.2.0/24` (TEST-NET-1) | RFC 5737 | -| IPv4 | `198.51.100.0/24` (TEST-NET-2) | RFC 5737 | -| IPv4 | `203.0.113.0/24` (TEST-NET-3) | RFC 5737 | -| IPv6 | `2001:db8::/32` | RFC 3849 | - -**These are allowed without suppression (not flagged by linter):** - -| Type | Range | Why | -|------|-------|-----| -| RFC 1918 private | `10.0.0.0/8` | Private address space | -| RFC 1918 private | `172.16.0.0/12` | Private address space | -| RFC 1918 private | `192.168.0.0/16` | Private address space | -| Loopback | `127.0.0.0/8` | Loopback range | -| Link-local | `169.254.0.0/16` | Link-local | - -**These require `stop/start_vyoslinter` suppression:** - -| Example | Why suppression is needed | -|---------|--------------------------| -| `8.8.8.8` (Google DNS) | Real public IP, not documentation-reserved | -| `64:ff9b::/96` (NAT64) | Well-known prefix, not in doc ranges | -| Real provider IPs in examples | Authenticity matters for the example | - -### Line Length Rules - -Maximum 80 characters per line. Lines inside `.. code-block::` directives are **exempt** from the line length limit (they render with `
` tags and preserve source formatting).
-
-### Suppression Syntax
-
-When real public IPs or long lines are unavoidable, wrap the block:
-
-```rst
-.. stop_vyoslinter
-
-.. code-block:: none
-
-   set system name-server '8.8.8.8'
-
-.. start_vyoslinter
-```
-
-**Rules for suppression markers:**
-
-- Place `.. stop_vyoslinter` on its own line, immediately before the content
-- Place `.. start_vyoslinter` on its own line, immediately after the content
-- Always re-enable the linter — never leave it stopped for the rest of the file
-- Suppress the smallest possible region
-- Do NOT use suppression for content that can use documentation-reserved addresses instead
-
-### Common Mistakes
-
-- Removing `stop/start_vyoslinter` markers without fixing the underlying issue (exposes the line to the linter and fails CI)
-- Using real public IPs when documentation addresses would work just as well
-- Adding suppression markers around content that only has private (RFC 1918) addresses or `0.0.0.0/0` — these are allowed and don't need suppression
-- 
cgit v1.2.3


From d2814d1ea147f89081e15d656bc7172018ee537b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 10 May 2026 16:15:47 +0000
Subject: fix: scope vyoslinter markers to real parser contexts

Agent-Logs-Url: https://github.com/vyos/vyos-documentation/sessions/5d679560-8a77-4735-b585-74c09293eea5

Co-authored-by: andamasov <12631358+andamasov@users.noreply.github.com>
---
 .github/instructions/doc-linter.instructions.md | 46 +++++++++++++++-----
 scripts/doc-linter.py                           | 57 +++++++++++++++++--------
 2 files changed, 75 insertions(+), 28 deletions(-)

(limited to '.github')

diff --git a/.github/instructions/doc-linter.instructions.md b/.github/instructions/doc-linter.instructions.md
index 5e7d359e..cc56f8fe 100644
--- a/.github/instructions/doc-linter.instructions.md
+++ b/.github/instructions/doc-linter.instructions.md
@@ -4,7 +4,10 @@ applyTo: "**/*.md,**/*.rst,**/*.txt"
 
 ## Doc Linter Rules
 
-Every pull request is automatically linted by `scripts/doc-linter.py`. It checks **only changed files** for two things: IP address usage and line length. MyST Markdown (`.md`), legacy RST (`.rst`), and `.txt` includes are all linted.
+Every pull request is automatically linted by `scripts/doc-linter.py`.
+It checks **only changed files** for two things: IP address usage
+and line length. MyST Markdown (`.md`), legacy RST (`.rst`), and
+`.txt` includes are all linted.
 
 ### IP Address Rules
 
@@ -33,23 +36,29 @@ The linter rejects public IP addresses that are not reserved for documentation.
 
 | Example | Why suppression is needed |
 |---------|--------------------------|
-| `8.8.8.8` (Google DNS) | Real public IP, not documentation-reserved |
+| Google Public DNS | Real public IP, not documentation-reserved |
 | `64:ff9b::/96` (NAT64) | Well-known prefix, not in doc ranges |
 | Real provider IPs in examples | Authenticity matters for the example |
 
 ### Line Length Rules
 
-Maximum 80 characters per line. The line-length check is **skipped** inside fenced code:
+Maximum 80 characters per line. The line-length check is
+**skipped** inside fenced code:
 
-- MyST / Markdown: ` ```...``` ` and `:::...:::` fenced blocks (any info string, including ` ```{eval-rst} `, ` ```{cfgcmd} `, etc.).
+- MyST / Markdown: ` ```...``` ` and `:::...:::` fenced blocks
+  (any info string, including ` ```{eval-rst} `, ` ```{cfgcmd} `,
+  etc.).
 - RST: lines indented under `.. code-block::` directives.
 
 ### Suppression Syntax
 
-When real public IPs or long lines are unavoidable, wrap the block. The marker form depends on the surrounding parser:
+When real public IPs or long lines are unavoidable, wrap the block.
+The marker form depends on the surrounding parser:
 
 **MyST Markdown (`.md`)** — use the MyST comment form:
 
+% stop_vyoslinter
+
 ````md
 % stop_vyoslinter
 
@@ -60,8 +69,12 @@ set system name-server '8.8.8.8'
 % start_vyoslinter
 ````
 
+% start_vyoslinter
+
 **RST (`.rst`) and `.txt` includes** — use the RST comment form:
 
+% stop_vyoslinter
+
 ```rst
 .. stop_vyoslinter
 
@@ -72,19 +85,30 @@ set system name-server '8.8.8.8'
 .. start_vyoslinter
 ```
 
-**Inside a `{eval-rst}` block in MyST** — the embedded content is parsed as RST, so the RST form (`.. stop_vyoslinter`) matches the surrounding parser. The linter recognizes both forms regardless of context, but use the form that fits the parser of the enclosing block.
+% start_vyoslinter
+
+**Inside a `{eval-rst}` block in MyST** — the embedded content is parsed
+as RST, so the RST form (`.. stop_vyoslinter`) matches the surrounding
+parser. The linter recognizes marker lines only in the parser context
+where they apply, including RST markers inside `{eval-rst}` blocks.
 
 **Rules for suppression markers:**
 
 - Place the stop marker on its own line, immediately before the content.
 - Place the start marker on its own line, immediately after the content.
-- Always re-enable the linter — never leave it stopped for the rest of the file.
+- Always re-enable the linter — never leave it stopped for the rest
+  of the file.
 - Suppress the smallest possible region.
-- Do NOT use suppression for content that can use documentation-reserved addresses instead.
+- Do NOT use suppression for content that can use
+  documentation-reserved addresses instead.
 
 ### Common Mistakes
 
-- Removing `stop/start_vyoslinter` markers without fixing the underlying issue (exposes the line to the linter and fails CI).
+- Removing `stop/start_vyoslinter` markers without fixing the
+  underlying issue (exposes the line to the linter and fails CI).
 - Using real public IPs when documentation addresses would work just as well.
-- Adding suppression markers around content that only has private (RFC 1918) addresses or `0.0.0.0/0` — these are allowed and don't need suppression.
-- Mixing marker forms (using `% stop_vyoslinter` inside an `{eval-rst}` block, or `.. stop_vyoslinter` in plain MyST prose).
+- Adding suppression markers around content that only has private
+  (RFC 1918) addresses or `0.0.0.0/0` — these are allowed and don't
+  need suppression.
+- Mixing marker forms (using `% stop_vyoslinter` inside an
+  `{eval-rst}` block, or `.. stop_vyoslinter` in plain MyST prose).
diff --git a/scripts/doc-linter.py b/scripts/doc-linter.py
index ec00472c..81c9baca 100644
--- a/scripts/doc-linter.py
+++ b/scripts/doc-linter.py
@@ -31,15 +31,31 @@ SUPPORTED_EXTS = ('.md', '.rst', '.txt')
 
 # MyST / Markdown fenced code block: leading whitespace + 3+ backticks or 3+ colons.
 # Same character and length-or-greater closes.
-MD_FENCE_RE = re.compile(r'^(\s*)(`{3,}|:{3,})')
-
-
-def is_suppression_marker(line, kind):
-    """Detect `.. stop_vyoslinter` (RST/txt) or `% stop_vyoslinter` (MyST)."""
-    token = f"{kind}_vyoslinter"
-    if token not in line:
+MD_FENCE_RE = re.compile(r'^(\s*)(`{3,}|:{3,})(.*)$')
+
+SUPPRESSION_MARKER_RE = {
+    'rst': {
+        'stop': re.compile(r'^\s*\.\.\s+stop_vyoslinter\s*$'),
+        'start': re.compile(r'^\s*\.\.\s+start_vyoslinter\s*$'),
+    },
+    'md': {
+        'stop': re.compile(r'^\s*%\s+stop_vyoslinter\s*$'),
+        'start': re.compile(r'^\s*%\s+start_vyoslinter\s*$'),
+    },
+}
+
+
+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."""
+    if SUPPRESSION_MARKER_RE['md'][kind].match(line):
+        return not in_md_fence
+    if not SUPPRESSION_MARKER_RE['rst'][kind].match(line):
         return False
-    return f".. {token}" in line or f"% {token}" in line
+    if in_rst_codeblock:
+        return False
+    if in_md_fence:
+        return md_fence_is_eval_rst
+    return True
 
 
 def lint_mac(cnt, line):
@@ -94,6 +110,7 @@ def lint_linelen(cnt, line):
 def handle_file_action(filepath):
     errors = []
     in_md_fence = False
+    md_fence_is_eval_rst = False
     md_fence_char = None
     md_fence_min_len = 0
     in_rst_codeblock = False
@@ -103,15 +120,7 @@ def handle_file_action(filepath):
 
     with open(filepath) as fp:
         for cnt, line in enumerate(fp, start=1):
-            if is_suppression_marker(line, 'stop'):
-                start_vyoslinter = False
-            if is_suppression_marker(line, 'start'):
-                start_vyoslinter = True
-
-            if not start_vyoslinter:
-                continue
-
-            # MD/MyST fenced code block tracking (``` or :::)
+            # MD/MyST fenced code block tracking (``` or :::).
             fence_match = MD_FENCE_RE.match(line)
             if fence_match:
                 fence = fence_match.group(2)
@@ -121,8 +130,10 @@ def handle_file_action(filepath):
                     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
 
             # RST `.. code-block::` tracking (existing semantics for .rst/.txt).
             if in_rst_codeblock:
@@ -137,6 +148,18 @@ def handle_file_action(filepath):
                     else:
                         break
 
+            if is_suppression_marker(
+                line, 'stop', in_md_fence, in_rst_codeblock, md_fence_is_eval_rst
+            ):
+                start_vyoslinter = False
+            if is_suppression_marker(
+                line, 'start', in_md_fence, in_rst_codeblock, md_fence_is_eval_rst
+            ):
+                start_vyoslinter = True
+
+            if not start_vyoslinter:
+                continue
+
             test_line_length = not (in_md_fence or in_rst_codeblock)
 
             err_mac = lint_mac(cnt, line.strip())
-- 
cgit v1.2.3


From 38d085ece37d571a3ab280ce2fcf9b075c00c535 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 10 May 2026 16:16:39 +0000
Subject: docs: suppress literal public IP examples in linter guide

Agent-Logs-Url: https://github.com/vyos/vyos-documentation/sessions/5d679560-8a77-4735-b585-74c09293eea5

Co-authored-by: andamasov <12631358+andamasov@users.noreply.github.com>
---
 .github/instructions/doc-linter.instructions.md | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

(limited to '.github')

diff --git a/.github/instructions/doc-linter.instructions.md b/.github/instructions/doc-linter.instructions.md
index cc56f8fe..358cf9df 100644
--- a/.github/instructions/doc-linter.instructions.md
+++ b/.github/instructions/doc-linter.instructions.md
@@ -34,12 +34,16 @@ The linter rejects public IP addresses that are not reserved for documentation.
 
 **These require `stop/start_vyoslinter` suppression:**
 
+% stop_vyoslinter
+
 | Example | Why suppression is needed |
 |---------|--------------------------|
-| Google Public DNS | Real public IP, not documentation-reserved |
+| `8.8.8.8` (Google DNS) | Real public IP, not documentation-reserved |
 | `64:ff9b::/96` (NAT64) | Well-known prefix, not in doc ranges |
 | Real provider IPs in examples | Authenticity matters for the example |
 
+% start_vyoslinter
+
 ### Line Length Rules
 
 Maximum 80 characters per line. The line-length check is
-- 
cgit v1.2.3