import { LintResult } from '../linter.js'; import { Root } from 'mdast'; import { visit } from 'unist-util-visit'; export function checkCodeBlocks(tree: Root): LintResult[] { const results: LintResult[] = []; visit(tree, 'code', (node: any, index: any, parent: any) => { // Rule 11: All code blocks must have language specified if (!node.lang) { results.push({ ruleId: '11', message: 'Code block must have a language specified' }); } const codeContent = node.value || ''; const lines = codeContent.split('\n'); // Rule 13: Additions must have //addstart //addend // Rule 14: Deletions must have //delstart //delend // We check if it *looks* like a diff. // Actually, strict diffs use +/- but here we use markers. // If we see ONE marker, we expect the matching pair. const hasAddStart = codeContent.includes('//addstart'); const hasAddEnd = codeContent.includes('//addend'); const hasDelStart = codeContent.includes('//delstart'); const hasDelEnd = codeContent.includes('//delend'); if (hasAddStart !== hasAddEnd) { results.push({ ruleId: '13', message: 'Mismatching //addstart and //addend markers' }); } if (hasDelStart !== hasDelEnd) { results.push({ ruleId: '14', message: 'Mismatching //delstart and //delend markers' }); } // Rule 12: File path before code block // If code block represents file content, it should have a path. // Heuristic: If it has markers (add/del), it implies editing a file, so it checks for path. // Also if check "modification" vs "command". // If language is NOT bash/shell, maybe it means file? // "ファイル追加・更新のコードブロック前に" (Before code blocks for file addition/update). const isFileEdit = hasAddStart || hasDelStart; // Simplified check. // User might just post a whole file without markers (new file). // But Rule 13 says "Additional code uses //addstart". // Rule 12 says "Before code block for file addition/update". // So if it's strictly a command, no need. if (isFileEdit) { // Check previous node for path if (index > 0) { const prevNode = parent.children[index - 1]; // Expecting formatting: _path/to/file_ (Emphasis) // It might be inside a Paragraph. let foundPath = false; if (prevNode.type === 'paragraph') { // Check if paragraph contains ONLY emphasis or starts with emphasis? // "記述" (Describe). // Markdown: _path/to/file_ -> Emphasis node. const emphasisNode = prevNode.children.find((c: any) => c.type === 'emphasis'); if (emphasisNode) { foundPath = true; // Could check if text looks like path? } } if (!foundPath) { results.push({ ruleId: '12', message: 'File addition/update code block must be preceded by file path in italics (_path_)' }); } } } // Rule 15: One command per block // If language is bash/shell/zsh, check for multiple commands? if (['bash', 'shell', 'zsh', 'sh'].includes(node.lang)) { // Heuristic: Multiple lines that might be commands? // This is hard to enforce strictly without false positives. // Ignoring for now or lightweight check? // "1コードブロックにつき1つのコマンド" // If I see `cd .. && ls` it's one line. // If I see: // command1 // command2 // That's two commands. // But output might be multi-line. // Rule says "Command OR Code unit". // If 15 is strict, maybe count non-empty lines that don't start with output? // Skipping strict implementation for Rule 15 to avoid noise, unless required specific logic. } }); return results; }