From f1f52879dbed2d8ffaaf29fdba959e401fc81d2a Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Thu, 1 Jan 2026 13:48:46 +0900 Subject: [PATCH 1/2] fix: exclude CODE_REF comments in unclosed code blocks from validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed an issue where CODE_REF comments inside unclosed markdown code blocks were not being excluded from validation. The getCodeBlockRanges() function now detects unclosed code blocks (starting ``` without closing ```) and treats them as code blocks from the start position to the end of the file. Changes: - Enhanced getCodeBlockRanges() to detect and handle unclosed code blocks - Added comprehensive test cases for code block exclusion scenarios: - CODE_REF in normal code blocks - CODE_REF in inline code - CODE_REF in unclosed code blocks - Multiple code blocks with mixed CODE_REFs This resolves the issue where documentation examples of CODE_REF syntax within code blocks were incorrectly validated. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/core/validate.test.ts | 80 +++++++++++++++++++++++++++++++++++++++ src/core/validate.ts | 24 +++++++++++- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/core/validate.test.ts b/src/core/validate.test.ts index 3bff612..0268b6b 100644 --- a/src/core/validate.test.ts +++ b/src/core/validate.test.ts @@ -162,6 +162,86 @@ describe('validate-docs-code', () => { expect(result).toHaveLength(1); expect(result[0].refPath).toBe('src/example.ts'); }); + + it('コードブロック内のCODE_REFを除外すること', () => { + const content = ` +# Test Document + + + +\`\`\`html + +\`\`\` + + + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(2); + expect(result[0].refPath).toBe('src/valid.ts'); + expect(result[1].refPath).toBe('src/another-valid.ts'); + }); + + it('インラインコード内のCODE_REFを除外すること', () => { + const content = ` +# Test Document + +Use this syntax: \`\` + + + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(1); + expect(result[0].refPath).toBe('src/valid.ts'); + }); + + it('閉じていないコードブロック内のCODE_REFを除外すること', () => { + const content = ` +# Test Document + + + +\`\`\`html + + + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(1); + expect(result[0].refPath).toBe('src/valid.ts'); + }); + + it('複数のコードブロックが混在する場合にCODE_REFを正しく抽出すること', () => { + const content = ` +# Documentation + + + +\`\`\`typescript + +const x = 1; +\`\`\` + + + +\`\`\`javascript + +\`\`\` + + + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(3); + expect(result[0].refPath).toBe('src/valid1.ts'); + expect(result[1].refPath).toBe('src/valid2.ts'); + expect(result[2].refPath).toBe('src/valid3.ts'); + }); }); describe('validateCodeRef', () => { diff --git a/src/core/validate.ts b/src/core/validate.ts index 80f1644..b6dbde4 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -53,7 +53,7 @@ export function findMarkdownFiles(dir: string): string[] { function getCodeBlockRanges(content: string): { start: number; end: number }[] { const ranges: { start: number; end: number }[] = []; - // Triple backtick code blocks + // Triple backtick code blocks (closed) const codeBlockPattern = /```[\s\S]*?```/g; let match: RegExpExecArray | null; @@ -64,6 +64,28 @@ function getCodeBlockRanges(content: string): { start: number; end: number }[] { }); } + // Find unclosed code blocks (starting ``` without closing ```) + const allCodeBlockStarts = /```/g; + const closedRanges = ranges.slice(); // Copy of closed ranges + let startMatch: RegExpExecArray | null; + + while ((startMatch = allCodeBlockStarts.exec(content)) !== null) { + const startPos = startMatch.index; + + // Check if this start position is already part of a closed block + const isPartOfClosedBlock = closedRanges.some( + (range) => startPos >= range.start && startPos < range.end + ); + + if (!isPartOfClosedBlock) { + // This is an unclosed block - treat from start to end of content + ranges.push({ + start: startPos, + end: content.length, + }); + } + } + // Inline code (backticks) const inlineCodePattern = /`[^`\n]+?`/g; while ((match = inlineCodePattern.exec(content)) !== null) { From fd132239a2ed46ff8e966163b8149a9605128b53 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Thu, 1 Jan 2026 13:54:15 +0900 Subject: [PATCH 2/2] refactor: improve code block detection logic using odd/even pairing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improved the getCodeBlockRanges() function to use a more robust odd/even pairing approach instead of the previous implementation that had potential issues with overlapping ranges. Previous implementation issues: - Closing backticks could be incorrectly treated as unclosed blocks - Range check did not account for the 3-character length of triple backticks - Could create overlapping or duplicate ranges New implementation: - Collects all triple backtick positions in order - Pairs them sequentially: indices 0-1, 2-3, 4-5, etc. - If odd number of backticks exists, the last one starts an unclosed block - Cleaner logic with fewer edge cases All existing tests pass (372 tests). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/core/validate.ts | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/core/validate.ts b/src/core/validate.ts index b6dbde4..bee5e27 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -53,34 +53,30 @@ export function findMarkdownFiles(dir: string): string[] { function getCodeBlockRanges(content: string): { start: number; end: number }[] { const ranges: { start: number; end: number }[] = []; - // Triple backtick code blocks (closed) - const codeBlockPattern = /```[\s\S]*?```/g; + // Find all triple backtick positions + const backtickPositions: number[] = []; + const backtickPattern = /```/g; let match: RegExpExecArray | null; - while ((match = codeBlockPattern.exec(content)) !== null) { - ranges.push({ - start: match.index, - end: match.index + match[0].length, - }); + while ((match = backtickPattern.exec(content)) !== null) { + backtickPositions.push(match.index); } - // Find unclosed code blocks (starting ``` without closing ```) - const allCodeBlockStarts = /```/g; - const closedRanges = ranges.slice(); // Copy of closed ranges - let startMatch: RegExpExecArray | null; - - while ((startMatch = allCodeBlockStarts.exec(content)) !== null) { - const startPos = startMatch.index; + // Pair up backticks: even indices (0, 2, 4...) are opening, odd indices (1, 3, 5...) are closing + for (let i = 0; i < backtickPositions.length; i += 2) { + const start = backtickPositions[i]; + const end = backtickPositions[i + 1]; - // Check if this start position is already part of a closed block - const isPartOfClosedBlock = closedRanges.some( - (range) => startPos >= range.start && startPos < range.end - ); - - if (!isPartOfClosedBlock) { - // This is an unclosed block - treat from start to end of content + if (end !== undefined) { + // Closed code block + ranges.push({ + start, + end: end + 3, // +3 to include the closing ``` + }); + } else { + // Unclosed code block (odd number of backticks) ranges.push({ - start: startPos, + start, end: content.length, }); }