diff --git a/crates/tsv_svelte/src/printer/nodes/element_analysis.rs b/crates/tsv_svelte/src/printer/nodes/element_analysis.rs index 58c0c5aa..eff19222 100644 --- a/crates/tsv_svelte/src/printer/nodes/element_analysis.rs +++ b/crates/tsv_svelte/src/printer/nodes/element_analysis.rs @@ -229,7 +229,7 @@ impl<'a> Printer<'a> { } // Check for newlines in content between first and last non-whitespace nodes - nodes[first..=last].iter().enumerate().any(|(i, n)| { + nodes[first..=last].iter().any(|n| { let FragmentNode::Text(t) = n else { return false; }; @@ -242,17 +242,22 @@ impl<'a> Printer<'a> { // Inline, whitespace-only: newlines are separators t.has_newline() } else { - // Inline, text with content: exclude boundary (ASCII) whitespace. - // A non-breaking space is content, so trim_ascii keeps it attached. - let is_first_content = i == 0; - let is_last_content = i == last - first; - let check_str = match (is_first_content, is_last_content) { - (true, true) => raw.trim_ascii(), - (true, false) => raw.trim_ascii_start(), - (false, true) => raw.trim_ascii_end(), - (false, false) => raw, - }; - check_str.contains('\n') + // Inline, text with content: exclude the boundary (ASCII) whitespace runs on + // BOTH edges, whatever the node's position. A non-breaking space is content, so + // trim_ascii keeps it attached. + // + // The edge run is a *separator* between this text and its neighbour, and the + // fill owns it either way — it reflows to a space when the run fits and to a + // break when it does not. So its spelling is not the element-expansion signal; + // only a newline strictly INSIDE the text's own content is. Trimming just the + // fragment-edge sides (the old `is_first_content`/`is_last_content` match) left a + // middle text's separator run counted, which made `a b,⏎c + // ` report SourceBreaks: the element went block-style on pass 1, the + // fill then reflowed that very newline away, and pass 2 — seeing no newline left — + // collapsed it inline. Two mechanisms reading one newline and answering + // differently, the same class [`MultilineCause`] closed at the separator-flow site + // (conformance_prettier.md §Svelte: Inline content block-style). + raw.trim_ascii().contains('\n') } }) } diff --git a/crates/tsv_svelte/src/printer/nodes/fragment_doc.rs b/crates/tsv_svelte/src/printer/nodes/fragment_doc.rs index 007796fb..4093ac74 100644 --- a/crates/tsv_svelte/src/printer/nodes/fragment_doc.rs +++ b/crates/tsv_svelte/src/printer/nodes/fragment_doc.rs @@ -813,11 +813,23 @@ impl<'a> Printer<'a> { // Last child after breaking element (e.g. multiline attrs): // skip wrapping because group([breaking_element, line()]) forces // line() to break too, incorrectly separating closing tag from text. - // Note: non-last text after a breaking tag (prev_is_tag && !is_last - // && prev_will_break) also falls through without action — group() - // would force line() to break, and leading_line is only for - // non-breaking continuation. The text's leading ws handles spacing. - } else if !prev_will_break { + // That forced break is exactly what a TERMINAL tail must not take — it hugs the + // intact closing tag per the author's space (`inline_wide_content_trailing_long`). + // Note: non-last text after a breaking *tag* (`prev_is_tag && !is_last && + // prev_will_break`) still falls through without action — group() would force + // line() to break, and leading_line is only for non-breaking continuation. The + // text's leading ws handles spacing. + } else if !prev_will_break || !prev_is_tag { + // The second disjunct is the NON-TERMINAL guard. A text run followed by another + // flowing element must keep its own line — hugging it onto the closing tag shifts + // where that element lands, which feeds back into the fit decision + // (`inline_wide_content_text_sibling_long` is the guard, and its README the + // reasoning). The wrap below is what produces that own line, so a *breaking* + // previous element must reach it too: `prev_will_break` says the element already + // carries a hard break, and skipping the wrap there left the boundary unhandled — + // the leading run then rode `sibling_newline_flows`' space arm and the text hugged. + // Reachable only since that flow rule landed: before it a leading newline pinned a + // hardline, so the fall-through happened to render as the own line anyway. trim_left = true; add_leading_space = false; // line() handles the space // Pop the last doc (the inline element) and rejoin it with the trailing text. diff --git a/docs/conformance_prettier.md b/docs/conformance_prettier.md index 962151c9..cc0d4904 100644 --- a/docs/conformance_prettier.md +++ b/docs/conformance_prettier.md @@ -511,7 +511,7 @@ Note this is **orthogonal to whether the element lays out multiline at all**, wh 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](#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. 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 regressed 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. No such escape reaches a control-flow block, whose head and tail wrap a *fragment*: the only break available is at its own head↔body seam, so 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 is gated on giving a block the same whole-unit drop an element has (widening the `next_is_flow` / `break_before_wide_flow` coupling past `is_inline_el_or_comp`), 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 rule's boundary is the presence of a `fill` to reflow into, not the shape of the separator node.** Flowing means reflowing a run per width, and a run reflows only where there is prose for a `fill` to pack. So the rule reaches a whitespace-only separator standing *between two non-text siblings* — `text1⏎a` and `ab` converge alike — **when that separator's inline run holds a content text**, and not otherwise. A run of pure elements or tags (``) keeps its authored lines: with no prose its newlines are the only structure the author has, collapsing them packs independent siblings onto one line, and on a short run the collapse cascades into the parent element's own hug decision — an outright idempotency break (`` re-hugs on the second pass once its children fold). A run is bounded by whatever owns its own line — a block element, a control-flow block, a comment — and by an authored blank line, so a fragment's prose does not license flow in a neighbouring run that has none. Two mechanics keep this sound and are load-bearing. First, a flowing single newline takes the **space arm verbatim** rather than a parallel one: emitting a different collapsible form for the newline than for the space it claims to equal makes the formatter write a newline that its next pass re-reads as flowable and collapses. Second, the boundary must defer to the next sibling for a **tag** as well as an element — a bare `line` in the child list resolves all-or-nothing with the parent group, and a multiline fragment's parent group is always broken, so without that the one boundary owned by a content text's `fill` would flow while the rest of the same run hard-broke. That deferral is gated on the flow predicate and not on "next is a tag", because a plain authored space before a tag may have a **comment** on its other side, whose line is authorship. Note `authoring_audit` is blind to the tag half: both spellings already agreed there — on the hard-broken form — so it scores them as converging either way. — [inline_sibling_newline_flow](../tests/fixtures/svelte/elements/inline_sibling_newline_flow_prettier_divergence/) (an inline element, a component, an expression tag and a render tag, plus all five controls) and [expressions/angle_escaped](../tests/fixtures/svelte/expressions/angle_escaped_prettier_divergence/) (the tag-separator case, where prettier's plain `line` holds each tag on its authored line), and the shapes the rule reaches across the suite: [fill_text_inline_collapse](../tests/fixtures/svelte/elements/fill_text_inline_collapse_prettier_divergence/), [root_text_newline_inline](../tests/fixtures/svelte/elements/root_text_newline_inline_prettier_divergence/), [block_text_multiline_collapse](../tests/fixtures/svelte/elements/block_text_multiline_collapse_prettier_divergence/), [inline_attrs_multiline_content](../tests/fixtures/svelte/elements/inline_attrs_multiline_content_prettier_divergence/), [components/root_with_text](../tests/fixtures/svelte/components/root_with_text_prettier_divergence/). An element-separator run where both formatters agree once tsv flows it is pinned by `tests/fixtures/svelte/script/escapes` and `tests/fixtures/svelte/style/escapes`. +**The rule's boundary is the presence of a `fill` to reflow into, not the shape of the separator node.** Flowing means reflowing a run per width, and a run reflows only where there is prose for a `fill` to pack. So the rule reaches a whitespace-only separator standing *between two non-text siblings* — `text1⏎a` and `ab` converge alike — **when that separator's inline run holds a content text**, and not otherwise. A run of pure elements or tags (``) keeps its authored lines: with no prose its newlines are the only structure the author has, collapsing them packs independent siblings onto one line, and on a short run the collapse cascades into the parent element's own hug decision — an outright idempotency break (`` re-hugs on the second pass once its children fold). A run is bounded by whatever owns its own line — a block element, a control-flow block, a comment — and by an authored blank line, so a fragment's prose does not license flow in a neighbouring run that has none. Two mechanics keep this sound and are load-bearing. First, a flowing single newline takes the **space arm verbatim** rather than a parallel one: emitting a different collapsible form for the newline than for the space it claims to equal makes the formatter write a newline that its next pass re-reads as flowable and collapses. Second, the boundary must defer to the next sibling for a **tag** as well as an element — a bare `line` in the child list resolves all-or-nothing with the parent group, and a multiline fragment's parent group is always broken, so without that the one boundary owned by a content text's `fill` would flow while the rest of the same run hard-broke. That deferral is gated on the flow predicate and not on "next is a tag", because a plain authored space before a tag may have a **comment** on its other side, whose line is authorship. Note `authoring_audit` is blind to the tag half: both spellings already agreed there — on the hard-broken form — so it scores them as converging either way. — [inline_sibling_newline_flow](../tests/fixtures/svelte/elements/inline_sibling_newline_flow_prettier_divergence/) (an inline element, a component, an expression tag and a render tag, plus all five controls) and [expressions/angle_escaped](../tests/fixtures/svelte/expressions/angle_escaped_prettier_divergence/) (the tag-separator case, where prettier's plain `line` holds each tag on its authored line), and the shapes the rule reaches across the suite: [fill_text_inline_collapse](../tests/fixtures/svelte/elements/fill_text_inline_collapse_prettier_divergence/), [root_text_newline_inline](../tests/fixtures/svelte/elements/root_text_newline_inline_prettier_divergence/), [block_text_multiline_collapse](../tests/fixtures/svelte/elements/block_text_multiline_collapse_prettier_divergence/), [inline_attrs_multiline_content](../tests/fixtures/svelte/elements/inline_attrs_multiline_content_prettier_divergence/), [components/root_with_text](../tests/fixtures/svelte/components/root_with_text_prettier_divergence/). Where this rule and the render-free content boundary meet on one element, the two compose to a single form — the flowed content with the boundaries hugged — and must settle in **one pass**, since the flow and the collapse it enables are one decision ([inline_content_flow_collapse](../tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/), which pins prettier's two stable forms alongside it). An element-separator run where both formatters agree once tsv flows it is pinned by `tests/fixtures/svelte/script/escapes` and `tests/fixtures/svelte/style/escapes`. **The rule's second boundary is the enclosing element's own multiline cause.** Landing both spellings on one doc converges them only if the *arm* they land in is itself spelling-independent, and it is not when the enclosing element went multiline **because of** those very newlines. Collapsing the separator there deletes the break that chose the multiline arm, so the next pass takes the inline arm — whose separator before a tag is a bare `line`, all-or-nothing with the already-broken parent group — and splits the run apart again. The two spellings become each other's *output* rather than one fixed point: `⏎{a}⏎{b}x⏎` ⇄ `⏎{a} {b}x⏎`, forever. So the rule stands down exactly there — inside such an element the newline is not pure spelling, it is the sanctioned Tier-2 element-expansion signal — and holds wherever the multiline layout is **structural**: the root fragment, block bodies, and any element forced multiline by block children, an expanding control-flow block, or a whitespace-collapsing container. This is what makes the orthogonality claimed above real rather than aspirational; before it, the flow rule and the expansion signal read the same newline and answered differently. Pinned by [inline_content_spaced_tags_tail_long](../tests/fixtures/svelte/elements/inline_content_spaced_tags_tail_long/), whose overflowing `` holds a spaced tag pair with a trailing text run (the shape that makes the run prose) — one case each for a tag lead and an element lead, plus the 100-char control that stays inline and an `unformatted_shared_line` variant carrying the other spelling. diff --git a/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/README.md b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/README.md new file mode 100644 index 00000000..d3735cd5 --- /dev/null +++ b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/README.md @@ -0,0 +1,41 @@ +# inline_content_flow_collapse_prettier_divergence + +tsv: an inline element whose content is a run of inline siblings converges on the **fully inline** +form — the inter-sibling newline flows back onto the content line, the hugged boundaries are +preserved, and the element collapses. Prettier: holds a distinct stable form for each authoring. + +## Reason + +**Design choice — the flow rule, and the layout it feeds, decided together.** Two rules compose, +and both are already sanctioned: + +- **The inter-sibling newline flows.** Svelte 5 collapses an inter-sibling whitespace run to one + space, so a newline and a space between two siblings render identically and the spelling carries + no signal — see + [inline_sibling_newline_flow](../inline_sibling_newline_flow_prettier_divergence/). +- **The content boundary does not select the layout.** Boundary whitespace is render-free, so the + hugged, spaced and newline authorings of one document converge — see + [§Svelte: Inline content block-style](../../../../../docs/conformance_prettier.md#svelte-inline-content-block-style). + +Together they leave exactly one form: the flowed content, boundaries hugged. Prettier converges +neither axis, so it keeps a stable form for each authoring — the dangled form for the +inter-sibling newline, and the block-style form for the boundary newline +(`prettier_variant_boundary_newline`). + +What this fixture adds beyond those two is that the composition must settle in **one pass**. The +flow and the collapse it enables are one decision; deciding the layout against the pre-flow +content and the flow against the pre-collapse layout makes the first pass emit the block-style +form and only the second reach the inline one — so the formatter's own output is not a fixed +point (F1), and the authorings of one document land on two forms. + +## Cases + +An HTML inline element and a table cell (inline-classified, so it takes the same layout), each +in the converged inline form, with the inter-sibling-newline authoring as +`unformatted_ours_newline.svelte` (tsv normalizes it to `input`; prettier instead dangles the tag +delimiters around it, a form it keeps stable — pinned as `prettier_variant_dangle.svelte`) and the +boundary-newline authoring as `prettier_variant_boundary_newline.svelte` (prettier keeps it +stable; tsv normalizes it to `input`). + +See +[conformance_prettier.md §Svelte: Inline content block-style](../../../../../docs/conformance_prettier.md#svelte-inline-content-block-style). diff --git a/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/expected.json b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/expected.json new file mode 100644 index 00000000..47a6dbc3 --- /dev/null +++ b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/expected.json @@ -0,0 +1,448 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 733, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Comment", + "start": 0, + "end": 559, + "data": "\n\tAn inline element whose content is a run of inline siblings, authored with a newline BETWEEN\n\ttwo of them and the content boundaries hugged. The inter-sibling newline flows back onto the\n\tcontent line (Svelte 5 collapses the run to one space, so its spelling carries no signal), and\n\tthe hugged boundaries are preserved, so the element converges on the fully inline form. That\n\tmust settle in ONE pass: the flow and the layout it feeds are decided together, not on\n\tsuccessive passes. Covers an HTML inline element, a table cell (inline-classified).\n" + }, + { + "type": "Text", + "start": 559, + "end": 560, + "raw": "\n", + "data": "\n" + }, + { + "type": "RegularElement", + "start": 560, + "end": 647, + "name": "div", + "name_loc": { + "start": { + "line": 9, + "column": 1, + "character": 561 + }, + "end": { + "line": 9, + "column": 4, + "character": 564 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 565, + "end": 567, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "RegularElement", + "start": 567, + "end": 640, + "name": "span", + "name_loc": { + "start": { + "line": 10, + "column": 2, + "character": 568 + }, + "end": { + "line": 10, + "column": 6, + "character": 572 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "RegularElement", + "start": 573, + "end": 587, + "name": "code", + "name_loc": { + "start": { + "line": 10, + "column": 8, + "character": 574 + }, + "end": { + "line": 10, + "column": 12, + "character": 578 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 579, + "end": 580, + "raw": "a", + "data": "a" + } + ] + } + }, + { + "type": "Text", + "start": 587, + "end": 588, + "raw": " ", + "data": " " + }, + { + "type": "RegularElement", + "start": 588, + "end": 602, + "name": "code", + "name_loc": { + "start": { + "line": 10, + "column": 23, + "character": 589 + }, + "end": { + "line": 10, + "column": 27, + "character": 593 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 594, + "end": 595, + "raw": "b", + "data": "b" + } + ] + } + }, + { + "type": "Text", + "start": 602, + "end": 604, + "raw": ", ", + "data": ", " + }, + { + "type": "RegularElement", + "start": 604, + "end": 618, + "name": "code", + "name_loc": { + "start": { + "line": 10, + "column": 39, + "character": 605 + }, + "end": { + "line": 10, + "column": 43, + "character": 609 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 610, + "end": 611, + "raw": "c", + "data": "c" + } + ] + } + }, + { + "type": "Text", + "start": 618, + "end": 619, + "raw": " ", + "data": " " + }, + { + "type": "RegularElement", + "start": 619, + "end": 633, + "name": "code", + "name_loc": { + "start": { + "line": 10, + "column": 54, + "character": 620 + }, + "end": { + "line": 10, + "column": 58, + "character": 624 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 625, + "end": 626, + "raw": "d", + "data": "d" + } + ] + } + } + ] + } + }, + { + "type": "Text", + "start": 640, + "end": 641, + "raw": "\n", + "data": "\n" + } + ] + } + }, + { + "type": "Text", + "start": 647, + "end": 649, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "RegularElement", + "start": 649, + "end": 732, + "name": "div", + "name_loc": { + "start": { + "line": 13, + "column": 1, + "character": 650 + }, + "end": { + "line": 13, + "column": 4, + "character": 653 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 654, + "end": 656, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "RegularElement", + "start": 656, + "end": 725, + "name": "td", + "name_loc": { + "start": { + "line": 14, + "column": 2, + "character": 657 + }, + "end": { + "line": 14, + "column": 4, + "character": 659 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "RegularElement", + "start": 660, + "end": 674, + "name": "code", + "name_loc": { + "start": { + "line": 14, + "column": 6, + "character": 661 + }, + "end": { + "line": 14, + "column": 10, + "character": 665 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 666, + "end": 667, + "raw": "a", + "data": "a" + } + ] + } + }, + { + "type": "Text", + "start": 674, + "end": 675, + "raw": " ", + "data": " " + }, + { + "type": "RegularElement", + "start": 675, + "end": 689, + "name": "code", + "name_loc": { + "start": { + "line": 14, + "column": 21, + "character": 676 + }, + "end": { + "line": 14, + "column": 25, + "character": 680 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 681, + "end": 682, + "raw": "b", + "data": "b" + } + ] + } + }, + { + "type": "Text", + "start": 689, + "end": 691, + "raw": ", ", + "data": ", " + }, + { + "type": "RegularElement", + "start": 691, + "end": 705, + "name": "code", + "name_loc": { + "start": { + "line": 14, + "column": 37, + "character": 692 + }, + "end": { + "line": 14, + "column": 41, + "character": 696 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 697, + "end": 698, + "raw": "c", + "data": "c" + } + ] + } + }, + { + "type": "Text", + "start": 705, + "end": 706, + "raw": " ", + "data": " " + }, + { + "type": "RegularElement", + "start": 706, + "end": 720, + "name": "code", + "name_loc": { + "start": { + "line": 14, + "column": 52, + "character": 707 + }, + "end": { + "line": 14, + "column": 56, + "character": 711 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 712, + "end": 713, + "raw": "d", + "data": "d" + } + ] + } + } + ] + } + }, + { + "type": "Text", + "start": 725, + "end": 726, + "raw": "\n", + "data": "\n" + } + ] + } + } + ] + }, + "options": null, + "comments": [] +} diff --git a/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/input.svelte b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/input.svelte new file mode 100644 index 00000000..66f43f62 --- /dev/null +++ b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/input.svelte @@ -0,0 +1,15 @@ + +
+ a b, c d +
+ +
+ a b, c d +
diff --git a/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/prettier_variant_boundary_newline.svelte b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/prettier_variant_boundary_newline.svelte new file mode 100644 index 00000000..e9ee4c8c --- /dev/null +++ b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/prettier_variant_boundary_newline.svelte @@ -0,0 +1,19 @@ + +
+ + a b, c d + +
+ +
+ + a b, c d + +
diff --git a/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/prettier_variant_dangle.svelte b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/prettier_variant_dangle.svelte new file mode 100644 index 00000000..d348df92 --- /dev/null +++ b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/prettier_variant_dangle.svelte @@ -0,0 +1,21 @@ + +
+ a b, + c d +
+ +
+ a b, + c d +
diff --git a/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/unformatted_ours_newline.svelte b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/unformatted_ours_newline.svelte new file mode 100644 index 00000000..3dd6341d --- /dev/null +++ b/tests/fixtures/svelte/elements/inline_content_flow_collapse_prettier_divergence/unformatted_ours_newline.svelte @@ -0,0 +1,17 @@ + +
+ a b, + c d +
+ +
+ a b, + c d +
diff --git a/tests/fixtures/svelte/elements/inline_wide_element_content_tail_long/expected.json b/tests/fixtures/svelte/elements/inline_wide_element_content_tail_long/expected.json new file mode 100644 index 00000000..5bcb7ecf --- /dev/null +++ b/tests/fixtures/svelte/elements/inline_wide_element_content_tail_long/expected.json @@ -0,0 +1,392 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 1083, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Comment", + "start": 0, + "end": 589, + "data": "\n\tA wide inline element whose content is a nested inline ELEMENT (not text), followed by a\n\tNON-TERMINAL text run — another inline element follows it. The element lays out block-style\n\twith both tags intact, and the text run takes its own line: non-terminal text must not hug the\n\tclosing tag, since that shifts where the following element lands (the guard\n\tinline_wide_content_text_sibling_long states). Both formatters agree, so this is not a\n\tdivergence; what it pins is that the form is a FIXED POINT — the space-authored boundary must\n\tsettle in one pass, not hug on a second.\n" + }, + { + "type": "Text", + "start": 589, + "end": 591, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "Comment", + "start": 591, + "end": 693, + "data": " 100 chars - the element fits inline, so it collapses and the text run follows on its own line " + }, + { + "type": "Text", + "start": 693, + "end": 694, + "raw": "\n", + "data": "\n" + }, + { + "type": "RegularElement", + "start": 694, + "end": 835, + "name": "p", + "name_loc": { + "start": { + "line": 12, + "column": 1, + "character": 695 + }, + "end": { + "line": 12, + "column": 2, + "character": 696 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 697, + "end": 699, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "RegularElement", + "start": 699, + "end": 797, + "name": "span", + "name_loc": { + "start": { + "line": 13, + "column": 2, + "character": 700 + }, + "end": { + "line": 13, + "column": 6, + "character": 704 + } + }, + "attributes": [ + { + "type": "Attribute", + "start": 705, + "end": 771, + "name": "data-attr", + "name_loc": { + "start": { + "line": 13, + "column": 7, + "character": 705 + }, + "end": { + "line": 13, + "column": 16, + "character": 714 + } + }, + "value": [ + { + "start": 716, + "end": 770, + "type": "Text", + "raw": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "data": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ] + } + ], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "RegularElement", + "start": 772, + "end": 790, + "name": "code", + "name_loc": { + "start": { + "line": 13, + "column": 75, + "character": 773 + }, + "end": { + "line": 13, + "column": 79, + "character": 777 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 778, + "end": 783, + "raw": "text1", + "data": "text1" + } + ] + } + } + ] + } + }, + { + "type": "Text", + "start": 797, + "end": 805, + "raw": "\n\ttext2 ", + "data": "\n\ttext2 " + }, + { + "type": "RegularElement", + "start": 805, + "end": 823, + "name": "span", + "name_loc": { + "start": { + "line": 14, + "column": 8, + "character": 806 + }, + "end": { + "line": 14, + "column": 12, + "character": 810 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 811, + "end": 816, + "raw": "text3", + "data": "text3" + } + ] + } + }, + { + "type": "Text", + "start": 823, + "end": 831, + "raw": ", text4\n", + "data": ", text4\n" + } + ] + } + }, + { + "type": "Text", + "start": 835, + "end": 837, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "Comment", + "start": 837, + "end": 934, + "data": " 101 chars - the element breaks block-style; the non-terminal text run keeps its own line " + }, + { + "type": "Text", + "start": 934, + "end": 935, + "raw": "\n", + "data": "\n" + }, + { + "type": "RegularElement", + "start": 935, + "end": 1082, + "name": "p", + "name_loc": { + "start": { + "line": 18, + "column": 1, + "character": 936 + }, + "end": { + "line": 18, + "column": 2, + "character": 937 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 938, + "end": 940, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "RegularElement", + "start": 940, + "end": 1044, + "name": "span", + "name_loc": { + "start": { + "line": 19, + "column": 2, + "character": 941 + }, + "end": { + "line": 19, + "column": 6, + "character": 945 + } + }, + "attributes": [ + { + "type": "Attribute", + "start": 946, + "end": 1013, + "name": "data-attr", + "name_loc": { + "start": { + "line": 19, + "column": 7, + "character": 946 + }, + "end": { + "line": 19, + "column": 16, + "character": 955 + } + }, + "value": [ + { + "start": 957, + "end": 1012, + "type": "Text", + "raw": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "data": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ] + } + ], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 1014, + "end": 1017, + "raw": "\n\t\t", + "data": "\n\t\t" + }, + { + "type": "RegularElement", + "start": 1017, + "end": 1035, + "name": "code", + "name_loc": { + "start": { + "line": 20, + "column": 3, + "character": 1018 + }, + "end": { + "line": 20, + "column": 7, + "character": 1022 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 1023, + "end": 1028, + "raw": "text1", + "data": "text1" + } + ] + } + }, + { + "type": "Text", + "start": 1035, + "end": 1037, + "raw": "\n\t", + "data": "\n\t" + } + ] + } + }, + { + "type": "Text", + "start": 1044, + "end": 1052, + "raw": "\n\ttext2 ", + "data": "\n\ttext2 " + }, + { + "type": "RegularElement", + "start": 1052, + "end": 1070, + "name": "span", + "name_loc": { + "start": { + "line": 22, + "column": 8, + "character": 1053 + }, + "end": { + "line": 22, + "column": 12, + "character": 1057 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 1058, + "end": 1063, + "raw": "text3", + "data": "text3" + } + ] + } + }, + { + "type": "Text", + "start": 1070, + "end": 1078, + "raw": ", text4\n", + "data": ", text4\n" + } + ] + } + } + ] + }, + "options": null, + "comments": [] +} diff --git a/tests/fixtures/svelte/elements/inline_wide_element_content_tail_long/input.svelte b/tests/fixtures/svelte/elements/inline_wide_element_content_tail_long/input.svelte new file mode 100644 index 00000000..e55ee2b1 --- /dev/null +++ b/tests/fixtures/svelte/elements/inline_wide_element_content_tail_long/input.svelte @@ -0,0 +1,23 @@ + + + +

+ text1 + text2 text3, text4 +

+ + +

+ + text1 + + text2 text3, text4 +