From 543f4a4226c35f94c4b697ce13a07e305ddeaab1 Mon Sep 17 00:00:00 2001 From: ydah Date: Fri, 26 Dec 2025 00:32:35 +0900 Subject: [PATCH 1/2] Fix ERB text flow formatting to keep adjacent inline elements together Fixes: https://github.com/marcoroth/herb/issues/903 Co-Authored-By: Marco Roth --- .../packages/formatter/src/format-helpers.ts | 11 +- .../packages/formatter/src/format-printer.ts | 220 ++++++------------ .../test/document-formatting.test.ts | 3 +- .../erb-formatter-additional.test.ts | 8 +- .../erb-formatter-compat.test.ts | 8 +- .../packages/formatter/test/erb/erb.test.ts | 62 +++-- .../test/erb/whitespace-formatting.test.ts | 144 ++++++++++-- .../herb-disable-comment-formatting.test.ts | 23 +- 8 files changed, 259 insertions(+), 220 deletions(-) diff --git a/javascript/packages/formatter/src/format-helpers.ts b/javascript/packages/formatter/src/format-helpers.ts index b864827ba..fe80b5d09 100644 --- a/javascript/packages/formatter/src/format-helpers.ts +++ b/javascript/packages/formatter/src/format-helpers.ts @@ -398,19 +398,24 @@ export function isContentPreserving(element: HTMLElementNode | HTMLOpenTagNode | } /** - * Count consecutive inline elements/ERB at the start of children (with no whitespace between) + * Count consecutive inline elements/ERB with no whitespace between them. + * Starts from startIndex and skips indices in processedIndices. */ -export function countAdjacentInlineElements(children: Node[]): number { +export function countAdjacentInlineElements(children: Node[], startIndex = 0, processedIndices?: Set): number { let count = 0 let lastSignificantIndex = -1 - for (let i = 0; i < children.length; i++) { + for (let i = startIndex; i < children.length; i++) { const child = children[i] if (isPureWhitespaceNode(child) || isNode(child, WhitespaceNode)) { continue } + if (processedIndices?.has(i)) { + break + } + const isInlineOrERB = (isNode(child, HTMLElementNode) && isInlineElement(getTagName(child))) || isNode(child, ERBContentNode) if (!isInlineOrERB) { diff --git a/javascript/packages/formatter/src/format-printer.ts b/javascript/packages/formatter/src/format-printer.ts index c37a50356..5c4cf9db6 100644 --- a/javascript/packages/formatter/src/format-printer.ts +++ b/javascript/packages/formatter/src/format-printer.ts @@ -1831,9 +1831,9 @@ export class FormatPrinter extends Printer { const adjacentInlineCount = countAdjacentInlineElements(children) if (adjacentInlineCount >= 2) { - const { processedIndices } = this.renderAdjacentInlineElements(children, adjacentInlineCount) - this.visitRemainingChildren(children, processedIndices) - + const lastProcessedIndex = this.renderAdjacentInlinePrefix(children, adjacentInlineCount) + const remainingChildren = children.slice(lastProcessedIndex + 1) + this.buildAndWrapTextFlow(remainingChildren) return } @@ -1841,110 +1841,14 @@ export class FormatPrinter extends Printer { } /** - * Wrap remaining words that don't fit on the current line - * Returns the wrapped lines with proper indentation + * Render adjacent inline elements at the start of children as a prefix. + * Handles line-breaking elements (br, hr) by flushing accumulated content. + * Returns the index of the last processed child. */ - private wrapRemainingWords(words: string[], wrapWidth: number): string[] { - const lines: string[] = [] - let line = "" - - for (const word of words) { - const testLine = line + (line ? " " : "") + word - - if (testLine.length > wrapWidth && line) { - lines.push(this.indent + line) - line = word - } else { - line = testLine - } - } - - if (line) { - lines.push(this.indent + line) - } - - return lines - } - - /** - * Try to merge text starting with punctuation to inline content - * Returns object with merged content and whether processing should stop - */ - private tryMergePunctuationText(inlineContent: string, trimmedText: string, wrapWidth: number): { mergedContent: string, shouldStop: boolean, wrappedLines: string[] } { - const combined = inlineContent + trimmedText - - if (combined.length <= wrapWidth) { - return { - mergedContent: inlineContent + trimmedText, - shouldStop: false, - wrappedLines: [] - } - } - - const match = trimmedText.match(/^[.!?:;]+/) - - if (!match) { - return { - mergedContent: inlineContent, - shouldStop: false, - wrappedLines: [] - } - } - - const punctuation = match[0] - const restText = trimmedText.substring(punctuation.length).trim() - - if (!restText) { - return { - mergedContent: inlineContent + punctuation, - shouldStop: false, - wrappedLines: [] - } - } - - const words = restText.split(/[ \t\n\r]+/) - let toMerge = punctuation - let mergedWordCount = 0 - - for (const word of words) { - const testMerge = toMerge + ' ' + word - - if ((inlineContent + testMerge).length <= wrapWidth) { - toMerge = testMerge - mergedWordCount++ - } else { - break - } - } - - const mergedContent = inlineContent + toMerge - - if (mergedWordCount >= words.length) { - return { - mergedContent, - shouldStop: false, - wrappedLines: [] - } - } - - const remainingWords = words.slice(mergedWordCount) - const wrappedLines = this.wrapRemainingWords(remainingWords, wrapWidth) - - return { - mergedContent, - shouldStop: true, - wrappedLines - } - } - - /** - * Render adjacent inline elements together on one line - */ - private renderAdjacentInlineElements(children: Node[], count: number): { processedIndices: Set } { + private renderAdjacentInlinePrefix(children: Node[], count: number): number { let inlineContent = "" let processedCount = 0 let lastProcessedIndex = -1 - const processedIndices = new Set() for (let index = 0; index < children.length && processedCount < count; index++) { const child = children[index] @@ -1957,9 +1861,8 @@ export class FormatPrinter extends Printer { inlineContent += this.renderInlineElementAsString(child) processedCount++ lastProcessedIndex = index - processedIndices.add(index) - if (inlineContent && isLineBreakingElement(child)) { + if (isLineBreakingElement(child)) { this.pushWithIndent(inlineContent) inlineContent = "" } @@ -1967,43 +1870,23 @@ export class FormatPrinter extends Printer { inlineContent += this.renderERBAsString(child) processedCount++ lastProcessedIndex = index - processedIndices.add(index) } } - if (lastProcessedIndex >= 0) { + if (inlineContent && lastProcessedIndex >= 0) { for (let index = lastProcessedIndex + 1; index < children.length; index++) { const child = children[index] if (isPureWhitespaceNode(child) || isNode(child, WhitespaceNode)) { - continue - } - - if (isNode(child, ERBContentNode)) { - inlineContent += this.renderERBAsString(child) - processedIndices.add(index) - continue + break } if (isNode(child, HTMLTextNode)) { - const trimmed = child.content.trim() - - if (trimmed && /^[.!?:;]/.test(trimmed)) { - const wrapWidth = this.maxLineLength - this.indent.length - const result = this.tryMergePunctuationText(inlineContent, trimmed, wrapWidth) + const words = normalizeAndSplitWords(child.content) - inlineContent = result.mergedContent - processedIndices.add(index) - - if (result.shouldStop) { - if (inlineContent) { - this.pushWithIndent(inlineContent) - } - - result.wrappedLines.forEach(line => this.push(line)) - - return { processedIndices } - } + if (words.length > 0 && words[0] && !' \t\n\r'.includes(words[0][0])) { + inlineContent += words[0] + lastProcessedIndex = index } } @@ -2015,7 +1898,7 @@ export class FormatPrinter extends Printer { this.pushWithIndent(inlineContent) } - return { processedIndices } + return lastProcessedIndex } /** @@ -2053,25 +1936,6 @@ export class FormatPrinter extends Printer { }).join("") } - /** - * Visit remaining children after processing adjacent inline elements - */ - private visitRemainingChildren(children: Node[], processedIndices: Set): void { - for (let index = 0; index < children.length; index++) { - const child = children[index] - - if (isPureWhitespaceNode(child) || isNode(child, WhitespaceNode)) { - continue - } - - if (processedIndices.has(index)) { - continue - } - - this.visit(child) - } - } - /** * Build words array from text/inline/ERB and wrap them */ @@ -2123,6 +1987,10 @@ export class FormatPrinter extends Printer { } } + if (words.length > 0) { + words[words.length - 1].word = words[words.length - 1].word.trimEnd() + } + this.flushWords(words) } @@ -2272,12 +2140,45 @@ export class FormatPrinter extends Printer { return false } - if (lastProcessedIndex >= 0) { - const hasWhitespace = hasWhitespaceBetween(children, lastProcessedIndex, index) || this.lastUnitEndsWithWhitespace(result) + const hasWhitespace = lastProcessedIndex >= 0 ? hasWhitespaceBetween(children, lastProcessedIndex, index) || this.lastUnitEndsWithWhitespace(result) : true - if (!hasWhitespace && this.tryMergeAtomicAfterText(result, children, lastProcessedIndex, inlineContent, 'inline', child)) { + if (isLineBreakingElement(child)) { + if (!hasWhitespace && result.length > 0) { + const lastUnit = result[result.length - 1] + + if (lastUnit.unit.isAtomic && (lastUnit.unit.type === 'inline' || lastUnit.unit.type === 'erb')) { + lastUnit.unit.content += inlineContent + + result.push({ + unit: { content: '', type: 'block', isAtomic: false, breaksFlow: true }, + node: null + }) + + return true + } + } + + result.push({ + unit: { content: '', type: 'block', isAtomic: false, breaksFlow: true }, + node: child + }) + + return false + } + + if (!hasWhitespace && lastProcessedIndex >= 0) { + if (this.tryMergeAtomicAfterText(result, children, lastProcessedIndex, inlineContent, 'inline', child)) { return true } + + if (result.length > 0) { + const lastUnit = result[result.length - 1] + + if (lastUnit.unit.isAtomic && (lastUnit.unit.type === 'inline' || lastUnit.unit.type === 'erb')) { + lastUnit.unit.content += inlineContent + return true + } + } } result.push({ @@ -2302,6 +2203,15 @@ export class FormatPrinter extends Printer { return true } + if (!hasWhitespace && result.length > 0) { + const lastUnit = result[result.length - 1] + + if (lastUnit.unit.isAtomic && (lastUnit.unit.type === 'inline' || lastUnit.unit.type === 'erb')) { + lastUnit.unit.content += erbContent + return true + } + } + if (hasWhitespace && result.length > 0) { const lastUnit = result[result.length - 1] const lastIsAtomic = lastUnit.unit.isAtomic && (lastUnit.unit.type === 'inline' || lastUnit.unit.type === 'erb') @@ -2433,7 +2343,7 @@ export class FormatPrinter extends Printer { nextEffectiveLength = effectiveLength + spaceBefore + word.length } - if (currentLine && !isClosingPunctuation(word) && nextEffectiveLength >= wrapWidth) { + if (currentLine && !isClosingPunctuation(word) && nextEffectiveLength > wrapWidth) { lines.push(this.indent + currentLine.trimEnd()) currentLine = word diff --git a/javascript/packages/formatter/test/document-formatting.test.ts b/javascript/packages/formatter/test/document-formatting.test.ts index e20003a48..4aff75a7e 100644 --- a/javascript/packages/formatter/test/document-formatting.test.ts +++ b/javascript/packages/formatter/test/document-formatting.test.ts @@ -689,8 +689,7 @@ describe("Document-level formatting", () => {

