diff --git a/javascript/packages/formatter/src/format-helpers.ts b/javascript/packages/formatter/src/format-helpers.ts index 8fc9f1974..5a75f0853 100644 --- a/javascript/packages/formatter/src/format-helpers.ts +++ b/javascript/packages/formatter/src/format-helpers.ts @@ -521,3 +521,14 @@ export function isHerbDisableComment(node: Node): boolean { return trimmed.startsWith("herb:disable") } + +/** + * Check if a text node is YAML frontmatter (starts and ends with ---) + */ +export function isFrontmatter(node: Node): node is HTMLTextNode { + if (!isNode(node, HTMLTextNode)) return false + + const content = node.content.trim() + + return content.startsWith("---") && /---\s*$/.test(content) +} diff --git a/javascript/packages/formatter/src/format-printer.ts b/javascript/packages/formatter/src/format-printer.ts index ceca30000..7c20838bc 100644 --- a/javascript/packages/formatter/src/format-printer.ts +++ b/javascript/packages/formatter/src/format-printer.ts @@ -33,6 +33,7 @@ import { isBlockLevelNode, isClosingPunctuation, isContentPreserving, + isFrontmatter, isHerbDisableComment, isInlineElement, isNonWhitespaceNode, @@ -641,15 +642,16 @@ export class FormatPrinter extends Printer { // --- Visitor methods --- visitDocumentNode(node: DocumentNode) { - const hasTextFlow = this.isInTextFlowContext(null, node.children) + const children = this.formatFrontmatter(node) + const hasTextFlow = this.isInTextFlowContext(null, children) if (hasTextFlow) { - const children = filterSignificantChildren(node.children) + const filteredChildren = filterSignificantChildren(children) const wasInlineMode = this.inlineMode this.inlineMode = true - this.visitTextFlowChildren(children) + this.visitTextFlowChildren(filteredChildren) this.inlineMode = wasInlineMode @@ -659,10 +661,10 @@ export class FormatPrinter extends Printer { let lastWasMeaningful = false let hasHandledSpacing = false - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i] + for (let i = 0; i < children.length; i++) { + const child = children[i] - if (shouldPreserveUserSpacing(child, node.children, i)) { + if (shouldPreserveUserSpacing(child, children, i)) { this.push("") hasHandledSpacing = true continue @@ -672,8 +674,8 @@ export class FormatPrinter extends Printer { continue } - if (shouldAppendToLastLine(child, node.children, i)) { - this.appendChildToLastLine(child, node.children, i) + if (shouldAppendToLastLine(child, children, i)) { + this.appendChildToLastLine(child, children, i) lastWasMeaningful = true hasHandledSpacing = false continue @@ -1473,6 +1475,21 @@ export class FormatPrinter extends Printer { // --- Utility methods --- + private formatFrontmatter(node: DocumentNode): Node[] { + const firstChild = node.children[0] + const hasFrontmatter = firstChild && isFrontmatter(firstChild) + + if (!hasFrontmatter) return node.children + + this.push(firstChild.content.trimEnd()) + + const remaining = node.children.slice(1) + + if (remaining.length > 0) this.push("") + + return remaining + } + /** * Append a child node to the last output line */ diff --git a/javascript/packages/formatter/test/erb-formatter/erb-formatter-fixtures.test.ts b/javascript/packages/formatter/test/erb-formatter/erb-formatter-fixtures.test.ts index ad83354c0..0c4ab6a8a 100644 --- a/javascript/packages/formatter/test/erb-formatter/erb-formatter-fixtures.test.ts +++ b/javascript/packages/formatter/test/erb-formatter/erb-formatter-fixtures.test.ts @@ -590,7 +590,10 @@ describe("ERB Formatter Fixture Tests", () => { const result = formatter.format(source) expect(result).toBe(dedent` - --- title: "My Page" layout: "application" --- + --- + title: "My Page" + layout: "application" + ---
Debug mode
+ <% end %> + ` + const result = formatter.format(source) + expect(result).toEqual(source) + }) + + test("preserves frontmatter indentation as-is", () => { + const source = dedent` + --- + nested: + key: value + another: + deep: true + --- + +Text
+Text
+Some content
+ --- +More content
+