From 36309f3b345061ca718ec9c07bd6993b04648422 Mon Sep 17 00:00:00 2001 From: bayrakdarerdem Date: Wed, 12 Aug 2026 09:57:05 +0000 Subject: [PATCH] fix(lint-mdx): stop flagging closing code fences as missing a language checkCodeBlocks() matched every line starting with ``` the same way, opening and closing fences alike. Closing fences never carry a language, so every single fenced code block in the docs -- thousands of them -- was reported as "Code block missing language specifier" on its closing line. Running `node scripts/lint-mdx.js all` before this change: 349 files checked, 1246 errors, 75 warnings After: 349 files checked, 100 errors, 74 warnings The function now tracks the length of the currently-open fence and only treats a ``` line as a new opening fence when not already inside a block. This also fixes a related edge case surfaced while writing the fix: two files (agents/plugins/custom-plugins.mdx and base-account/guides/authenticate-users.mdx) use a 4-backtick fence to show a literal 3-backtick code sample as content. A plain boolean would have treated that inner 3-backtick line as a real closing fence and gotten out of sync for the rest of the file, so the fix tracks fence length and only closes a block on a fence at least as long as the one that opened it (matching CommonMark's nested-fence rule). The CodeGroup label check is unchanged in behavior; it's simplified now that the surrounding opening/closing logic no longer needs it to compensate. Verified the ~100 remaining reported issues are real (genuine language-less code blocks, missing frontmatter, multiple H1s, etc.) by spot-checking several files directly. Assisted by Claude (Anthropic). --- scripts/lint-mdx.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/scripts/lint-mdx.js b/scripts/lint-mdx.js index 07c42575b..46409e1ec 100755 --- a/scripts/lint-mdx.js +++ b/scripts/lint-mdx.js @@ -183,6 +183,14 @@ function checkCodeBlocks(content, filePath) { const issues = []; const lines = content.split("\n"); let inCodeGroup = false; + // Length (in backticks) of the fence currently open, or 0 when not inside + // a code block. Tracked (rather than a boolean) because MDX lets a fence + // of 4+ backticks nest a literal 3-backtick fence as content -- e.g. a + // ` ```` ` block used to show what a fenced code sample looks like. Only + // a fence with at least as many backticks as the one that opened the + // block can close it (matching CommonMark); shorter backtick runs inside + // are just text. + let openFenceLength = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; @@ -190,10 +198,22 @@ function checkCodeBlocks(content, filePath) { if (line.includes("")) inCodeGroup = true; if (line.includes("")) inCodeGroup = false; - // Check for code block opening - const codeBlockMatch = line.match(/^```(\S*)/); + const codeBlockMatch = line.match(/^(`{3,})(\S*)/); if (codeBlockMatch) { - const lang = codeBlockMatch[1]; + const fenceLength = codeBlockMatch[1].length; + + if (openFenceLength > 0) { + // Inside a block: only a fence at least as long as the one that + // opened it actually closes the block. A shorter run of backticks + // is nested content, not a real fence. + if (fenceLength >= openFenceLength) { + openFenceLength = 0; + } + continue; + } + + openFenceLength = fenceLength; + const lang = codeBlockMatch[2]; // Check for empty language if (!lang) { @@ -205,9 +225,9 @@ function checkCodeBlocks(content, filePath) { } // In CodeGroup, should have language AND label - if (inCodeGroup && lang && !lang.includes(" ") && !/\s+\S+/.test(line.slice(3 + lang.length))) { + if (inCodeGroup && lang) { // Check if there's a label after the language - const afterLang = line.slice(3 + lang.length).trim(); + const afterLang = line.slice(fenceLength + lang.length).trim(); if (!afterLang) { issues.push({ line: i + 1,