- <%= event.static_metadata.location %> • - <%= event.formatted_dates %> + <%= event.static_metadata.location %> • <%= event.formatted_dates %>

Hanakai

@@ -288,8 +312,7 @@ describe("@herb-tools/formatter", () => {

This will be the all-in-one home for everything to do with Hanami, - Dry and - Rom. + Dry and Rom.

`) @@ -360,13 +383,13 @@ describe("@herb-tools/formatter", () => { const result = formatter.format(input) expect(result).toBe(dedent` -

- Visit - our amazing product catalog with hundreds of items - or contact our customer support team for assistance - with your order. -

- `) +

+ Visit + our amazing product catalog with hundreds of items or + contact our customer support team for assistance with + your order. +

+ `) }) test("handles multiple inline elements with adjacent text", () => { @@ -652,8 +675,8 @@ describe("@herb-tools/formatter", () => { <%= hosted_image_tag('mailer/footer-logo.png', class: 'h-[48px] mb-1') %>

- ©<%= Time.current.year %> - Company Inc, All - rights reserved. + ©<%= Time.current.year %> - Company Inc, All rights + reserved.
Main Street, San Francisco, CAs, USA 12345
@@ -1363,9 +1386,7 @@ describe("@herb-tools/formatter", () => { Cover Image
Dimensions - - <%= "#{cover.metadata['width']}x#{cover.metadata['height']}" %> - + <%= "#{cover.metadata['width']}x#{cover.metadata['height']}" %> — <%= link_to "View original", rails_blob_path(cover), target: "_blank", rel: "noopener" %>
@@ -1373,6 +1394,7 @@ describe("@herb-tools/formatter", () => { <% end %> `) + }) test("adjecent ERB text within elements", () => { @@ -1437,8 +1459,8 @@ describe("@herb-tools/formatter", () => { const expected = dedent`

- <%= icon("icon") %>some text some text some text some text some text some - text some text + <%= icon("icon") %>some text some text some text some text some text some text + some text
` @@ -1448,12 +1470,12 @@ describe("@herb-tools/formatter", () => { test("ERB output after adjecent text within HTML element causing line-break", () => { const input = dedent` -
some text some text some text some text some text some text<%= icon("icon") %>
+
some text some text some text some text some text somes text<%= icon("icon") %>
` const expected = dedent`
- some text some text some text some text some text some + some text some text some text some text some text somes text<%= icon("icon") %>
` @@ -1530,8 +1552,8 @@ describe("@herb-tools/formatter", () => { const expected = dedent` <%= link_to "/" do %> - <%= icon("icon") %>some text some text some text some text some text some - text some text + <%= icon("icon") %>some text some text some text some text some text some text + some text <% end %> ` diff --git a/javascript/packages/formatter/test/erb/whitespace-formatting.test.ts b/javascript/packages/formatter/test/erb/whitespace-formatting.test.ts index 4735ffccd..2ed4f5af4 100644 --- a/javascript/packages/formatter/test/erb/whitespace-formatting.test.ts +++ b/javascript/packages/formatter/test/erb/whitespace-formatting.test.ts @@ -44,19 +44,14 @@ describe("ERB whitespace formatting", () => { `) - expect(result).toContain('<% if disabled %>') - expect(result).toContain('<% end %>') - - expect(result).not.toContain('<% if disabled%>') - expect(result).not.toContain('<%end%>') + expect(formatter.format(result)).toBe(result) }) test("preserves already properly spaced ERB tags", () => { const source = '
class="test" <% end %>>
' const result = formatter.format(source) - expect(result).toContain('<% if condition %>') - expect(result).toContain('<% end %>') + expect(result).toBe('
class="test" <% end %>>
') }) test("formats standalone ERB content tags with proper spacing", () => { @@ -103,11 +98,13 @@ describe("ERB whitespace formatting", () => { ` const result = formatter.format(source) - expect(result).toContain('<%= user.name %>') - expect(result).toContain('
') - expect(result).toContain('
') - expect(result).toContain('') - expect(result).toContain('') + expect(result).toBe(dedent` +
+ <% users.each do |user| %> + <%= user.name %> + <% end %> +
+ `) }) test("does not add whitespace before apostrophe after ERB tag (issue #855)", () => { @@ -390,6 +387,119 @@ describe("ERB whitespace formatting", () => { }) }) + describe("line breaking elements and text flow", () => { + test("does not add line breaks after ERB tags following
", () => { + const source = dedent` +

Ut enim ad minima veniam
+ <%= foo %> sed quia consequuntur magni <%= bar %>. Lorem ipsum dolor sit amet...

+ ` + const result = formatter.format(source) + + expect(result).toBe(dedent` +

+ Ut enim ad minima veniam
+ <%= foo %> sed quia consequuntur magni <%= bar %>. Lorem ipsum dolor sit + amet... +

+ `) + }) + + test("keeps period attached to ERB tag in text flow", () => { + const source = `

Summary:
Lorem ipsum <%= foo %> dolor <%= bar %>. Sit amet...

` + const result = formatter.format(source) + + expect(result).toBe(dedent` +

+ Summary:
+ Lorem ipsum <%= foo %> dolor <%= bar %>. Sit amet... +

+ `) + }) + + test("issue 903: does not separate ERB tags from surrounding text", () => { + expectFormattedToMatch(`Failed to <%= model.ignored? ? "unignore" : "ignore" %> model`) + }) + + test("issue 903: preserves apostrophe with ERB tag inline", () => { + expectFormattedToMatch(`

<%= user.name %>'s profile

`) + }) + + test("issue 903: preserves possessive apostrophe after ERB tag", () => { + expectFormattedToMatch(`Waiting for <%= contractor.name.first_or_business %>'s signature.`) + }) + + test("issue 903: multiple ERB tags in text flow after
", () => { + const source = dedent` +

Summary:
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua, <%= foo %> quis nostrud exercitation ullamco laboris. Duis aute irure dolor in reprehenderit <%= bar %> that <%= baz %> sed quia non numquam eius modi tempora.

+ ` + const result = formatter.format(source) + + expect(result).toBe(dedent` +

+ Summary:
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua, <%= foo %> quis nostrud + exercitation ullamco laboris. Duis aute irure dolor in reprehenderit + <%= bar %> that <%= baz %> sed quia non numquam eius modi tempora. +

+ `) + }) + + test("issue 903: full example with two paragraphs containing
and ERB", () => { + const source = dedent` +

Ut enim ad minima veniam
+ <%= foo %> sed quia consequuntur magni <%= bar %>. Lorem ipsum dolor sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ +

Summary:
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua, <%= foo %> quis nostrud exercitation ullamco laboris. Duis aute irure dolor in reprehenderit <%= bar %> that <%= baz %> sed quia non numquam eius modi tempora.

+ ` + const result = formatter.format(source) + + expect(result).toBe(dedent` +

+ Ut enim ad minima veniam
+ <%= foo %> sed quia consequuntur magni <%= bar %>. Lorem ipsum dolor sit amet + consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor + in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui + officia deserunt mollit anim id est laborum. +

+ +

+ Summary:
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua, <%= foo %> quis nostrud + exercitation ullamco laboris. Duis aute irure dolor in reprehenderit + <%= bar %> that <%= baz %> sed quia non numquam eius modi tempora. +

+ `) + }) + + test("handles long text with ERB tags after
", () => { + const source = dedent` +

Ut enim ad minima veniam
+ <%= foo %> sed quia consequuntur magni <%= bar %>. Lorem ipsum dolor sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ ` + const result = formatter.format(source) + + expect(result).toBe(dedent` +

+ Ut enim ad minima veniam
+ <%= foo %> sed quia consequuntur magni <%= bar %>. Lorem ipsum dolor sit amet + consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor + in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui + officia deserunt mollit anim id est laborum. +

+ `) + }) + }) + describe("shared utility validation", () => { test("demonstrates consistent ERB content formatting where it applies", () => { const erbContentCases = [ @@ -406,14 +516,8 @@ describe("ERB whitespace formatting", () => { }) test("documents current behavior for ERB logic tags", () => { - const logicCases = ['<% if condition%>', '<%end%>'] - - logicCases.forEach(testCase => { - const result = formatter.format(testCase) - - expect(result).toContain('<%') - expect(result).toContain('%>') - }) + expect(formatter.format('<% if condition%>')).toBe('<% if condition%>') + expect(formatter.format('<%end%>')).toBe('<%end%>') }) }) }) diff --git a/javascript/packages/formatter/test/herb-disable-comment-formatting.test.ts b/javascript/packages/formatter/test/herb-disable-comment-formatting.test.ts index 325078bea..ab402c24e 100644 --- a/javascript/packages/formatter/test/herb-disable-comment-formatting.test.ts +++ b/javascript/packages/formatter/test/herb-disable-comment-formatting.test.ts @@ -30,8 +30,8 @@ describe("herb:disable comment formatting", () => { expect(result).toBe(dedent`
<%# herb:disable html-tag-name-lowercase %> Dolores id occaecati ipsam. Eius blanditiis odio quas. Corrupti officia quasi - sunt neque soluta veritatis. Sint esse nihil alias quia qui. Aut omnis quia - ut dolores reiciendis. Numquam voluptate esse voluptas. + sunt neque soluta veritatis. Sint esse nihil alias quia qui. Aut omnis quia ut + dolores reiciendis. Numquam voluptate esse voluptas.
<%# herb:disable html-tag-name-lowercase %> `) }) @@ -51,8 +51,8 @@ describe("herb:disable comment formatting", () => {
<%# herb:disable html-tag-name-lowercase %> Dolores id occaecati ipsam. Eius blanditiis odio quas. Corrupti officia - quasi sunt neque soluta veritatis. Sint esse nihil alias quia qui. Aut - omnis quia ut dolores reiciendis. Numquam voluptate esse voluptas. + quasi sunt neque soluta veritatis. Sint esse nihil alias quia qui. Aut omnis + quia ut dolores reiciendis. Numquam voluptate esse voluptas.
<%# herb:disable html-tag-name-lowercase %>
`) @@ -85,8 +85,8 @@ describe("herb:disable comment formatting", () => { expect(result).toBe(dedent`

<%# herb:disable some-rule %> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod - tempor incididunt ut labore et dolore magna aliqua. + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua.

`) }) @@ -102,8 +102,8 @@ describe("herb:disable comment formatting", () => { expect(result).toBe(dedent` <%# herb:disable all %> - Some content that needs wrapping because it is quite long and exceeds the - line length limit. + Some content that needs wrapping because it is quite long and exceeds the line + length limit. `) }) @@ -152,8 +152,7 @@ describe("herb:disable comment formatting", () => { expect(result).toBe(dedent`

- Some text with <%# herb:disable some-rule %>inline content - here. + Some text with <%# herb:disable some-rule %>inline content here.

`) }) @@ -286,8 +285,8 @@ describe("herb:disable comment formatting", () => { const expectedOutput = dedent`
<%# herb:disable html-tag-name-lowercase %> Dolores id occaecati ipsam. Eius blanditiis odio quas. Corrupti officia quasi - sunt neque soluta veritatis. Sint esse nihil alias quia qui. Aut omnis quia - ut dolores reiciendis. Numquam voluptate esse voluptas. + sunt neque soluta veritatis. Sint esse nihil alias quia qui. Aut omnis quia ut + dolores reiciendis. Numquam voluptate esse voluptas.
<%# herb:disable html-tag-name-lowercase %> ` From ce8c7327b4d2ea6aec2024b668a3f3cac3c16907 Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Mon, 2 Mar 2026 02:35:39 +0100 Subject: [PATCH 2/2] Use `expectFormattedToMatch` --- .../test/erb/whitespace-formatting.test.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/javascript/packages/formatter/test/erb/whitespace-formatting.test.ts b/javascript/packages/formatter/test/erb/whitespace-formatting.test.ts index 2ed4f5af4..70ab853de 100644 --- a/javascript/packages/formatter/test/erb/whitespace-formatting.test.ts +++ b/javascript/packages/formatter/test/erb/whitespace-formatting.test.ts @@ -33,7 +33,7 @@ describe("ERB whitespace formatting", () => { ` const result = formatter.format(source) - expect(result).toBe(dedent` + const expected = dedent` @@ -42,16 +42,14 @@ describe("ERB whitespace formatting", () => { > Text - `) + ` - expect(formatter.format(result)).toBe(result) + expect(result).toBe(expected) + expectFormattedToMatch(expected) }) test("preserves already properly spaced ERB tags", () => { - const source = '
class="test" <% end %>>
' - const result = formatter.format(source) - - expect(result).toBe('
class="test" <% end %>>
') + expectFormattedToMatch('
class="test" <% end %>>
') }) test("formats standalone ERB content tags with proper spacing", () => { @@ -82,10 +80,7 @@ describe("ERB whitespace formatting", () => { }) test("preserves ERB comment formatting", () => { - const source = '<%# This is a comment %>' - const result = formatter.format(source) - - expect(result).toEqual('<%# This is a comment %>') + expectFormattedToMatch('<%# This is a comment %>') }) test("handles complex ERB structures that get inlined", () => {