diff --git a/crates/tsv_svelte/src/printer/nodes/fragment_text_doc.rs b/crates/tsv_svelte/src/printer/nodes/fragment_text_doc.rs index 1bb1c20d3..2834bf81a 100644 --- a/crates/tsv_svelte/src/printer/nodes/fragment_text_doc.rs +++ b/crates/tsv_svelte/src/printer/nodes/fragment_text_doc.rs @@ -613,6 +613,11 @@ impl<'a> Printer<'a> { // of `break_before_wide_flow` below (never folded into `next_is_flow_or_tag`, whose // other readers key the leading-side arms and the inline-sibling wrap on the flow set). let next_is_rendering_block = next_node.is_some_and(is_control_flow_block); + // Whether the next sibling is a BLOCK element. Its own `handle_block_child` supplies the + // break in front of it, so a trailing space before it is spent on that break at EVERY + // position of this text — the deferred-trim arm below — never kept as the fill's own + // `line`. See that arm. + let next_is_block_el = next_node.is_some_and(|n| self.is_block_element_node(n)); // Whether the next sibling is a flowing inline element OR component (the // Fill-idempotency boundary). Text before such a node ends its fill with a trailing // `line` so the boundary breaks per width inside the fill (keeping the run idempotent), @@ -994,7 +999,23 @@ impl<'a> Printer<'a> { trim_right = true; trailing_hardlines = if trailing_ws_newlines >= 2 { 2 } else { 1 }; } else if has_trailing_ws && !is_last && position.next_is_inline() { - if is_first || next_is_flow_or_tag { + if next_is_block_el { + // BLOCK element follower: a block sibling takes its own line, so the space is + // spent on the break its `handle_block_child` emits (`break_before` reads the + // deferred trim), at every position of this text alike. A FIRST child used to + // fall through to the `trailing_line` arm below instead, keeping the block hugged + // on the text line (`text1 text2
block1
`) where the same text one + // sibling later broke before it — prettier-plugin-svelte's `handleTextChild` + // returns early for its first child before its block-follower trim, and tsv had + // mirrored the artifact. Of that boundary's three spellings only the first-child + // space hugged (the glued and newline spellings already break), so a spelling + // and a position were selecting the layout; and a block that renders multiline + // dangled its head on the text line, the one unit kind without the whole-unit + // drop. Cataloged: conformance_prettier_svelte.md §Svelte: Inline content + // block-style, `elements/block_after_spaced_text_prettier_divergence`. + trim_right = true; + *handle_whitespace_of_prev_text = true; + } else if is_first || next_is_flow_or_tag { // One boundary, one answer: a first child, and a middle child before a tag or // before a flowing inline element / component, all end the fill with a trailing // `line`, so the boundary breaks per width INSIDE the fill — which is what keeps @@ -1020,11 +1041,11 @@ impl<'a> Printer<'a> { trailing_line = true; // A first child's leading boundary is the parent's, already trimmed. trim_right = !is_first; - } else if !is_first { - // Remaining inline callers: the follower is `is_inline_content` but neither - // `is_inline_el_or_comp` nor a tag, which leaves exactly a BLOCK element. Wrap it - // with `group([line, element])`. (Not a comment — see the arm below, which is - // where a comment follower actually lands.) + } else { + // Remaining inline callers: the follower is `is_inline_content` but neither a + // block element, `is_inline_el_or_comp`, nor a tag. Wrap it with + // `group([line, element])`. (Not a comment — see the arm below, which is where a + // comment follower actually lands.) trim_right = true; *handle_whitespace_of_prev_text = true; } diff --git a/docs/conformance_prettier.md b/docs/conformance_prettier.md index 50a73f973..4a4a56085 100644 --- a/docs/conformance_prettier.md +++ b/docs/conformance_prettier.md @@ -44,6 +44,7 @@ The fixture-pinned `◆prettier_bug` cases — where Prettier produces output th - `` — ignores `singleQuote` and skips escaping → invalid output (`this={"a"b"}`) — [svelte_element_this_string](../tests/fixtures/svelte/special_elements/svelte_element_this_string_prettier_divergence/) - `` — fails to collapse repeated whitespace — [svelte_element_class_whitespace](../tests/fixtures/svelte/special_elements/svelte_element_class_whitespace_prettier_divergence/) - Space after block element — strands a leading space, non-idempotent — [space_after_block](../tests/fixtures/svelte/elements/space_after_block_prettier_divergence/) +- Space before an inline element after block-then-text — deletes the rendered space (`text1 text2 x` → `text1 text2x`), a content change; idempotent on its own output, so nothing reveals it — [block_text_inline_space](../tests/fixtures/svelte/elements/block_text_inline_space_prettier_divergence/) - `//` comment in a `
` / ``), so it renders as page text or is dropped on the next pass; non-idempotent either way — [ws_sensitive_attr_comment_line](../tests/fixtures/svelte/elements/ws_sensitive_attr_comment_line_prettier_divergence/)
 - Constrained `infer … extends` operand parens — strips required parens → output fails to re-parse — [constrained_extends_parens](../tests/fixtures/typescript/types/infer/constrained_extends_parens_prettier_divergence/)
 - Negative literal type sign comment (`-/* c */ 1`) — *adds* parens to hold the comment (`-(/* c */ 1)`), but no such type exists: `-` is a negative literal type only when the next token is a numeric/bigint literal → output fails to re-parse — [negative_literal_sign_comment](../tests/fixtures/typescript/types/negative_literal_sign_comment_prettier_divergence/)
diff --git a/docs/conformance_prettier_svelte.md b/docs/conformance_prettier_svelte.md
index 5e4fb52b4..31e503f36 100644
--- a/docs/conformance_prettier_svelte.md
+++ b/docs/conformance_prettier_svelte.md
@@ -64,6 +64,7 @@ the parser keeps the wider set deliberately. See
 - svelte:element `this` — ◆prettier_bug — [svelte_element_this_string](../tests/fixtures/svelte/special_elements/svelte_element_this_string_prettier_divergence/). Anything between the `{` and a string literal (a paren, or a leading comment) fails prettier's `{`-precedes-literal check and collapses the binding to the plain attribute `this="x"` — which for a comment is also ◆content_preservation, since the collapsed form has nowhere to put it and prettier drops it.
 - svelte:element class ws — ◆prettier_bug — [svelte_element_class_whitespace](../tests/fixtures/svelte/special_elements/svelte_element_class_whitespace_prettier_divergence/)
 - Space after block element — ◆prettier_bug — [space_after_block](../tests/fixtures/svelte/elements/space_after_block_prettier_divergence/)
+- Space before an inline element after block-then-text — ◆prettier_bug ◆content_preservation — [block_text_inline_space](../tests/fixtures/svelte/elements/block_text_inline_space_prettier_divergence/) (`
b
⏎text1 text2 x` → prettier's `text1 text2x`, which renders `text1 text2x`: its `handleTextChild` trims the text's trailing space before an inline element but, with a block-element predecessor, sets the flag that would re-emit it to false, so nothing prints it — from either authoring, for a one-word text, and whether or not the run is the fragment's first; a tag or a component follower keeps the space under both. tsv keeps it as the element's per-width wrap, as everywhere else) - Nested foreign `