Fix jsx template stripping the space between adjacent text lines (#359)#364
Fix jsx template stripping the space between adjacent text lines (#359)#364brainkim wants to merge 2 commits into
Conversation
The children tokenizer trimmed trailing whitespace off a text run before every newline, treating a text–text break the same as an element-adjacent one. Standard JSX collapses an interior text–text newline to a single space while stripping whitespace next to an element, so multi-line prose written with the `jsx` template tag lost the spaces at its line breaks (`<p>alpha⏎beta</p>` rendered `alphabeta`). Collapse the break to a single space only when the newline follows non-empty text and the next significant character in the span is not `<` (i.e. the next token is text). Element-adjacent stripping, escaped newlines, blank lines, and indentation are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a test block covering the cases verified against esbuild and tsc: interior spaces preserved (not collapsed), a newline before an expression stripped with no space, first-line leading whitespace preserved, and whitespace-only text between elements kept on one line but removed across a newline. With #359's fix, Crank matches both reference transpilers on all of these. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Whitespace parity investigation (for the record)Before merging I wanted to know whether the Result: parity on every in-content case
These are locked in by the Two findings worth recording
The one intentional differenceTemplate-edge whitespace. Crank trims leading/trailing whitespace at the very start and end of the whole template ( ⏎` The escaped-newline Bottom line: #359's fix brings the |
Fixes #359.
The bug
The
jsxtagged template collapses a newline between two text runs to nothing, where standard JSX collapses it to a single space. Multi-line prose loses the spaces at its line breaks:The fix
In
src/jsx-tag.tsthe children tokenizer trimmed trailing whitespace off a text run before every newline, treating a text–text break the same as an element-adjacent one. When a newline follows non-empty text and the next significant character in the span is not<(the next token is text, not an element), collapse the break to a single space instead of stripping it. Everything else is unchanged.Whitespace parity (verified against esbuild + tsc)
I checked Crank's output after this fix against both transpilers the issue cites. They agree, and Crank now matches them on every in-content case:
alpha⏎beta(text–text)alpha beta✅alpha betaalpha⏎<b/>/<b/>⏎beta(element-adjacent)a b(interior spaces)a b✅a b(not collapsed)a⏎{X}/{X}⏎b(expression-adjacent)Hello⏎World(first-line leading)Hello World✅Hello Worlda⇥b(tab in text)a⇥b✅a⇥b(both preserve tabs)<b/>···<i/>(ws-only, one line)<b/>⏎<i/>(ws-only, across newline)The one intentional difference is template-edge whitespace: Crank trims leading/trailing whitespace at the very start and end of the whole template (e.g.
jsx\⏎⏎`
), where JSX-in-a-fragment would preserve it. This is by design — the template body has no enclosing element boundary, and authors universally pad it with newlines/indentation for readability — and is covered by the existingtop-level strings/newlines and whitespace` tests.Tests
Added a
#359regression test and a parity test block intest/jsx-tag.tscovering the table above. Full suite passes;tsc --noEmit, ESLint, and Prettier clean.🤖 Generated with Claude Code