mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 08:53:22 +00:00
fix(markdown): treat # lines inside closed frontmatter as YAML comments, not headings (#2153)
`parseMarkdown` previously walked the lines after the opening `---` and
recorded the first `^#{1,6}\s`-shaped line as a `headingBeforeClose`,
then flagged MISSING_CLOSE when that index came before the actual closing
fence. YAML allows `#` comment lines anywhere inside the document, so a
template that leads with annotation comments inside the fence (e.g. a
`# Research Template` header before the keys) hit a false-positive
MISSING_CLOSE even though the closing `---` was present.
Fix: only walk for the closing `---`. When it is found, content between
the fences is YAML; `#` lines are comments, not headings. When the close
is genuinely missing, surface the first heading-shaped line as a
where-it-went-off-the-rails hint (this path was already correct; we keep
it for the genuine missing-close case).
Two regression tests added to `test/markdown-validation.test.ts`:
- `#` comment lines at the top of a closed frontmatter
- `#` comment lines interleaved with keys
All 68 tests across the four markdown/frontmatter test files stay green.
This commit is contained in:
+19
-19
@@ -213,39 +213,39 @@ function collectValidationErrors(
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. MISSING_CLOSE — find the next `---` after the opener. If a markdown
|
||||
// heading appears before it, that's a strong signal the closing
|
||||
// delimiter is missing (the heading was meant to be in the body).
|
||||
// 3. MISSING_CLOSE — find the next `---` after the opener.
|
||||
let closeLine = -1;
|
||||
let headingBeforeClose = -1;
|
||||
for (let i = firstNonEmpty + 1; i < lines.length; i++) {
|
||||
const t = lines[i].trim();
|
||||
if (t === '---') {
|
||||
if (lines[i].trim() === '---') {
|
||||
closeLine = i;
|
||||
break;
|
||||
}
|
||||
if (/^#{1,6}\s/.test(t) && headingBeforeClose === -1) {
|
||||
headingBeforeClose = i;
|
||||
}
|
||||
}
|
||||
if (closeLine === -1) {
|
||||
// No closing fence found. Surface the first heading-shaped line as a
|
||||
// hint for where the parser thinks the frontmatter went off the rails —
|
||||
// only useful when the close is genuinely missing, since YAML allows
|
||||
// `#` comment lines inside a closed fence (see comment below).
|
||||
let headingHint = -1;
|
||||
for (let i = firstNonEmpty + 1; i < lines.length; i++) {
|
||||
if (/^#{1,6}\s/.test(lines[i].trim())) {
|
||||
headingHint = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
errors.push({
|
||||
code: 'MISSING_CLOSE',
|
||||
message:
|
||||
headingBeforeClose >= 0
|
||||
? `No closing --- before heading at line ${headingBeforeClose + 1}`
|
||||
headingHint >= 0
|
||||
? `No closing --- before heading at line ${headingHint + 1}`
|
||||
: 'No closing --- delimiter found',
|
||||
line: headingBeforeClose >= 0 ? headingBeforeClose + 1 : firstNonEmpty + 1,
|
||||
line: headingHint >= 0 ? headingHint + 1 : firstNonEmpty + 1,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (headingBeforeClose >= 0 && headingBeforeClose < closeLine) {
|
||||
errors.push({
|
||||
code: 'MISSING_CLOSE',
|
||||
message: `Heading at line ${headingBeforeClose + 1} found inside frontmatter zone (closing --- comes after)`,
|
||||
line: headingBeforeClose + 1,
|
||||
});
|
||||
}
|
||||
// Closing fence found. Content between opening and closing is YAML, which
|
||||
// permits `#` comment lines anywhere — those are not markdown headings
|
||||
// and must not raise MISSING_CLOSE.
|
||||
|
||||
// 4. EMPTY_FRONTMATTER — open and close present but nothing meaningful between.
|
||||
const fmBody = lines.slice(firstNonEmpty + 1, closeLine).join('\n').trim();
|
||||
|
||||
@@ -50,6 +50,38 @@ describe('parseMarkdown validation surface', () => {
|
||||
const e = parsed.errors!.find(e => e.code === 'MISSING_CLOSE');
|
||||
expect(e).toBeDefined();
|
||||
});
|
||||
|
||||
test('YAML # comment at top of closed frontmatter does NOT trigger MISSING_CLOSE', () => {
|
||||
// Real-world repro: research-note templates often lead with `#` comment
|
||||
// lines as YAML comments inside the fence. The parser previously read
|
||||
// these as markdown H1s and false-positived MISSING_CLOSE even when the
|
||||
// closing `---` was present.
|
||||
const md = `${fence}
|
||||
# Research Template
|
||||
# This file serves as a template for all research findings
|
||||
|
||||
research_id: "R19"
|
||||
title: "iOS App Clip security limitations"
|
||||
${fence}
|
||||
|
||||
body`;
|
||||
const parsed = parseMarkdown(md, undefined, { validate: true });
|
||||
expect(parsed.errors!.map(e => e.code)).not.toContain('MISSING_CLOSE');
|
||||
});
|
||||
|
||||
test('YAML # comments interleaved with keys do NOT trigger MISSING_CLOSE', () => {
|
||||
const md = `${fence}
|
||||
type: concept
|
||||
# section: identifiers
|
||||
research_id: "R19"
|
||||
# section: routing
|
||||
slug: research/r19
|
||||
${fence}
|
||||
|
||||
body`;
|
||||
const parsed = parseMarkdown(md, undefined, { validate: true });
|
||||
expect(parsed.errors!.map(e => e.code)).not.toContain('MISSING_CLOSE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('YAML_PARSE', () => {
|
||||
|
||||
Reference in New Issue
Block a user