What's wrong
The text handed to the YAML parser has its final line ending removed, so the same YAML parses differently depending on where a key sits:
TrySplitFrontmatterBlocks slices each block to lines[close - 1].End (Frontmatter/Frontmatter.cs, ~lines 385–387), which excludes the last line's line ending.
- The parse path then does
string section = block.Trim(); (~line 329).
A literal (|) or keep (|+) block scalar on the last key therefore loses its trailing newline(s).
Repro
Frontmatter.ExtractFrontmatter("---\ntitle: T\nnotes: |\n line1\n line2\n---\nbody\n");
// notes = "line1\nline2" <- clip chomping should give "line1\nline2\n"
Frontmatter.ExtractFrontmatter("---\nnotes: |\n line1\n line2\ntitle: T\n---\nbody\n");
// notes = "line1\nline2\n" <- correct, only because another key follows
// keep chomping: "---\ncode: |+\n x = 1\n\n\n---\nbody\n"
// code = "x = 1" (YamlDotNet on the same YAML directly gives "x = 1\n\n\n")
CombineFrontmatter on the first document writes the value back as
so the trailing newline is permanently lost from the file, and the scalar style changes on what should be a no-op round trip.
Why it matters
Moving a key, or adding one after it, changes its value. Consumers that rely on a trailing newline (code snippets, |+ content, text compared byte-for-byte) get different data. Every CombineFrontmatter pass also rewrites the file.
Suggested fix
Parse the block with its final line ending kept: slice to lines[close].Start instead of lines[close - 1].End. Use string.IsNullOrWhiteSpace only for the empty-block check, and don't Trim() the text that gets parsed. Leading Trim() also strips first-line indentation, which would break uniformly indented frontmatter.
Acceptance criteria
notes parses to "line1\nline2\n" regardless of key position.
- The
|+ example yields "x = 1\n\n\n".
- Combine with AsIs naming and no merging leaves a trailing
| block's value unchanged.
What's wrong
The text handed to the YAML parser has its final line ending removed, so the same YAML parses differently depending on where a key sits:
TrySplitFrontmatterBlocksslices each block tolines[close - 1].End(Frontmatter/Frontmatter.cs, ~lines 385–387), which excludes the last line's line ending.string section = block.Trim();(~line 329).A literal (
|) or keep (|+) block scalar on the last key therefore loses its trailing newline(s).Repro
CombineFrontmatteron the first document writes the value back asso the trailing newline is permanently lost from the file, and the scalar style changes on what should be a no-op round trip.
Why it matters
Moving a key, or adding one after it, changes its value. Consumers that rely on a trailing newline (code snippets,
|+content, text compared byte-for-byte) get different data. Every CombineFrontmatter pass also rewrites the file.Suggested fix
Parse the block with its final line ending kept: slice to
lines[close].Startinstead oflines[close - 1].End. Usestring.IsNullOrWhiteSpaceonly for the empty-block check, and don'tTrim()the text that gets parsed. LeadingTrim()also strips first-line indentation, which would break uniformly indented frontmatter.Acceptance criteria
notesparses to"line1\nline2\n"regardless of key position.|+example yields"x = 1\n\n\n".|block's value unchanged.