Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion crates/tsv_svelte/src/printer/nodes/fragment_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,9 +726,17 @@ impl<'a> Printer<'a> {
/// split, since breaking there would inject a rendered space.) So an inline sibling isolated
/// by authored newlines flows back onto the content line, converging those authorings.
///
/// Four neighbours are excluded, none of them a mere spelling difference:
/// Five neighbours are excluded, none of them a mere spelling difference:
/// - a **comment**, whose authored position is authorship — folding one into a text fill
/// would relocate it across a semantic boundary (§Comment Position Philosophy);
/// - a **`<br>`**, which IS a rendered line break, so the source newline beside it mirrors
/// the render rather than spelling a space — the one licence this rule runs on. It is
/// line-owning exactly as a comment is: it BOUNDS the run without sterilizing it (the runs
/// on either side still flow), and its space spelling stays as authored (`text <br /> text`
/// is never forced open). Prettier holds it too, so this is parity
/// (`elements/void_br_newline`; the bounding control is in
/// `inline_sibling_newline_flow_prettier_divergence`). The other void elements (`<img>`,
/// `<input>`, …) render inline and flow like any inline element;
/// - a **block element**, which owns its own line via `handle_block_child`;
/// - a **blank line** (2+ newlines), a Tier-2 authoring signal, screened by the callers;
/// - a **control-flow block** (`{#if}` / `{#each}` / `{#key}` / `{#await}` / `{#snippet}`),
Expand Down Expand Up @@ -766,6 +774,10 @@ impl<'a> Printer<'a> {
FragmentNode::ExpressionTag(_)
| FragmentNode::RenderTag(_)
| FragmentNode::HtmlTag(_) => true,
// A `<br>` is a rendered line break — line-owning, like a comment (the doc comment's
// second exclusion). Keyed on the name alone: `TagFacts::is_void` covers every void
// element, and the others render inline.
FragmentNode::Element(el) if el.name(self.source) == "br" => false,
// An inline element/component flows; a block one owns its line.
FragmentNode::Element(_) | FragmentNode::SpecialElement(_) => {
!self.is_block_element_node(node)
Expand Down
2 changes: 1 addition & 1 deletion docs/conformance_prettier_svelte.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ Note this is **orthogonal to whether the element lays out multiline at all**, wh

### What the rule does not reshape, and where convergence stops

Four things are **not** reshaped, because none is a mere spelling difference. A **comment** keeps its authored line (its position is authorship — folding one into a fill would relocate it across a semantic boundary, see [§Comment Position Philosophy](./conformance_prettier.md#comment-position-philosophy)); a **blank line** still breaks (a Tier-2 authoring signal independent of render); a **block sibling** still takes its own line (blocks merely partition a fragment into inline runs, each of which flows on its own); and a **control-flow block** (`{#if}` / `{#each}` / `{#key}` / `{#await}` / `{#snippet}`) keeps its own line too — for `{#snippet}` that is unconditional (it is a declaration and takes its own line, [see below](#svelte-inline-content-block-style)), so only the four rendering heads remain in question here. The reason for that last one is neither "a block's width is not fixed" (a breaking `{expr}` tag expands mid-run too — `{f(⏎…⏎)}text4` — so width separates nothing) nor a bare appeal to a fixture. It is that **a flowed block has no way to pay an overflow except by tearing itself open.** An inline element that cannot fit drops to its own line *whole*, tags intact — that is what `break_before_wide_flow` buys, and it is what makes flowing safe for elements. A **spaced** block follower has that escape too (the spaced half of `break_before_wide_flow` admits control-flow blocks — [§Svelte: Blocks, "Multiline block head after spaced text"](#svelte-blocks)), but a **glued** one still does not: a glued block detaches by its own layout, so its weld survives only in the source (measuring it as a welded unit is a two-pass hazard), and the only break available there is the block's own head↔body seam — the body node lands on its own line and the flowed sibling text welds to the tail (`{#key key}⏎text6⏎{/key}text7`). The visible cost is that identical constructs then render differently by horizontal accident — in a run of five blocks, the two that happen to straddle the width boundary expand while their siblings stay inline. So the exclusion is a **consequence of a missing mechanism, not a property of blocks**: admitting them to the flow is gated on the remaining glued half of that whole-unit drop, not on re-litigating this predicate. Measured against the fixture suite, admitting blocks as-is converges ~39 more `authoring_audit` sites — the yield is real, which is exactly why the bar is the layout, not the count. A breaking `{expr}` tag, by contrast, expands *inside its own expression* (the call's argument list) and leaves both of its outer adjacencies untouched, so it flows without this hazard — a settled choice rather than a rough edge.
Five things are **not** reshaped, because none is a mere spelling difference. A **comment** keeps its authored line (its position is authorship — folding one into a fill would relocate it across a semantic boundary, see [§Comment Position Philosophy](./conformance_prettier.md#comment-position-philosophy)); a **`<br />`** keeps its authored line too — it *is* a rendered line break, so the source newline beside it mirrors the render rather than spelling a space, which is the one licence the flow rule runs on; it is line-owning exactly as a comment is (it bounds the run without sterilizing it, and its space spelling `text <br /> text` is never forced open), and prettier holds it as well, so this is agreement rather than divergence — [void_br_newline](../tests/fixtures/svelte/elements/void_br_newline/) pins every authoring and [inline_sibling_newline_flow](../tests/fixtures/svelte/elements/inline_sibling_newline_flow_prettier_divergence/)'s `<br />` control carries the bounding-not-sterilizing half beside the comment's; a **blank line** still breaks (a Tier-2 authoring signal independent of render); a **block sibling** still takes its own line (blocks merely partition a fragment into inline runs, each of which flows on its own); and a **control-flow block** (`{#if}` / `{#each}` / `{#key}` / `{#await}` / `{#snippet}`) keeps its own line too — for `{#snippet}` that is unconditional (it is a declaration and takes its own line, [see below](#svelte-inline-content-block-style)), so only the four rendering heads remain in question here. The reason for that last one is neither "a block's width is not fixed" (a breaking `{expr}` tag expands mid-run too — `{f(⏎…⏎)}text4` — so width separates nothing) nor a bare appeal to a fixture. It is that **a flowed block has no way to pay an overflow except by tearing itself open.** An inline element that cannot fit drops to its own line *whole*, tags intact — that is what `break_before_wide_flow` buys, and it is what makes flowing safe for elements. A **spaced** block follower has that escape too (the spaced half of `break_before_wide_flow` admits control-flow blocks — [§Svelte: Blocks, "Multiline block head after spaced text"](#svelte-blocks)), but a **glued** one still does not: a glued block detaches by its own layout, so its weld survives only in the source (measuring it as a welded unit is a two-pass hazard), and the only break available there is the block's own head↔body seam — the body node lands on its own line and the flowed sibling text welds to the tail (`{#key key}⏎text6⏎{/key}text7`). The visible cost is that identical constructs then render differently by horizontal accident — in a run of five blocks, the two that happen to straddle the width boundary expand while their siblings stay inline. So the exclusion is a **consequence of a missing mechanism, not a property of blocks**: admitting them to the flow is gated on the remaining glued half of that whole-unit drop, not on re-litigating this predicate. Measured against the fixture suite, admitting blocks as-is converges ~39 more `authoring_audit` sites — the yield is real, which is exactly why the bar is the layout, not the count. A breaking `{expr}` tag, by contrast, expands *inside its own expression* (the call's argument list) and leaves both of its outer adjacencies untouched, so it flows without this hazard — a settled choice rather than a rough edge.

**The comment exclusion is held on each SIDE independently, and it is where this section's convergence deliberately stops rather than a divergence from prettier.** "Keeps its authored line" is the symmetric reading, and it is the weaker one: the newline *after* a comment holds the following run off the comment's line exactly as the newline *before* it holds the comment off the preceding run's, so a one-sided authoring is a fixed point on that side alone — `text1 <!-- c -->⏎text2` and `text1⏎<!-- c --> text2` both stay, at any width, beside the both-sided form — and beside the form with a newline on neither side, which stays hugged, because the rule HOLDS an authored line rather than FORCING one around a comment (that fourth authoring is the pair's null control: it varies the same dimension, so without it an implementation that always broke both sides would satisfy the other three). Prettier holds all four too, so there is nothing to catalog against it here; the exclusion's whole cost is that these authorings do not converge with each other, which is the price [§Comment Position Philosophy](./conformance_prettier.md#comment-position-philosophy) is already paid for. ⚠️ **It is comment-specific, and the discriminating control has to hold the SITE fixed** — an element or a tag in the identical position flows (`text1 <span>a</span>⏎text2` collapses). Two *other* gates in this rule suppress flow for every sibling kind at once (the prose gate below, and the reflowable-fill suppression the paragraph after it, which closes this rule by making the element multiline for a reason it does not read), so an element control run in a shape either one already catches keeps its lines regardless of what sits there, and reads back as "the newline rule is not about comments" when it is. — [inline_separator_comment_newline](../tests/fixtures/svelte/elements/inline_separator_comment_newline/) pins all four comment authorings, and is deliberately **not** a divergence fixture: prettier holds each one too, so there is nothing for an `output_prettier` to claim. The element twins of the one-sided pair are [inline_sibling_newline_flow](../tests/fixtures/svelte/elements/inline_sibling_newline_flow_prettier_divergence/)'s `prettier_variant_newline_after` / `prettier_variant_newline_before`, which converge on its input — that pair beside these four is the whole control. ⚠️ **The exclusion is scoped to the comment's OWN two boundaries: it BOUNDS the run, it does not STERILIZE one.** The runs a comment separates keep flowing on their own — `text1 <span>a</span> text2⏎<!-- c -->⏎text3 <span>b</span> text4` converges from every isolated authoring of those spans, with only the comment's two newlines held. Nothing distinguishes that from the wider reading ("a run holding a comment keeps every line") unless a control puts a flowing run *beside* the comment and re-authors it: a control whose run is already hugged in every file is a fixed point under either rule and cannot separate them, which is why [inline_sibling_newline_flow](../tests/fixtures/svelte/elements/inline_sibling_newline_flow_prettier_divergence/)'s comment-boundary control is re-authored in all three `prettier_variant_*` files rather than held. The same is true of every other run-bounding sibling (that fixture's block-sibling control carries the parallel pin), so the property belongs to run boundaries in general and not to comments — what is comment-specific is only the *holding*. The practical consequence is that a comment tsv has broken onto its own line for **width** ([fill_break_before_comment_spaced_long](../tests/fixtures/svelte/elements/fill_break_before_comment_spaced_long_prettier_divergence/)) reaches a second fixed point when the run after it was authored on the next line — the same document, differing only in whether that run shares the comment's line.

Expand Down
Loading