import { LintResult } from '../linter.js'; import { Root } from 'mdast'; import { visit } from 'unist-util-visit'; export function checkToc(tree: Root): LintResult[] { const results: LintResult[] = []; let frontmatterNodeIndex = -1; let tocFound = false; let tocImmediatelyAfter = false; // Find frontmatter index visit(tree, 'yaml', (node, index) => { if (frontmatterNodeIndex === -1 && typeof index === 'number') { frontmatterNodeIndex = index; } }); if (frontmatterNodeIndex !== -1) { // Check nodes after frontmatter // We expect the next *meaningful* node to be the TOC. // Sometimes there might be empty text nodes or whitespace, but remark usually handles blocks. // However, if there are blank lines, they might just be ignored in AST structure or appear as distinct nodes depending on config. // With remark-parse, usually the next block-level node is what we care about. const children = tree.children; // Look at the node immediately following YAML // But be careful about index bounds if (frontmatterNodeIndex + 1 < children.length) { const nextNode = children[frontmatterNodeIndex + 1]; // Check if it matches {{ toc }} // Usually parsed as a paragraph containing text "{{ toc }}" if (nextNode.type === 'paragraph') { const textContent = nextNode.children.map(c => (c as any).value).join(''); if (textContent.trim() === '{{ toc }}') { tocImmediatelyAfter = true; } } } } // If frontmatter exists, we expect TOC after it. // If frontmatter is missing, Rule 1 catches it, but if we found FM, we check TOC. if (frontmatterNodeIndex !== -1 && !tocImmediatelyAfter) { results.push({ ruleId: '2', message: '{{ toc }} must be placed immediately after frontmatter' }); } return results; }