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
33 changes: 27 additions & 6 deletions crates/tsv_svelte/src/printer/nodes/fragment_text_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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 <div>block1</div>`) 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
Expand All @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions docs/conformance_prettier.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ The fixture-pinned `◆prettier_bug` cases — where Prettier produces output th
- `<svelte:element this={'x'}>` — 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/)
- `<svelte:element class="a b">` — 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 <span>x</span>` → `text1 text2<span>x</span>`), 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 `<pre>` / `<textarea>` attribute list — ejects the comment out of the element (`</pre> // c`, `</textarea // c⏎>`), 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/)
Expand Down
9 changes: 5 additions & 4 deletions docs/conformance_prettier_svelte.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions docs/fixture_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,19 @@ deno task fixtures:update

When `output_prettier.*` exists, prettier baseline validation (F3) is skipped — F2 checks the file matches prettier instead.

**`output_prettier.*` is a claim about prettier only.** F2 pins prettier's first pass on the
input and F4/F4b its chain to a fixed point; nothing grades what *tsv* does with that form.
tsv's answer is one of three classes — it **holds** prettier's form, **normalizes** it back to
input, or rewrites it to a **third** stable form — and only two are expressible today, each by a
byte-copy of `output_prettier.*` under a variant name: a `variant_*` copy claims *holds* (N9), a
`divergent_variant_*` copy claims *third form* (N11). *Normalizes* has no marker: a
`prettier_variant_*` copy is refused as redundant (`RedundantPrettierVariantMatchesOutputPrettier`),
so that claim lives in README prose only. A copy also drifts silently — `fixtures:update:formatted`
regenerates `output_prettier.*` but never a copy of it — so prefer stating the class in the README
and reading it live with `deno task fixtures:audit -v <pattern>`, whose `output_prettier.*` row
(`ours -> self` / `ours -> input` / a novel form) is the grade. A validator-computed grade of that
row is the intended replacement for the copies.

---

### Svelte Parser Divergence
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# block_after_spaced_text_long_prettier_divergence

The width boundary of [block_after_spaced_text](../block_after_spaced_text_prettier_divergence/):
a block element after a first-child spaced text takes its own line at **every** width. At exactly
100 chars the hugged line would fit, and prettier keeps it (`prettier_variant_hugged.svelte`);
tsv still breaks before the block. At 101 the hugged line overflows: tsv breaks before the block,
which then collapses inline on its fresh line (89 chars), where prettier opens the block
block-style with its head left dangling on the text line (`prettier_variant_dangled.svelte`) —
tsv normalizes that form back to `input.svelte` too, the block's content reflowing onto its
fresh line. `unformatted_ours_one_line.svelte` authors both cases on one line; tsv normalizes it
to `input.svelte` in one pass and prettier lands on the dangled form.

## Reason

Design choice — see the sibling fixture's README: the block's own break separates it from the
text, so no width makes the hug the right layout, and a multiline unit's head never ends a
content line.

See
[conformance_prettier_svelte.md §Svelte: Inline content block-style](../../../../../docs/conformance_prettier_svelte.md#svelte-inline-content-block-style).

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- width is not the axis: at 100 the hugged line would fit exactly, and the block still takes
its own line -->
<div>
text1 text2
<div>eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee</div>
</div>

<!-- at 101 the hugged line overflows; the block collapses inline on its fresh line (89 chars)
rather than opening block-style with its head left on the text line -->
<div>
text1 text2
<div>eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- width is not the axis: at 100 the hugged line would fit exactly, and the block still takes
its own line -->
<div>
text1 text2 <div>eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee</div>
</div>

<!-- at 101 the hugged line overflows; the block collapses inline on its fresh line (89 chars)
rather than opening block-style with its head left on the text line -->
<div>
text1 text2 <div>
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!-- width is not the axis: at 100 the hugged line would fit exactly, and the block still takes
its own line -->
<div>
text1 text2 <div>eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee</div>
</div>

<!-- at 101 the hugged line overflows; the block collapses inline on its fresh line (89 chars)
rather than opening block-style with its head left on the text line -->
<div>
text1 text2
<div>eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- width is not the axis: at 100 the hugged line would fit exactly, and the block still takes
its own line -->
<div>
text1 text2 <div>eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee</div>
</div>

<!-- at 101 the hugged line overflows; the block collapses inline on its fresh line (89 chars)
rather than opening block-style with its head left on the text line -->
<div>
text1 text2 <div>eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# block_after_spaced_text_prettier_divergence

**A block element after spaced text takes its own line.** A block sibling partitions the
inline run and its own break separates the two, so the space before it is spent on that break
in every fragment family — a block element, a section, an inline element, a component, a block
body and the root — and whether the block renders inline (`text1 text2⏎<div>block1</div>`) or
multiline (its head never ends the text line). Prettier keeps the block hugged to the text when
the text is the fragment's **first child** (`text1 text2 <div>block1</div>`,
`prettier_variant_space.svelte`) and breaks it after any other predecessor; tsv normalizes the
space spelling to `input.svelte` in one pass.

The controls agree with prettier: a text that is *not* the fragment's first child (after an
element, after a tag) breaks before the block, an element predecessor does, and a **glued**
boundary is split the same way (`unformatted_glued.svelte` — a block boundary is render-free,
so both formatters normalize it to input).

## Reason

Design choice — the last row-dependent cell of the space rule. A space between siblings is
decided by the follower's kind alone, and a block-element follower breaks it after an element, a
component, a tag, a comment, a `<br />` and a control-flow block — and after a text that is not
the fragment's first child. Only a **first-child** text kept the block on its line, and only in
the space spelling: the glued and newline spellings of the same boundary already break. That is
a spelling and a position selecting a layout, not a policy: prettier's hug is its
`printChildren` returning early for a first child before the block-follower trim runs, and tsv
had mirrored it. The multiline block is the sharper case — a multiline unit's head never ends a
content line for an inline element, a component or a control-flow block, and the block element
was the one unit kind still dangling its head there.

See
[conformance_prettier_svelte.md §Svelte: Inline content block-style](../../../../../docs/conformance_prettier_svelte.md#svelte-inline-content-block-style).
Loading
Loading