From 5d19c4fa32b3d8a7e2c607c701e6dc69afa34009 Mon Sep 17 00:00:00 2001 From: Vijay Misal Date: Mon, 10 Aug 2026 21:12:50 +0530 Subject: [PATCH 1/2] Formatter: fix attribute spacing lost on elements nested in ERB blocks When formatting a content-preserving element (e.g.
) whose body
contains an ERB block/conditional, the formatter falls back to
IdentityPrinter to reconstruct that ERB-wrapped subtree byte-for-byte.

Without track_whitespace: true, the parser doesn't emit a node for the
whitespace that separates a tag name from its first attribute, or the
whitespace between attributes - that gap simply isn't represented in the
AST. IdentityPrinter.visitHTMLOpenTagNode wrote the tag name and then its
children back-to-back with no separator, so any HTML element with
attributes inside an ERB block lost its attribute spacing:

    
<% if condition %>x<% end %>
formatted to:
<% if condition %>x<% end %>
which changes browser behavior (the elements aren't equivalent - the formatted version creates a bogus spanclass custom element and drops the class attribute). Confirmed against @herb-tools/formatter 0.10.3 per the report in #2142. Fix IdentityPrinter.visitHTMLOpenTagNode to compare each child's start position against the end of the previously written node, restoring a single separating space whenever the AST has left a gap. When whitespace tracking is enabled the gap is already covered by an explicit WhitespaceNode, so no extra space is added in that case (verified via the printer package's own IndentPrinter/track_whitespace test suite, which continues to pass unchanged). Added regression tests to javascript/packages/formatter/test/html/content-preserving-tags.test.ts covering both a single-attribute and multi-attribute element nested in an ERB block. Tested via vitest run in javascript/packages/printer (116/116 passing, matching baseline) and javascript/packages/formatter (1372/1480 passing, 2 more than baseline - the 2 new regression tests; the remaining failures are pre-existing and unrelated, mostly CLI-binary and Tailwind-sorter tests that need a built binary/tailwind config not available in this sandbox). Co-Authored-By: Claude Sonnet 5 --- .../test/html/content-preserving-tags.test.ts | 12 ++++++++++ .../packages/printer/src/identity-printer.ts | 24 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/javascript/packages/formatter/test/html/content-preserving-tags.test.ts b/javascript/packages/formatter/test/html/content-preserving-tags.test.ts index 593f854a9..e532fa6d1 100644 --- a/javascript/packages/formatter/test/html/content-preserving-tags.test.ts +++ b/javascript/packages/formatter/test/html/content-preserving-tags.test.ts @@ -178,6 +178,18 @@ describe("@herb-tools/formatter - content preserving tags", () => { expect(formatter.format(result)).toEqual(result) }) + test("preserves attribute spacing on an element nested inside an ERB block (#2142)", () => { + const source = `
<% if condition %>x<% end %>
` + const result = formatter.format(source) + expect(result).toEqual(`
<% if condition %>x<% end %>
`) + }) + + test("preserves spacing between multiple attributes on an element nested inside an ERB block", () => { + const source = `
<% if condition %>x<% end %>
` + const result = formatter.format(source) + expect(result).toEqual(`
<% if condition %>x<% end %>
`) + }) + test("preserves textarea with ERB control flow", () => { const source = dedent`