Skip to content

Commit 7bda9c2

Browse files
authored
Formatter: Fix ERB quote misplacement in HTML attributes (#331)
This pull request fixes the formatter to correctly handle ERB expressions within HTML attribute values. Previously, the formatter would incorrectly move closing tags inside quoted attribute values. **Input:** ```erb <data value="<%= @post.external %>"></data> ``` **Before:** ```erb <data value="<%= @post.external %></data>"> ``` **After:** ```erb <data value="<%= @post.external %>"></data> ``` The fix uses AST-based tag construction instead of string manipulation to ensure proper quote boundaries. Resolves #250
1 parent 9238caf commit 7bda9c2

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

javascript/packages/formatter/src/printer.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,34 @@ export class Printer extends Visitor {
307307
} else if (node.is_void) {
308308
this.push(indent + inline)
309309
} else {
310-
this.push(indent + inline.replace('>', `></${tagName}>`))
310+
let result = `<${tagName}`
311+
312+
if (attributes.length > 0) {
313+
result += ` ${attributes.map(attr => this.renderAttribute(attr)).join(" ")}`
314+
}
315+
316+
if (inlineNodes.length > 0) {
317+
const currentIndentLevel = this.indentLevel
318+
this.indentLevel = 0
319+
const tempLines = this.lines
320+
this.lines = []
321+
322+
inlineNodes.forEach(node => {
323+
const wasInlineMode = this.inlineMode
324+
this.inlineMode = true
325+
this.visit(node)
326+
this.inlineMode = wasInlineMode
327+
})
328+
329+
const inlineContent = this.lines.join("")
330+
this.lines = tempLines
331+
this.indentLevel = currentIndentLevel
332+
333+
result += inlineContent
334+
}
335+
336+
result += `></${tagName}>`
337+
this.push(indent + result)
311338
}
312339

313340
return

javascript/packages/formatter/test/html/attributes.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,64 @@ describe("@herb-tools/formatter", () => {
199199
</div>
200200
`)
201201
})
202+
203+
test("handles ERB content in attribute values correctly (issue #250)", () => {
204+
const source = dedent`
205+
<data value="<%= @post.external %>"></data>
206+
`
207+
const result = formatter.format(source)
208+
expect(result).toEqual(dedent`
209+
<data value="<%= @post.external %>"></data>
210+
`)
211+
})
212+
213+
test("handles complex ERB expressions in attribute values", () => {
214+
const source = dedent`
215+
<div class="<%= user.admin? ? 'admin' : 'user' %>"></div>
216+
`
217+
const result = formatter.format(source)
218+
expect(result).toEqual(dedent`
219+
<div class="<%= user.admin? ? 'admin' : 'user' %>"></div>
220+
`)
221+
})
222+
223+
test("handles multiple ERB expressions in single attribute", () => {
224+
const source = dedent`
225+
<div class="prefix-<%= @id %>-<%= @type %>-suffix"></div>
226+
`
227+
const result = formatter.format(source)
228+
expect(result).toEqual(dedent`
229+
<div class="prefix-<%= @id %>-<%= @type %>-suffix"></div>
230+
`)
231+
})
232+
233+
test("handles ERB in attribute with nested quotes", () => {
234+
const source = dedent`
235+
<img src="<%= asset_path('icons/user.png') %>" alt="User">
236+
`
237+
const result = formatter.format(source)
238+
expect(result).toEqual(dedent`
239+
<img src="<%= asset_path('icons/user.png') %>" alt="User">
240+
`)
241+
})
242+
243+
test("handles ERB in self-closing tags with attributes", () => {
244+
const source = dedent`
245+
<input type="text" value="<%= @user.name %>" />
246+
`
247+
const result = formatter.format(source)
248+
expect(result).toEqual(dedent`
249+
<input type="text" value="<%= @user.name %>" />
250+
`)
251+
})
252+
253+
test("handles ERB in void elements", () => {
254+
const source = dedent`
255+
<input type="text" value="<%= @user.name %>">
256+
`
257+
const result = formatter.format(source)
258+
expect(result).toEqual(dedent`
259+
<input type="text" value="<%= @user.name %>">
260+
`)
261+
})
202262
})

0 commit comments

Comments
 (0)