From 5c0b22b9be8987b0d587476af4b5f9f16f6f8395 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Wed, 5 Aug 2026 00:09:31 -0400 Subject: [PATCH 1/2] fix: keep type-suffix trailing line comments inside their construct --- CLAUDE.md | 26 +- .../src/cli/commands/gap_audit_known.txt | 16 +- .../printer/statements/type_declarations.rs | 47 +- crates/tsv_ts/src/printer/types/composite.rs | 22 +- crates/tsv_ts/src/printer/types/mod.rs | 160 +++- .../src/printer/types/union_intersection.rs | 6 + docs/comments.md | 4 +- docs/conformance_prettier_ts_comments.md | 4 +- .../type_suffix_comment_after_eq/input.svelte | 14 - .../unformatted_inside_suffix.svelte | 15 - .../README.md | 41 + .../expected.json | 445 +++++----- .../input.svelte | 18 + .../output_prettier.svelte | 14 + .../unformatted_ours_flat.svelte | 15 + .../expected.json | 672 +++++++++++++++ .../input.svelte | 20 + .../input.svelte | 16 - .../unformatted_inside_suffix.svelte | 20 - .../README.md | 35 + .../expected.json | 498 +++++------ .../input.svelte | 23 + .../output_prettier.svelte | 15 + .../unformatted_ours_flat.svelte | 19 + .../README.md | 43 + .../audit_signature.txt | 26 + .../expected.json | 784 ++++++++++++++++++ .../input.svelte | 28 + .../output_prettier.svelte | 20 + .../unformatted_ours_flat.svelte | 19 + 30 files changed, 2484 insertions(+), 601 deletions(-) delete mode 100644 tests/fixtures/typescript/declarations/variable/type_suffix_comment_after_eq/input.svelte delete mode 100644 tests/fixtures/typescript/declarations/variable/type_suffix_comment_after_eq/unformatted_inside_suffix.svelte create mode 100644 tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/README.md rename tests/fixtures/typescript/declarations/variable/{type_suffix_comment_after_eq => type_suffix_trailing_comment_prettier_divergence}/expected.json (57%) create mode 100644 tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/input.svelte create mode 100644 tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/output_prettier.svelte create mode 100644 tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/unformatted_ours_flat.svelte create mode 100644 tests/fixtures/typescript/types/mapped_value_trailing_comment_run/expected.json create mode 100644 tests/fixtures/typescript/types/mapped_value_trailing_comment_run/input.svelte delete mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_run/input.svelte delete mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_run/unformatted_inside_suffix.svelte create mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/README.md rename tests/fixtures/typescript/types/{type_suffix_trailing_comment_run => type_suffix_trailing_comment_run_prettier_divergence}/expected.json (57%) create mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/input.svelte create mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/output_prettier.svelte create mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/unformatted_ours_flat.svelte create mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/README.md create mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/audit_signature.txt create mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/expected.json create mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/input.svelte create mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/output_prettier.svelte create mode 100644 tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/unformatted_ours_flat.svelte diff --git a/CLAUDE.md b/CLAUDE.md index 478c52ecf..4059ec765 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -855,13 +855,25 @@ fuzzer, and round-trip are all blind to it; only a prettier `compare` finds it why the rule lives in one emitter per question rather than at each container. ⚠️ **A trailing GAP inside a construct is the third emitter of that same rule** — -`Printer::push_trailing_comments_in_range` (a paren shell's `)`, an indexed access's `]`). -It asks the separator question of the **source** ("did the author give this comment its own -line?"), since these comments may be legitimately glued, and the break rides **inside** the -`line_suffix` — a real break between two deferred comments splits the very construct they -are escaping. Only a **line** comment defers by construction; a block defers solely to stay -behind one, because deferring is what carries a comment out past the closer. Back-to-back -emission welds the run, and an inline block mixed into a deferred one **reorders** it. +`Printer::push_trailing_comments_in_range` (a paren shell's `)`, an indexed access's `]`, a +mapped-type member's value). It asks the separator question of the **source** ("did the +author give this comment its own line?"), since these comments may be legitimately glued, +and the break rides **inside** the `line_suffix` — a real break between two deferred +comments splits the very construct they sit in. Only a **line** comment defers by +construction; a block defers solely to stay behind one. Back-to-back emission welds the run, +and an inline block mixed into a deferred one **reorders** it — so open-coding this loop is +the recurring bug rather than a shortcut. + +⚠️ **A deferred run must not leave the construct it was written in.** Deferring is *end of +line*, not *escape*: a construct that closes without breaking carries the comment past its +own closer, re-binding it and landing it on a line that may already hold a deferred comment, +where the two weld irreversibly. So a **line** comment in one of these gaps forces its +construct **open** — the closer drops to its own line and the comment flushes inside. Every +bracketed type region does this (`{}`, `<>`, tuple `[]`, function-type `()`, indexed-access +`[]`), and a paren shell is **retained** rather than stripped for the same reason. The one +sanctioned exception is a union / intersection member a `|`/`&` separator still **follows**, +whose per-member break ends the line where the shell ends +(`Printer::type_member_separator_follows`); the last member has no separator and retains. ⚠️ **A deferred run's FLUSH must end the line, so a `lineSuffixBoundary` belongs only where nothing else does.** The renderer drains the buffer at a break-mode `line` or at a boundary, diff --git a/crates/tsv_debug/src/cli/commands/gap_audit_known.txt b/crates/tsv_debug/src/cli/commands/gap_audit_known.txt index 41a173151..8e3995e80 100644 --- a/crates/tsv_debug/src/cli/commands/gap_audit_known.txt +++ b/crates/tsv_debug/src/cli/commands/gap_audit_known.txt @@ -14,7 +14,7 @@ # the gate rather than being pinned. # # Format: KINDSHAPEPAYLOADS -# shapes: 547 +# shapes: 533 DROPPED !)⟨⟩!. annotation,block,jsdoc_cast DROPPED &&⟨⟩␣ annotation,block,jsdoc_cast,multiline DROPPED &⟨⟩␣ annotation,block,jsdoc_cast,line,multiline @@ -432,11 +432,8 @@ SWALLOW )=>⟨⟩( line SWALLOW )=>⟨⟩(( line SWALLOW )=>⟨⟩({ line SWALLOW )=>⟨⟩/* line -SWALLOW )[]⟨⟩; line SWALLOW )⟨⟩. line SWALLOW )⟨⟩; line -SWALLOW )⟨⟩>; line -SWALLOW )⟨⟩[]; line SWALLOW )⟨⟩␣ line SWALLOW */(⟨⟩IDENT line SWALLOW */*⟨⟩/* line @@ -470,7 +467,6 @@ SWALLOW =>(⟨⟩( line SWALLOW =>(⟨⟩IDENT line SWALLOW =>(⟨⟩{ line SWALLOW =>⟨⟩␣ line -SWALLOW =⟨⟩␣ line SWALLOW >⟨⟩() line SWALLOW >⟨⟩(): line SWALLOW >⟨⟩; line @@ -490,17 +486,11 @@ SWALLOW IDENT⟨⟩:(/ line SWALLOW IDENT⟨⟩; line SWALLOW IDENT⟨⟩;// line SWALLOW IDENT⟨⟩?(/ line -SWALLOW IDENT⟨⟩] line -SWALLOW IDENT⟨⟩]; line SWALLOW IDENT⟨⟩{ line SWALLOW IDENT⟨⟩␣ line SWALLOW NUM⟨⟩, line SWALLOW NUM⟨⟩,// line SWALLOW NUM⟨⟩; line -SWALLOW [];⟨⟩␣ line -SWALLOW ];⟨⟩␣ line -SWALLOW ]⟨⟩; line -SWALLOW ]⟨⟩␣ line SWALLOW `,⟨⟩␣ line SWALLOW `⟨⟩); line SWALLOW `⟨⟩, line @@ -527,7 +517,6 @@ SWALLOW },⟨⟩␣ annotation,block,jsdoc_cast,multiline SWALLOW }⟨⟩) annotation,block,jsdoc_cast,line,multiline SWALLOW }⟨⟩); line SWALLOW }⟨⟩)}; line -SWALLOW }⟨⟩]; line SWALLOW }⟨⟩␣ line SWALLOW ␣⟨⟩& line SWALLOW ␣⟨⟩( line @@ -544,7 +533,6 @@ SWALLOW ␣⟨⟩/* line SWALLOW ␣⟨⟩// line,multiline SWALLOW ␣⟨⟩: line SWALLOW ␣⟨⟩; line -SWALLOW ␣⟨⟩= line SWALLOW ␣⟨⟩? line SWALLOW ␣⟨⟩IDENT annotation,block,jsdoc_cast,line,multiline SWALLOW ␣⟨⟩[ line @@ -559,6 +547,4 @@ SWALLOW ␣⟨⟩{' line SWALLOW ␣⟨⟩{/* line SWALLOW ␣⟨⟩{} line SWALLOW ␣⟨⟩| line -SWALLOW ␣⟨⟩}; line -SWALLOW ␣⟨⟩}>( line SWALLOW ␣⟨⟩␣ annotation,block,jsdoc_cast,line,multiline diff --git a/crates/tsv_ts/src/printer/statements/type_declarations.rs b/crates/tsv_ts/src/printer/statements/type_declarations.rs index 178f7a818..01eac6f42 100644 --- a/crates/tsv_ts/src/printer/statements/type_declarations.rs +++ b/crates/tsv_ts/src/printer/statements/type_declarations.rs @@ -60,6 +60,12 @@ fn type_has_internal_breaking(printer: &Printer<'_>, ts_type: &TSType<'_>) -> bo // is the disagreement `build_conditional_check_doc` names for its own union gate. // A narrow arm, not the recursion the wider enumeration wants (`Ref<…>[]`, // `keyof Ref<…>`, `Ref<…>['k']` are still missing) — that one needs a corpus A/B. + // The first disjunct needs no comment carve-out: a parenthesized element always + // breaks once it carries one, since a trailing line comment RETAINS the shell over + // real hardlines ([`Printer::paren_retains_for_trailing_run`]) and a leading one + // takes its own `hardline`. The flat-shell exclusion this arm used to carry existed + // only while a trailing run was deferred out past the closer, leaving the shell + // rendering flat while still claiming a break. // The suffix's own `[]` is the same argument once it holds a comment // (`string[⏎↹// c⏎]`): the break is inside a delimiter the array owns, so it hugs // `=` exactly as the empty tuple type `[…]` and the empty object `{…}` already do. @@ -68,18 +74,19 @@ fn type_has_internal_breaking(printer: &Printer<'_>, ts_type: &TSType<'_>) -> bo // the first disjunct misses — an element the author left bare that the printer // parenthesizes anyway (`typeof x /* c */[]`), where the AST holds no // `Parenthesized` node to match on. - // ⚠️ The first disjunct asks whether the parens BREAK, so a shell whose whole - // comment run is deferred to end of line is excluded: its parens are stripped and - // nothing breaks inside them (`(U // c)[]` prints flat as `U[]; // c`). Claiming - // the internal break there sent it to `fluid`, whose marker broke on the shell's - // `break_parent` and split the `=` — a split the reparse can't reproduce, the - // parens being gone (F1). A LEADING comment keeps its real `hardline`, so it still - // answers `true` here. TSType::Array(a) => { - (matches!(a.element_type, TSType::Parenthesized(_)) - && !printer.paren_defers_its_whole_run(a.element_type)) + matches!(a.element_type, TSType::Parenthesized(_)) || matches!(printer.array_suffix_layout(a), ArraySuffixLayout::Split { .. }) } + // An indexed access whose index→`]` gap holds a line comment breaks inside the + // brackets it owns, exactly as the array suffix's `[]` does above — so it hugs + // `=` like the tuple / type-literal / type-argument siblings, which all keep an + // internally-breaking value on the `=` line in both formatters. Comment-free + // input never breaks here, so this disjunct cannot move any input the old + // enumeration covered. + TSType::IndexedAccess(i) => { + printer.has_line_comments_between(i.index_type.span().end, i.span.end) + } _ => false, } } @@ -253,7 +260,7 @@ impl<'a> Printer<'a> { // on `has_format_ignore`). TSType::Array(a) => { self.paren_interior_routed_inner(a.element_type).is_some() - || self.paren_defers_its_whole_run(a.element_type) + || self.paren_retains_for_trailing_run(a.element_type) } // A redundant paren shell whose comments are ALL in its trailing gap // (`= (U // c)[]`, `= (A // c)`). The shell emits a `break_parent` so that a @@ -261,7 +268,16 @@ impl<'a> Printer<'a> { // shell that break reaches only the `=` — where it renders a split the reparse // cannot reproduce, the parens being gone by then. The run itself is deferred, // so the value prints flat: the `=` must not break either. - TSType::Parenthesized(_) => self.paren_defers_its_whole_run(ty), + TSType::Parenthesized(_) => self.paren_retains_for_trailing_run(ty), + // A single-member union / intersection prints transparently as its member + // (prettier drops the node in postprocess), so the `=` asks the member — + // `= | (A // c)` collapses to the retained shell, which hugs. + TSType::Union(u) if u.types.len() == 1 => { + self.value_owns_its_comment_break(&u.types[0]) + } + TSType::Intersection(x) if x.types.len() == 1 => { + self.value_owns_its_comment_break(&x.types[0]) + } TSType::Literal(internal::TSLiteralType::TemplateLiteral(t)) => { self.template_literal_type_breaks_for_comment(t) } @@ -444,6 +460,15 @@ impl<'a> Printer<'a> { // expansion, so keep `= {` together like other internally-breaking types parts.push(d.text(" ")); parts.push(make_rhs(type_doc)); + } else if u.types.len() == 1 && self.value_owns_its_comment_break(&u.types[0]) { + // A single-member union prints transparently as its member (prettier + // drops the node in postprocess), so a member that owns its comment + // break hugs the `=` exactly as it would bare — `= | (A // c)` + // collapses to the retained shell, whose parens own the break. The + // hang below would split the `=` for a break the reparse (seeing the + // bare shell) reproduces at the shell, not the `=` (F1). + parts.push(d.text(" ")); + parts.push(make_rhs(type_doc)); } else if lead_space { // Normal unions: break after `=` with leading `| ` and a hanging indent. parts.push(hang_after_operator(d, make_rhs(type_doc))); diff --git a/crates/tsv_ts/src/printer/types/composite.rs b/crates/tsv_ts/src/printer/types/composite.rs index bd51d446e..64bd40965 100644 --- a/crates/tsv_ts/src/printer/types/composite.rs +++ b/crates/tsv_ts/src/printer/types/composite.rs @@ -1249,15 +1249,17 @@ impl<'a> Printer<'a> { } } - // Trailing comments after the value type. A block comment trails - // inline before the `;` (`V /* c */;`); a line comment goes through - // `line_suffix` (`build_trailing_comment_doc`) so it floats to - // end-of-line *after* the `;` (`V; // c`) instead of swallowing it — - // the `;` is emitted separately by the multiline/one-line branch below. + // Trailing comments after the value type, via the shared trailing-gap + // emitter: a block trails inline before the `;` (`V /* c */;`), a line + // comment rides `line_suffix` so it floats to end-of-line *after* the `;` + // (`V; // c`) instead of swallowing it — the `;` is emitted separately by + // the multiline/one-line branch below. Open-coding the loop here dropped + // the emitter's separator and ordering rules, welding a run onto one line + // (`V; // c1 // c2`) and reordering an inline block ahead of a deferred + // line comment; see [docs/comments.md](../../../../../docs/comments.md) + // §Trailing and dangling runs. let body_end = m.span.end.saturating_sub(1); // before `}` - for comment in comments_to_emit_in_range(self.comments, type_end, body_end) { - body_parts.push(self.build_trailing_comment_doc(comment)); - } + self.push_trailing_comments_in_range(&mut body_parts, type_end, body_end); } else { // No value type (`{ [K in T] }`): comments after the `]` (or the // optional modifier) still trail the member the same way — dropping @@ -1266,9 +1268,7 @@ impl<'a> Printer<'a> { body_parts.push(d.text(marker)); } let body_end = m.span.end.saturating_sub(1); // before `}` - for comment in comments_to_emit_in_range(self.comments, bracket_close, body_end) { - body_parts.push(self.build_trailing_comment_doc(comment)); - } + self.push_trailing_comments_in_range(&mut body_parts, bracket_close, body_end); } self.build_mapped_type_shell( diff --git a/crates/tsv_ts/src/printer/types/mod.rs b/crates/tsv_ts/src/printer/types/mod.rs index 9d54c8fd2..c001e5d24 100644 --- a/crates/tsv_ts/src/printer/types/mod.rs +++ b/crates/tsv_ts/src/printer/types/mod.rs @@ -50,7 +50,7 @@ use smallvec::smallvec; use tsv_lang::comments_to_emit_in_range; use tsv_lang::doc::DocBuf; use tsv_lang::doc::arena::DocId; -use tsv_lang::source_scan::find_char_skipping_comments; +use tsv_lang::source_scan::{find_char_skipping_comments, skip_comment}; /// A resolved keyword→value head — see [`Printer::keyword_value_head`]. pub(in crate::printer) struct KeywordValueHead<'t> { @@ -516,18 +516,37 @@ impl<'a> Printer<'a> { parts.push(c); } parts.push(d.text("[")); - if let Some(c) = index_comments { - parts.push(c); + // Comments in the index→`]` gap trail the index and STAY INSIDE the + // brackets — the treatment every other bracketed type region already + // gives its own trailing gap (a type literal's `}`, a type-argument + // list's `>`, a tuple's `]`, a function type's `)`, and the retained + // paren shell), so the construct answers the question one way. A + // **line** comment there runs to end of line, so the `]` cannot follow + // it: the brackets open, the index sits one level in, and the run + // takes that same interior column. Letting the comment ride out to + // end-of-line instead re-bound it from the index to the whole + // statement and landed it on a line that may already hold one, where + // the two weld irreversibly — see + // [conformance_prettier_ts_comments.md](../../../../../docs/conformance_prettier_ts_comments.md) + // §Comment relocation. The expanding-union index layout is + // comment-gated, so it never carries one of these. + let gap_start = i.index_type.span().end; + if self.has_line_comments_between(gap_start, i.span.end) { + let mut inner: DocBuf = DocBuf::new(); + if let Some(c) = index_comments { + inner.push(c); + } + inner.push(index_doc); + self.push_trailing_comments_in_range(&mut inner, gap_start, i.span.end); + parts.push(d.indent(d.concat(&[d.hardline(), d.concat(&inner)]))); + parts.push(d.hardline()); + } else { + if let Some(c) = index_comments { + parts.push(c); + } + parts.push(index_doc); + self.push_trailing_comments_in_range(&mut parts, gap_start, i.span.end); } - parts.push(index_doc); - // Comments in the index→`]` gap trail the index — previously unclaimed - // by any emitter here, a silent drop. The expanding-union index layout - // is comment-gated, so it never carries one of these. - self.push_trailing_comments_in_range( - &mut parts, - i.index_type.span().end, - i.span.end, - ); parts.push(d.text("]")); d.concat(&parts) } @@ -1075,29 +1094,68 @@ impl<'a> Printer<'a> { ) } - /// Whether `ty` is a `TSParenthesizedType` whose comments are **entirely** in its - /// trailing gap — so [`Self::build_parenthesized_type_unwrap_doc`] emits the whole run - /// through `line_suffix` and the shell itself renders flat. + /// Whether `ty` is a `TSParenthesizedType` that [`Self::build_parenthesized_type_unwrap_doc`] + /// **retains** for a trailing line comment — the shell keeps its parens and opens over + /// real hardlines, so the value owns its own break. /// - /// The shell still emits a `break_parent` for that run (a sibling member must not - /// absorb the comment's line — see that function), which makes `will_break` report a - /// break the output does not actually contain. This is the predicate that tells an - /// enclosing layout to disbelieve it. A **leading** comment is the opposite case: it - /// takes a real `hardline`, so the shell genuinely breaks and the answer is `false`. - pub(in crate::printer) fn paren_defers_its_whole_run(&self, ty: &TSType<'_>) -> bool { + /// The retention is what keeps the comment inside the parens the author wrote it in + /// rather than deferring it past the closer (see that function). An enclosing layout + /// reads this to know the value breaks *internally* and should therefore hug its `=`, + /// exactly as a tuple or type literal does. The single predicate for the retain/strip + /// question — that function consults this too, so an enclosing layout and the shell's + /// own emission cannot disagree. + pub(in crate::printer) fn paren_retains_for_trailing_run(&self, ty: &TSType<'_>) -> bool { let TSType::Parenthesized(p) = ty else { return false; }; - // A **line** comment is what makes the run deferred, and it is also the only thing - // that makes the shell emit its `break_parent`. A trailing run of blocks stays - // inline, breaks nothing, and needs no disbelieving — answering `true` for it - // collapsed a legitimately expanded shell (`array_paren_bracket_comment_long`). - // So the trailing question is asked as the line-comment one directly, not as a - // [`Self::paren_inner_comment_flags`] tuple: a line comment is never owned, so - // "has a trailing line comment" already implies "has a trailing comment to emit". + self.paren_shell_retains_for_trailing_run(p) + } + + /// The [`Self::paren_retains_for_trailing_run`] answer for an already-matched shell. + fn paren_shell_retains_for_trailing_run(&self, p: &TSParenthesizedType<'_>) -> bool { + // A **line** comment is the only thing that retains the shell: a trailing run of + // blocks stays inline and the shell still strips. The question is asked as the + // line-comment one directly, not as a [`Self::paren_inner_comment_flags`] tuple — + // a line comment is never owned, so "has a trailing line comment" already implies + // "has a trailing comment to emit". The leading gap is deliberately NOT consulted: + // a leading comment takes its own real `hardline` either way, so it neither adds + // to nor cancels the retention. let inner = p.type_annotation.span(); - !self.has_comments_to_emit_between(p.span.start, inner.start) - && self.has_line_comments_between(inner.end, p.span.end) + self.has_line_comments_between(inner.end, p.span.end) + && !self.type_member_separator_follows(p.span.end) + } + + /// Whether a union / intersection member separator (`|` / `&`) immediately follows + /// `pos` in source — looking through trivia and through the `)` closers of any + /// enclosing redundant paren layers, which strip along with the shell being asked + /// about and so cannot separate it from the member break. + /// + /// This is the one carve-out from the retain rule above, scoped to exactly its + /// argument: a separator means a per-member break ends the output line right after + /// this construct, so a deferred trailing comment flushes where it was written — + /// lossless, the position carrying no signal (the `union_intersection_parens_line_comment` + /// form, matching prettier). Where nothing but the statement's own tail follows, the + /// deferred run would escape past the `;` onto a line the reparse cannot re-break — + /// non-idempotent — so the shell is retained instead + /// (`type_suffix_trailing_comment_union_member`). A `|`/`&` after a type occurs only + /// as a member separator, so the byte answers the structural question directly. + fn type_member_separator_follows(&self, pos: u32) -> bool { + let bytes = self.source.as_bytes(); + let end = bytes.len(); + let mut i = pos as usize; + while i < end { + let b = bytes[i]; + if b.is_ascii_whitespace() || b == b')' { + i += 1; + continue; + } + if let Some(next) = skip_comment(bytes, i, end) { + i = next; + continue; + } + return b == b'|' || b == b'&'; + } + false } /// Unwrap redundant, comment-free `TSParenthesizedType` layers to find the @@ -1138,6 +1196,40 @@ impl<'a> Printer<'a> { return self.build_type_doc(p.type_annotation); } + // A **line** comment in the trailing gap keeps its place INSIDE the parens, so + // the shell is retained and opens rather than being stripped: the comment runs + // to end of line, so `)` cannot follow it. This is the treatment every other + // bracketed type region gives its own trailing gap, the value-position paren + // already gives its own (`const e = (⏎x // c⏎);`), and the already-retained + // union / intersection shells give theirs — so the question is answered one way. + // Stripping instead carried the comment out to end-of-line, re-binding it from + // the parenthesized type to the whole statement and landing it on a line that + // may already hold one, where the two weld irreversibly; it also emitted a + // `break_parent` for a break the reparse could not reproduce, the parens being + // gone (F1). See + // [conformance_prettier_ts_comments.md](../../../../../docs/conformance_prettier_ts_comments.md) + // §Comment relocation. + // The one exception, folded into the predicate: a member a `|`/`&` separator + // immediately follows, whose per-member break ends the line right after it — the + // stripped comment still trails the member it was written on, lossless, the + // carve-out §Comment Position Philosophy names + // (`union_intersection_parens_line_comment`; the last member has no separator + // and retains — `type_suffix_trailing_comment_union_member`). + if self.paren_shell_retains_for_trailing_run(p) { + let mut inner: DocBuf = DocBuf::new(); + if has_leading { + self.push_paren_shell_leading_run(&mut inner, paren_open, inner_start, true); + } + inner.push(self.build_type_doc(p.type_annotation)); + self.push_trailing_comments_in_range(&mut inner, inner_end, paren_close); + return d.concat(&[ + d.text("("), + d.indent(d.concat(&[d.hardline(), d.concat(&inner)])), + d.hardline(), + d.text(")"), + ]); + } + let mut parts: DocBuf = DocBuf::new(); let mut needs_break = false; @@ -1165,6 +1257,14 @@ impl<'a> Printer<'a> { // non-idempotent. That case is absorbed at the assignment, by // `value_owns_its_comment_break`, which is where the "does the value actually // break?" question already lives. + // + // TODO: `break_parent` breaks EVERY enclosing group, so a shell nested one + // composite deep (`B & (A // c) | C` — the shell ends the intersection inside a + // union member) also breaks that intermediate group, and THAT break the reparse + // cannot reproduce: the flush lands in the union's member gap, which re-breaks + // the union but reprints the intersection flat — a 2-pass convergence. Needs a + // break scoped to the group the deferred run actually flushes in, which the doc + // IR's unscoped `break_parent` cannot express today. if has_trailing { needs_break |= self.push_trailing_comments_in_range(&mut parts, inner_end, paren_close); } diff --git a/crates/tsv_ts/src/printer/types/union_intersection.rs b/crates/tsv_ts/src/printer/types/union_intersection.rs index 2f708de25..2cd614587 100644 --- a/crates/tsv_ts/src/printer/types/union_intersection.rs +++ b/crates/tsv_ts/src/printer/types/union_intersection.rs @@ -273,6 +273,12 @@ impl<'a> Printer<'a> { /// The hug path (`{ … } | null`) and the line-comment path return bare, /// ungrouped docs — they have no flat/broken choice to make (the object owns /// its own expansion; line comments force multiline). + /// A redundant paren shell around a member the next `|` separator follows can + /// strip and let its trailing line comment trail that member — the per-member + /// break ends the line right after it, the lossless carve-out of the preserve + /// rule (§Comment Position Philosophy). The shell answers that structurally + /// (`Printer::type_member_separator_follows`), so the LAST member — whose line + /// ends only at the statement's tail — retains its shell instead. pub(in crate::printer) fn build_union_type_doc(&self, union: &TSUnionType<'_>) -> DocId { let d = self.d(); if union.types.is_empty() { diff --git a/docs/comments.md b/docs/comments.md index 82965cbce..5c35dfb67 100644 --- a/docs/comments.md +++ b/docs/comments.md @@ -96,7 +96,9 @@ A run at the **end** of a container has two shared emitters, split by whether th **No gate can find this class.** Nothing is dropped, merged, or rewritten — the welded text reparses as the same two comments, byte for byte — so the print-once ledger and the census both balance, and the output is *stably* wrong, which leaves F1, the fuzzer, the round-trip and SAFETY blind too. Only a prettier `compare` shows it. That is the whole argument for one emitter per question here rather than a rule each container restates: a drifted copy is invisible until someone diffs it against prettier. -**A trailing GAP is the third emitter, and its separator is deferred.** `Printer::push_trailing_comments_in_range` covers the runs that trail a node *inside* a construct — a retained paren shell's `)`, an indexed access's `]`. It asks the same question before each comment, but from the source (*did the author give this one its own line?*) rather than from the run's shape, because unlike a container-end run these comments may be legitimately glued (`/* c1 */ /* c2 */` written on one line stays on one line). Two consequences are load-bearing. The break travels **inside** the `line_suffix` (`build_trailing_comment_doc_own_line`, prettier's `printTrailingComment` `hasNewline`-backwards branch): a real break emitted between two deferred comments lands in the enclosing construct and splits the very brackets the run is escaping, while a buffered one replays after the construct has closed. And only a **line** comment defers by construction — a block defers solely to stay behind a line comment already in the run, since deferring is what carries a comment out past the closer, and a block that could sit inline must, or it silently leaves the parens it was written in. Emitting the run back to back welds it (`// c1 // c2`) exactly as above, and mixing an inline block into a deferred run **reorders** it; both are as gate-blind as the container case. The caller supplies the layout the run sits in — the shell's `align(2)`, the expanded brackets' `indent` — because the break inherits whatever wraps the doc, not whatever wraps the closer. +**A trailing GAP is the third emitter, and its separator is deferred.** `Printer::push_trailing_comments_in_range` covers the runs that trail a node *inside* a construct — a paren shell's `)`, an indexed access's `]`, a mapped-type member's value. It asks the same question before each comment, but from the source (*did the author give this one its own line?*) rather than from the run's shape, because unlike a container-end run these comments may be legitimately glued (`/* c1 */ /* c2 */` written on one line stays on one line). Two consequences are load-bearing. The break travels **inside** the `line_suffix` (`build_trailing_comment_doc_own_line`, prettier's `printTrailingComment` `hasNewline`-backwards branch): a real break emitted between two deferred comments lands in the enclosing construct and splits the very brackets the run sits in, while a buffered one replays once the line is ending. And only a **line** comment defers by construction — a block defers solely to stay behind a line comment already in the run, and a block that could sit inline must. Emitting the run back to back welds it (`// c1 // c2`) exactly as above, and mixing an inline block into a deferred run **reorders** it; both are as gate-blind as the container case, which is why open-coding this loop is the recurring bug (the mapped-type member did both). The caller supplies the layout the run sits in — the shell's `align(2)`, the expanded brackets' `indent` — because the break inherits whatever wraps the doc, not whatever wraps the closer. + +**⚠️ A deferred run must not leave the construct it was written in.** Deferring is *end of line*, not *escape*: the run flushes wherever the enclosing line happens to end, so a construct that closes without breaking carries the comment past its own closer — re-binding it from the node it trails to whatever statement it lands on, and landing it on a line that may already hold a deferred comment, where the two weld irreversibly. So a **line** comment in one of these gaps forces its construct **open**: the closer drops to its own line and the comment flushes inside. Every bracketed type region does this — a type literal's `}`, a type-argument list's `>`, a tuple's `]`, a function type's `)`, an indexed access's `]`, and a paren shell, which is *retained* rather than stripped for exactly this reason (`tsv_ts`'s `build_parenthesized_type_unwrap_doc`). The one sanctioned exception is a union / intersection member a `|`/`&` separator still **follows** in source: the next separator's per-member break ends the output line exactly where the shell ends, so the strip is lossless and the comment still trails its member. The carve-out is asked structurally — `Printer::type_member_separator_follows`, scanning past trivia and the `)` closers of enclosing redundant layers — rather than as builder context, so it stops precisely where its argument stops: the **last** member has no separator, its line ends only at the statement's tail, and a stripped shell there carried the comment past the `;` while forcing a break the reparse could not reproduce (non-idempotent, and prettier's own form there still is — pinned by `type_suffix_trailing_comment_union_member`'s `audit_signature.txt`). So the last member retains like every other position. See [conformance_prettier_ts_comments.md §Comment relocation](./conformance_prettier_ts_comments.md#comment-relocation). The two differ only in what supplies the outer breaks. The trailing emitter owns the break away from the previous item, and reads `prev_end == 0` as "there is no previous item at all" — a comments-only file, which is how the program's `}`-less end-of-body reaches the same code. The dangling emitter leaves both outer breaks to the delimiter pair, so the caller's `sep` decides whether a lone fitting block comment stays inline (`{/* c */}`) or the body always explodes; "always breaks" is that same emitter with a `hardline` separator, not a second builder. diff --git a/docs/conformance_prettier_ts_comments.md b/docs/conformance_prettier_ts_comments.md index 9abd727df..03d810458 100644 --- a/docs/conformance_prettier_ts_comments.md +++ b/docs/conformance_prettier_ts_comments.md @@ -55,6 +55,8 @@ Prettier moves comments between syntactic boundaries into adjacent blocks, paren - `yield`/`yield*` grouping `(`→argument same-line line comment → the third restricted production, sharing the hanging-paren layout (`build_restricted_production_paren_doc`): tsv keeps the comment trailing the `(` (`yield ( // c⏎a`), and renders a sequence operand bare (`yield ( // c⏎a, b`). Prettier relocates it differently than the return/throw sibling above — it trails the comment on the **keyword** with the argument below (`yield // c⏎(a = b)`) and strips a now-redundant grouping pair around a plain identifier (`yield // c⏎a`), so `yield` has its own fixture rather than folding into the return/throw one — [yield_open_paren_line_comment](../tests/fixtures/typescript/syntax/comments/yield_open_paren_line_comment_prettier_divergence/) - Array element end-of-line block comment → Across the element's comma, from leading the next element to trailing the previous one (`['aaaa', /* c */⏎'bbbb']` → `['aaaa' /* c */, 'bbbb']`, flipping the binding from `'bbbb'` to `'aaaa'`); prettier classifies on newlines alone (`endOfLine`), so the comma — which carries the association — plays no part. tsv keeps the comment after the comma. **Not** the sanctioned pure-separator trail: that covers a same-line *line* comment (`A // c⏎, B` → `A, // c`), where the binding never changes and a `//` running to end-of-line leaves no other rendering; a block comment flips its binding and renders fine either side, so the move is unforced — [end_of_line_block_comment](../tests/fixtures/typescript/expressions/arrays/end_of_line_block_comment_prettier_divergence/) - Indexed-access `[`→index **multiline** block → Out before `[` (`A /* c⏎d */[K]`), which **changes what the code means**: a type's index suffix may not follow a line break (TypeScript's `parsePostfixTypeOrHigher` stops at `scanner.hasPrecedingLineBreak()`), and the comment's *interior* newline supplies one — so Prettier's relocated form parses as `type X = A;` plus a separate `ArrayExpression` statement, not an indexed access. The canonical parser agrees: `A /* c */[K]` → `TSIndexedAccessType`, `A /* c⏎d */[K]` → `TSTypeReference` + `ExpressionStatement`. Prettier reaches it in two passes and then re-prints the changed tree as a third form, so it has **no** stable rendering of this construct — unlike the single-line sibling below, whose relocation carries no newline and is therefore safe and dual-stable. tsv keeps the comment inside the brackets and hangs the index. Content preservation, not a position preference — [indexed_access_own_line_multiline_block_comment](../tests/fixtures/typescript/types/indexed_access_own_line_multiline_block_comment_prettier_divergence/) +- **Type-suffix trailing line comment / run**, at the end of an indexed access's brackets (`T[K // c1⏎]`) or of a redundant paren shell around a type (`(A // c2⏎)`) → Carried **out of the construct**, past the `=` onto the initializer's line (`const a: T[K] = // c1⏎↹y;`) or past the `;` (`type A = T[K]; // c1⏎// c2`), the shell stripped on the way; tsv keeps the comment inside the region the author wrote it in and drops the closer to its own line. +- **Type-suffix trailing line comment, member position** → the member-position face of the entry above. An indexed access that is a **union member** keeps the comment inside its brackets (`| T[⏎↹↹K // c1⏎↹]`); prettier trails it on the member (`| T[K] // c1`), re-binding it from the index to the whole member. A redundant paren shell around the **last** (or sole) member of a union / intersection is **retained** (`| B⏎| (⏎↹A // c2⏎↹)`); prettier strips it and carries the comment past the `;` — and its form there is **non-idempotent**: the escaped comment is all that held the union broken, so prettier's second pass collapses `type A2 =⏎↹| B⏎↹| A; // c2` to `type A2 = B | A; // c2` (pinned by the fixture's `audit_signature.txt`). A **non-last** member's shell still strips and defers, matching prettier (`(a // c⏎) | b` → `| a // c⏎| b`, [union_intersection_parens_line_comment](../tests/fixtures/typescript/types/union_intersection_parens_line_comment/)): that carve-out's argument is exactly that the next separator's per-member break ends the output line where the shell ends, flushing the deferred comment on the member it was written in — so it is keyed on a following `|`/`&` in source (`Printer::type_member_separator_follows`) and stops at the last member, where only the statement's tail follows — [type_suffix_trailing_comment_union_member](../tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/) **The four sibling bracketed type regions already answer this tsv's way in both formatters** — a type literal's `}`, a type-argument list's `>`, a tuple's `]`, a function type's `)` all keep a trailing comment inside and break the closer onto its own line, byte-identically — so prettier answers one question two ways and tsv answers it once; a **value**-position redundant paren is already retained for the same reason (`const e = (⏎x // c⏎);`), which makes the two sides of the paren rule agree too. Carrying the comment out is not lossless: it re-binds the comment from the index (or the parenthesized type) to the whole statement, and lands it on a line that may already hold one, where the run renders back to back and the second `//` becomes text of the first (`const a: T[K] = 1; // c1 // c2`) — irreversibly, since the merged form is a fixed point in both formatters. Keeping the comment inside is what makes that collision unreachable rather than something the renderer must defuse — [type_suffix_trailing_comment](../tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/), [type_suffix_trailing_comment_run](../tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/) - Array-type suffix `[`→`]` → Out in front of the brackets (`string[/* c */]` → `string /* c */[]`), re-binding the comment from the suffix to the element type; for a line comment, out of the declaration entirely (`type B = string[];⏎// c`), where it reads as leading whatever statement follows. tsv keeps it inside the brackets the author wrote it in, routing the pair through the same empty-brackets emitter the **empty tuple type** uses — `type A = [/* c */]` keeps its comment inside and a `//` there breaks the brackets open, and both formatters already agree on that, so preserving here makes the two bracket forms answer one question one way. The suffix's own `[]` becoming a break point also makes the array hug the type-alias `=` like every other empty container (`type B = string[⏎↹// c⏎]`, as `= [` / `= {` already do). The gap **before** the brackets can hold only a single-line block comment, which both formatters keep in place (`string /* c */[]`): a `//` or a multiline block puts a line break in front of the `[`, and by the same `hasPrecedingLineBreak()` rule as the indexed-access entry above the construct is then no longer an array type at all. A chain of suffixes keeps each comment with its own pair. Not preserving drops it — the region had no emitter — [array_bracket_comment](../tests/fixtures/typescript/types/array_bracket_comment_prettier_divergence/) - Array-type suffix `[`→`]`, **parenthesized** element → The same hoist one construct over (`(X & Y)[/* c */]` → `(X & Y) /* c */[]`, a `//` out of the declaration entirely); tsv keeps it inside the brackets, through the same empty-brackets emitter. The parens change what *precedes* the suffix, not what the suffix is. The `)`→`[` gap is a **match** — a single-line block comment written there stays there in both (`(X & Y) /* c */[]`), including after the `)` tsv synthesizes for an element the author left bare (`typeof x /* c */[]` → `(typeof x) /* c */[]`), and a `//` or multiline block cannot occur in that gap at all by the same `hasPrecedingLineBreak()` rule (the plain fixtures [array_paren_before_bracket_comment](../tests/fixtures/typescript/types/array_paren_before_bracket_comment/) and, for the layout boundary, [array_paren_before_bracket_comment_long](../tests/fixtures/typescript/types/array_paren_before_bracket_comment_long/)). A **hugged** union element takes this route; an expanding one takes the entry below — [array_paren_bracket_comment](../tests/fixtures/typescript/types/array_paren_bracket_comment_prettier_divergence/). At the width boundary the suffix's own `[]` is what breaks under tsv, the element staying put, while prettier — out of in-declaration room for its relocated form — strands the **block** comment on its own line after the `;`, the same escape its line comment already makes — [array_paren_bracket_comment_long](../tests/fixtures/typescript/types/array_paren_bracket_comment_long_prettier_divergence/) - Array-type suffix `[`→`]`, **expanding parenthesized union** element → Prettier's destination flips to the other side of the `)`: into the parens, trailing the last union member (`('a' | 'b')[/* c */]` → `('a' | 'b' /* c */)[]`), re-binding it from the suffix to that member — and a comment authored in the `)`→`[` gap lands there too, so two authorings collapse onto one form. The choice is keyed on the element's **layout**, not its kind: a union that prints hugged (`(T | null)[]`) takes the entry above instead, so prettier answers one gap two ways. tsv keeps each comment where it was written in both — [array_paren_union_bracket_comment](../tests/fixtures/typescript/types/array_paren_union_bracket_comment_prettier_divergence/) @@ -207,7 +209,7 @@ Prettier moves comments between syntactic boundaries into adjacent blocks, paren - Redundant paren **first** member mixed / trailing cmt (intersection **in a tuple element**) → the tuple-element analog of the entry above: the same `(/* b */ // c A) & B` / `(// c A /* t */) & B` shells, but the intersection is a **tuple element** (`[(…) & B]`) rather than a type-alias RHS. A tuple element is an **own-line** context — the caller already places it on its own indented line — so the first-member hoist keeps the run at the element indent instead of adding the trailing-prefix continuation level (`own_line` in `build_intersection_type_doc`; without it the reparsed bare form was one level shallower, a non-idempotency). The canonical is stable under both formatters, so unlike the type-alias RHS the divergence is only in the shell's normalization: the **mixed** shell's terminal is the dual-stable *glued* form (a `variant`, `prettier_intermediate_to_variant`) — [intersection_redundant_paren_first_member_tuple_mixed_line_comment](../tests/fixtures/typescript/types/intersection_redundant_paren_first_member_tuple_mixed_line_comment_prettier_divergence/) — while the **trailing** shell converges (through the same unstable member-hang first pass) all the way back to `input` (the element is on its own line for both formatters, so no own-line `variant` splits off), pinned by `prettier_intermediate_*` — [intersection_redundant_paren_first_member_tuple_trailing_line_comment](../tests/fixtures/typescript/types/intersection_redundant_paren_first_member_tuple_trailing_line_comment_prettier_divergence/) - Retained paren member **own-line leading** line cmt (collapsing intersection) → Out of parens, onto its own line before `(`, intersection broken; tsv keeps it inside the parens, forcing the member to break open — [union_intersection_paren_member_own_line_comment](../tests/fixtures/typescript/types/union_intersection_paren_member_own_line_comment_prettier_divergence/) - Retained paren intersection member cmt → Outside the parens (after `)`/`(`). Covers the plain intersection member and the **trailing-object** one (`(a & { x: X } /* c */)`), whose aligned layout prints its own `(`…`)` — so, like the leading gap, the trailing gap must be emitted there or the comment is silently dropped; a line comment in it keeps its line and drops the `)` to the next — [retained_paren_intersection_member_comment](../tests/fixtures/typescript/types/retained_paren_intersection_member_comment_prettier_divergence/) -- Retained paren shell trailing gap, comment **run** → the ≥2-comment extension of the two entries above (union shell and intersection-trailing-object shell alike). Placement is theirs — prettier hoists the run out, tsv keeps every comment inside the shell — and what the run adds is that the comments stay **distinct**: emitted with nothing between them a `//` welds the next comment into its own text (`// c1 // c2`), and a *block* after a line comment additionally **reorders** ahead of it (block inline vs `line_suffix`). Each comment following a line comment takes a break before it; two **glued blocks** keep sharing their line, so the separator is conditional on what precedes it. The run continues at the column of the region its shell emits it into — the `)`'s `align(2)` offset for the intersection shell, the interior indent for the union one — [retained_paren_shell_trailing_comment_run](../tests/fixtures/typescript/types/retained_paren_shell_trailing_comment_run_prettier_divergence/). The rule is the shared emitter's, not the shell's: the same run in the **prettier-ignore-frozen** indexed-access route welds identically, pinned by case `B` of [indexed_access_prettier_ignore_index](../tests/fixtures/typescript/types/indexed_access_prettier_ignore_index_prettier_divergence/), and where the gap belongs to a **stripped** paren or a plain indexed access the run escapes to trail the statement and both formatters agree — no divergence, pinned by the plain [type_suffix_trailing_comment_run](../tests/fixtures/typescript/types/type_suffix_trailing_comment_run/) +- Retained paren shell trailing gap, comment **run** → the ≥2-comment extension of the two entries above (union shell and intersection-trailing-object shell alike). Placement is theirs — prettier hoists the run out, tsv keeps every comment inside the shell — and what the run adds is that the comments stay **distinct**: emitted with nothing between them a `//` welds the next comment into its own text (`// c1 // c2`), and a *block* after a line comment additionally **reorders** ahead of it (block inline vs `line_suffix`). Each comment following a line comment takes a break before it; two **glued blocks** keep sharing their line, so the separator is conditional on what precedes it. The run continues at the column of the region its shell emits it into — the `)`'s `align(2)` offset for the intersection shell, the interior indent for the union one — [retained_paren_shell_trailing_comment_run](../tests/fixtures/typescript/types/retained_paren_shell_trailing_comment_run_prettier_divergence/). The rule is the shared emitter's, not the shell's: the same run in the **prettier-ignore-frozen** indexed-access route welds identically, pinned by case `B` of [indexed_access_prettier_ignore_index](../tests/fixtures/typescript/types/indexed_access_prettier_ignore_index_prettier_divergence/), and a **stripped** paren or a plain indexed access takes the same in-place treatment as the retained shell, rather than letting the run escape to trail the statement the way prettier does — the Type-suffix trailing entry above, [type_suffix_trailing_comment_run](../tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/) - Type alias head to `=` (line, union values) → With an **inline union** value prettier relocates to end-of-statement (`type A = B | C; // c` — the merge-prone declarator destination); with a **break-forced non-hugging union** it crosses the `=` non-idempotently (glued pass, then own-line leading the first member — dual-stable `variant_own_line`). tsv keeps the comment trailing the head + continuation indent in both; on the broken union the `=` drops onto its own line and members hang at the `=` level (not one deeper). Own-line authorings pull up under tsv, to the hang under prettier — [type_alias_line_pre_equals](../tests/fixtures/typescript/types/comments/type_alias_line_pre_equals_prettier_divergence/), [type_alias_line_pre_equals_break](../tests/fixtures/typescript/types/comments/type_alias_line_pre_equals_break_prettier_divergence/). The non-union face (prettier hangs, no float) is the [name_before_eq_line_comment](../tests/fixtures/typescript/types/aliases/name_before_eq_line_comment_prettier_divergence/) entry above - Type param keyword to value (own-line) → Up onto the keyword line — [type_param_keyword_own_line_comment](../tests/fixtures/typescript/types/comments/type_param_keyword_own_line_comment_prettier_divergence/) - Function param default to value (line) → Floated out to trail the whole parameter (after the value); tsv keeps it after `=` and drops the value to a continuation line indented one level (the uniform forced-continuation indent, matching the declarator's value hang) — [param_default_line_comment](../tests/fixtures/typescript/declarations/function/param_default_line_comment_prettier_divergence/) diff --git a/tests/fixtures/typescript/declarations/variable/type_suffix_comment_after_eq/input.svelte b/tests/fixtures/typescript/declarations/variable/type_suffix_comment_after_eq/input.svelte deleted file mode 100644 index 363279258..000000000 --- a/tests/fixtures/typescript/declarations/variable/type_suffix_comment_after_eq/input.svelte +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/tests/fixtures/typescript/declarations/variable/type_suffix_comment_after_eq/unformatted_inside_suffix.svelte b/tests/fixtures/typescript/declarations/variable/type_suffix_comment_after_eq/unformatted_inside_suffix.svelte deleted file mode 100644 index b4dfe1cd0..000000000 --- a/tests/fixtures/typescript/declarations/variable/type_suffix_comment_after_eq/unformatted_inside_suffix.svelte +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/README.md b/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/README.md new file mode 100644 index 000000000..1307e5cda --- /dev/null +++ b/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/README.md @@ -0,0 +1,41 @@ +# Divergence: type-suffix trailing line comment stays inside the brackets + +A line comment at the **end** of an indexed access's brackets (`T[K // c1⏎]`), or at the end +of a redundant paren shell around a type (`(A // c2⏎)`). tsv keeps it inside the region the +author wrote it in and drops the closer to its own line. **Prettier carries it out of the +construct** — past the `=` onto the initializer's line (`const a1: T[K] = // c1⏎\ty;`), or +past the `;` when there is no initializer (`let b: T[K]; // c3`) — and strips the paren shell +on the way. + +```ts +// tsv (comment stays inside) // prettier (carried out past the `=`) +const a1: T[ const a1: T[K] = // c1 + K // c1 y; +] = y; +``` + +## Reason + +**Every other bracketed type region already answers this question tsv's way, and prettier +agrees on all of them** — a type literal's `}` (`{ b: T // c⏎}`), a type-argument list's `>` +(`Array<⏎T // c⏎>`), a tuple's `]`, and a function type's `)` all keep the comment inside and +break the closer onto its own line, byte-identically in both formatters. Prettier carries the +comment out only from an indexed access and from a paren shell it is about to strip, so it +answers one question two ways; tsv answers it once. A **value**-position redundant paren is +already retained for exactly this reason (`const e = (⏎x // c⏎);`), so retaining the type-side +shell makes the two sides agree too. + +Carrying the comment out is also not lossless. It re-binds the comment from the index (or the +parenthesized type) to the whole statement, and it lands the comment on a line that may +already hold one — where the run renders back to back and the second `//` becomes text of the +first (`const a1: T[K] = 1; // c1 // c2`, irreversibly: the merged form is a fixed point in +both formatters). Keeping the comment inside the brackets is what makes that collision +unreachable rather than something the renderer has to defuse. Per +§Comment Position Philosophy, a relocation that can merge is preserved against. + +`unformatted_ours_flat.svelte` carries the flat authoring (`T[K // c1⏎] = y;`), which reaches +`input` under tsv only — prettier carries it out from either authoring, so its own broken form +collapses too. + +See [conformance_prettier.md §Comment Position Philosophy](../../../../../../docs/conformance_prettier.md#comment-position-philosophy) +and [conformance_prettier_ts_comments.md §Comment relocation](../../../../../../docs/conformance_prettier_ts_comments.md#comment-relocation). diff --git a/tests/fixtures/typescript/declarations/variable/type_suffix_comment_after_eq/expected.json b/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/expected.json similarity index 57% rename from tests/fixtures/typescript/declarations/variable/type_suffix_comment_after_eq/expected.json rename to tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/expected.json index 45683687e..48927c1da 100644 --- a/tests/fixtures/typescript/declarations/variable/type_suffix_comment_after_eq/expected.json +++ b/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/expected.json @@ -2,7 +2,7 @@ "css": null, "js": [], "start": 0, - "end": 474, + "end": 466, "type": "Root", "fragment": { "type": "Fragment", @@ -12,9 +12,9 @@ "comments": [ { "type": "Line", - "value": " a comment in the annotation's trailing gap escapes the type and lands after the", + "value": " a line comment at the end of an indexed access's brackets stays inside them,", "start": 20, - "end": 102, + "end": 99, "loc": { "start": { "line": 2, @@ -22,15 +22,15 @@ }, "end": { "line": 2, - "column": 83 + "column": 80 } } }, { "type": "Line", - "value": " `=`, dropping the initializer to its own line", - "start": 104, - "end": 152, + "value": " like every other bracketed type region, with the `]` on its own line", + "start": 101, + "end": 172, "loc": { "start": { "line": 3, @@ -38,15 +38,15 @@ }, "end": { "line": 3, - "column": 49 + "column": 72 } } }, { "type": "Line", - "value": " (unformatted_inside_suffix carries the authored placement)", - "start": 154, - "end": 215, + "value": " (unformatted_ours_flat carries the flat authoring)", + "start": 174, + "end": 227, "loc": { "start": { "line": 4, @@ -54,87 +54,87 @@ }, "end": { "line": 4, - "column": 62 + "column": 54 } } }, { "type": "Line", "value": " c1", - "start": 234, - "end": 239, + "start": 246, + "end": 251, "loc": { "start": { - "line": 5, - "column": 18 + "line": 6, + "column": 4 }, "end": { - "line": 5, - "column": 23 + "line": 6, + "column": 9 } } }, { "type": "Line", - "value": " a redundant paren's trailing gap escapes the same way once the parens are stripped", - "start": 247, - "end": 332, + "value": " a redundant paren shell is retained for the same reason, as in value position", + "start": 262, + "end": 342, "loc": { "start": { - "line": 8, + "line": 9, "column": 1 }, "end": { - "line": 8, - "column": 86 + "line": 9, + "column": 81 } } }, { "type": "Line", "value": " c2", - "start": 348, - "end": 353, + "start": 360, + "end": 365, "loc": { "start": { - "line": 9, - "column": 15 + "line": 11, + "column": 4 }, "end": { - "line": 9, - "column": 20 + "line": 11, + "column": 9 } } }, { "type": "Line", - "value": " with no initializer there is no `=` to cross — the comment trails the statement", - "start": 361, - "end": 443, + "value": " with no initializer there is no `=` to reach either", + "start": 376, + "end": 430, "loc": { "start": { - "line": 12, + "line": 14, "column": 1 }, "end": { - "line": 12, - "column": 83 + "line": 14, + "column": 55 } } }, { "type": "Line", "value": " c3", - "start": 458, - "end": 463, + "start": 446, + "end": 451, "loc": { "start": { - "line": 13, - "column": 14 + "line": 16, + "column": 4 }, "end": { - "line": 13, - "column": 19 + "line": 16, + "column": 9 } } } @@ -142,99 +142,99 @@ "instance": { "type": "Script", "start": 0, - "end": 473, + "end": 465, "context": "default", "content": { "type": "Program", "start": 18, - "end": 464, + "end": 456, "loc": { "start": { "line": 1, "column": 0 }, "end": { - "line": 14, + "line": 18, "column": 9 } }, "body": [ { "type": "VariableDeclaration", - "start": 217, - "end": 244, + "start": 229, + "end": 259, "loc": { "start": { "line": 5, "column": 1 }, "end": { - "line": 6, - "column": 4 + "line": 7, + "column": 7 } }, "declarations": [ { "type": "VariableDeclarator", - "start": 223, - "end": 243, + "start": 235, + "end": 258, "loc": { "start": { "line": 5, "column": 7 }, "end": { - "line": 6, - "column": 3 + "line": 7, + "column": 6 } }, "id": { "type": "Identifier", - "start": 223, - "end": 231, + "start": 235, + "end": 254, "loc": { "start": { "line": 5, "column": 7 }, "end": { - "line": 5, - "column": 15 + "line": 7, + "column": 2 } }, "name": "a1", "typeAnnotation": { "type": "TSTypeAnnotation", - "start": 225, - "end": 231, + "start": 237, + "end": 254, "loc": { "start": { "line": 5, "column": 9 }, "end": { - "line": 5, - "column": 15 + "line": 7, + "column": 2 } }, "typeAnnotation": { "type": "TSIndexedAccessType", - "start": 227, - "end": 231, + "start": 239, + "end": 254, "loc": { "start": { "line": 5, "column": 11 }, "end": { - "line": 5, - "column": 15 + "line": 7, + "column": 2 } }, "objectType": { "type": "TSTypeReference", - "start": 227, - "end": 228, + "start": 239, + "end": 240, "loc": { "start": { "line": 5, @@ -247,8 +247,8 @@ }, "typeName": { "type": "Identifier", - "start": 227, - "end": 228, + "start": 239, + "end": 240, "loc": { "start": { "line": 5, @@ -264,61 +264,61 @@ }, "indexType": { "type": "TSTypeReference", - "start": 229, - "end": 230, + "start": 244, + "end": 245, "loc": { "start": { - "line": 5, - "column": 13 + "line": 6, + "column": 2 }, "end": { - "line": 5, - "column": 14 + "line": 6, + "column": 3 } }, "typeName": { "type": "Identifier", - "start": 229, - "end": 230, + "start": 244, + "end": 245, "loc": { "start": { - "line": 5, - "column": 13 + "line": 6, + "column": 2 }, "end": { - "line": 5, - "column": 14 + "line": 6, + "column": 3 } }, "name": "K" - } + }, + "trailingComments": [ + { + "type": "Line", + "value": " c1", + "start": 246, + "end": 251 + } + ] } } } }, "init": { "type": "Identifier", - "start": 242, - "end": 243, + "start": 257, + "end": 258, "loc": { "start": { - "line": 6, - "column": 2 + "line": 7, + "column": 5 }, "end": { - "line": 6, - "column": 3 + "line": 7, + "column": 6 } }, - "name": "y", - "leadingComments": [ - { - "type": "Line", - "value": " c1", - "start": 234, - "end": 239 - } - ] + "name": "y" } } ], @@ -326,138 +326,153 @@ "leadingComments": [ { "type": "Line", - "value": " a comment in the annotation's trailing gap escapes the type and lands after the", + "value": " a line comment at the end of an indexed access's brackets stays inside them,", "start": 20, - "end": 102 + "end": 99 }, { "type": "Line", - "value": " `=`, dropping the initializer to its own line", - "start": 104, - "end": 152 + "value": " like every other bracketed type region, with the `]` on its own line", + "start": 101, + "end": 172 }, { "type": "Line", - "value": " (unformatted_inside_suffix carries the authored placement)", - "start": 154, - "end": 215 + "value": " (unformatted_ours_flat carries the flat authoring)", + "start": 174, + "end": 227 } ] }, { "type": "VariableDeclaration", - "start": 334, - "end": 358, + "start": 344, + "end": 373, "loc": { "start": { - "line": 9, + "line": 10, "column": 1 }, "end": { - "line": 10, - "column": 4 + "line": 12, + "column": 7 } }, "declarations": [ { "type": "VariableDeclarator", - "start": 340, - "end": 357, + "start": 350, + "end": 372, "loc": { "start": { - "line": 9, + "line": 10, "column": 7 }, "end": { - "line": 10, - "column": 3 + "line": 12, + "column": 6 } }, "id": { "type": "Identifier", - "start": 340, - "end": 345, + "start": 350, + "end": 368, "loc": { "start": { - "line": 9, + "line": 10, "column": 7 }, "end": { - "line": 9, - "column": 12 + "line": 12, + "column": 2 } }, "name": "a2", "typeAnnotation": { "type": "TSTypeAnnotation", - "start": 342, - "end": 345, + "start": 352, + "end": 368, "loc": { "start": { - "line": 9, + "line": 10, "column": 9 }, "end": { - "line": 9, - "column": 12 + "line": 12, + "column": 2 } }, "typeAnnotation": { - "type": "TSTypeReference", - "start": 344, - "end": 345, + "type": "TSParenthesizedType", + "start": 354, + "end": 368, "loc": { "start": { - "line": 9, + "line": 10, "column": 11 }, "end": { - "line": 9, - "column": 12 + "line": 12, + "column": 2 } }, - "typeName": { - "type": "Identifier", - "start": 344, - "end": 345, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 358, + "end": 359, "loc": { "start": { - "line": 9, - "column": 11 + "line": 11, + "column": 2 }, "end": { - "line": 9, - "column": 12 + "line": 11, + "column": 3 } }, - "name": "A" + "typeName": { + "type": "Identifier", + "start": 358, + "end": 359, + "loc": { + "start": { + "line": 11, + "column": 2 + }, + "end": { + "line": 11, + "column": 3 + } + }, + "name": "A" + }, + "trailingComments": [ + { + "type": "Line", + "value": " c2", + "start": 360, + "end": 365 + } + ] } } } }, "init": { "type": "Identifier", - "start": 356, - "end": 357, + "start": 371, + "end": 372, "loc": { "start": { - "line": 10, - "column": 2 + "line": 12, + "column": 5 }, "end": { - "line": 10, - "column": 3 + "line": 12, + "column": 6 } }, - "name": "y", - "leadingComments": [ - { - "type": "Line", - "value": " c2", - "start": 348, - "end": 353 - } - ] + "name": "y" } } ], @@ -465,109 +480,109 @@ "leadingComments": [ { "type": "Line", - "value": " a redundant paren's trailing gap escapes the same way once the parens are stripped", - "start": 247, - "end": 332 + "value": " a redundant paren shell is retained for the same reason, as in value position", + "start": 262, + "end": 342 } ] }, { "type": "VariableDeclaration", - "start": 445, - "end": 457, + "start": 432, + "end": 455, "loc": { "start": { - "line": 13, + "line": 15, "column": 1 }, "end": { - "line": 13, - "column": 13 + "line": 17, + "column": 3 } }, "declarations": [ { "type": "VariableDeclarator", - "start": 449, - "end": 456, + "start": 436, + "end": 454, "loc": { "start": { - "line": 13, + "line": 15, "column": 5 }, "end": { - "line": 13, - "column": 12 + "line": 17, + "column": 2 } }, "id": { "type": "Identifier", - "start": 449, - "end": 456, + "start": 436, + "end": 454, "loc": { "start": { - "line": 13, + "line": 15, "column": 5 }, "end": { - "line": 13, - "column": 12 + "line": 17, + "column": 2 } }, "name": "b", "typeAnnotation": { "type": "TSTypeAnnotation", - "start": 450, - "end": 456, + "start": 437, + "end": 454, "loc": { "start": { - "line": 13, + "line": 15, "column": 6 }, "end": { - "line": 13, - "column": 12 + "line": 17, + "column": 2 } }, "typeAnnotation": { "type": "TSIndexedAccessType", - "start": 452, - "end": 456, + "start": 439, + "end": 454, "loc": { "start": { - "line": 13, + "line": 15, "column": 8 }, "end": { - "line": 13, - "column": 12 + "line": 17, + "column": 2 } }, "objectType": { "type": "TSTypeReference", - "start": 452, - "end": 453, + "start": 439, + "end": 440, "loc": { "start": { - "line": 13, + "line": 15, "column": 8 }, "end": { - "line": 13, + "line": 15, "column": 9 } }, "typeName": { "type": "Identifier", - "start": 452, - "end": 453, + "start": 439, + "end": 440, "loc": { "start": { - "line": 13, + "line": 15, "column": 8 }, "end": { - "line": 13, + "line": 15, "column": 9 } }, @@ -576,34 +591,42 @@ }, "indexType": { "type": "TSTypeReference", - "start": 454, - "end": 455, + "start": 444, + "end": 445, "loc": { "start": { - "line": 13, - "column": 10 + "line": 16, + "column": 2 }, "end": { - "line": 13, - "column": 11 + "line": 16, + "column": 3 } }, "typeName": { "type": "Identifier", - "start": 454, - "end": 455, + "start": 444, + "end": 445, "loc": { "start": { - "line": 13, - "column": 10 + "line": 16, + "column": 2 }, "end": { - "line": 13, - "column": 11 + "line": 16, + "column": 3 } }, "name": "K" - } + }, + "trailingComments": [ + { + "type": "Line", + "value": " c3", + "start": 446, + "end": 451 + } + ] } } } @@ -615,17 +638,9 @@ "leadingComments": [ { "type": "Line", - "value": " with no initializer there is no `=` to cross — the comment trails the statement", - "start": 361, - "end": 443 - } - ], - "trailingComments": [ - { - "type": "Line", - "value": " c3", - "start": 458, - "end": 463 + "value": " with no initializer there is no `=` to reach either", + "start": 376, + "end": 430 } ] } diff --git a/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/input.svelte b/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/input.svelte new file mode 100644 index 000000000..1425aceaf --- /dev/null +++ b/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/input.svelte @@ -0,0 +1,18 @@ + diff --git a/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/output_prettier.svelte b/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/output_prettier.svelte new file mode 100644 index 000000000..f4579e40a --- /dev/null +++ b/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/output_prettier.svelte @@ -0,0 +1,14 @@ + diff --git a/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/unformatted_ours_flat.svelte b/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/unformatted_ours_flat.svelte new file mode 100644 index 000000000..0b153a53e --- /dev/null +++ b/tests/fixtures/typescript/declarations/variable/type_suffix_trailing_comment_prettier_divergence/unformatted_ours_flat.svelte @@ -0,0 +1,15 @@ + diff --git a/tests/fixtures/typescript/types/mapped_value_trailing_comment_run/expected.json b/tests/fixtures/typescript/types/mapped_value_trailing_comment_run/expected.json new file mode 100644 index 000000000..fe4e5e623 --- /dev/null +++ b/tests/fixtures/typescript/types/mapped_value_trailing_comment_run/expected.json @@ -0,0 +1,672 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 418, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " a run of comments trailing a mapped member stays distinct, one per line", + "start": 20, + "end": 94, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 75 + } + } + }, + { + "type": "Line", + "value": " c1", + "start": 122, + "end": 127, + "loc": { + "start": { + "line": 4, + "column": 15 + }, + "end": { + "line": 4, + "column": 20 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 130, + "end": 135, + "loc": { + "start": { + "line": 5, + "column": 2 + }, + "end": { + "line": 5, + "column": 7 + } + } + }, + { + "type": "Line", + "value": " an own-line comment after an inline block keeps its own line", + "start": 142, + "end": 205, + "loc": { + "start": { + "line": 8, + "column": 1 + }, + "end": { + "line": 8, + "column": 64 + } + } + }, + { + "type": "Block", + "value": " c3 ", + "start": 232, + "end": 240, + "loc": { + "start": { + "line": 10, + "column": 14 + }, + "end": { + "line": 10, + "column": 22 + } + } + }, + { + "type": "Line", + "value": " c4", + "start": 244, + "end": 249, + "loc": { + "start": { + "line": 11, + "column": 2 + }, + "end": { + "line": 11, + "column": 7 + } + } + }, + { + "type": "Line", + "value": " a block written on its own line defers behind the line comment ahead of it,", + "start": 256, + "end": 334, + "loc": { + "start": { + "line": 14, + "column": 1 + }, + "end": { + "line": 14, + "column": 79 + } + } + }, + { + "type": "Line", + "value": " keeping source order", + "start": 336, + "end": 359, + "loc": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 15, + "column": 24 + } + } + }, + { + "type": "Line", + "value": " c5", + "start": 387, + "end": 392, + "loc": { + "start": { + "line": 17, + "column": 15 + }, + "end": { + "line": 17, + "column": 20 + } + } + }, + { + "type": "Block", + "value": " c6 ", + "start": 395, + "end": 403, + "loc": { + "start": { + "line": 18, + "column": 2 + }, + "end": { + "line": 18, + "column": 10 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 417, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 408, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 20, + "column": 9 + } + }, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start": 96, + "end": 139, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 6, + "column": 3 + } + }, + "id": { + "type": "Identifier", + "start": 101, + "end": 102, + "loc": { + "start": { + "line": 3, + "column": 6 + }, + "end": { + "line": 3, + "column": 7 + } + }, + "name": "A" + }, + "typeAnnotation": { + "type": "TSMappedType", + "start": 105, + "end": 138, + "loc": { + "start": { + "line": 3, + "column": 10 + }, + "end": { + "line": 6, + "column": 2 + } + }, + "typeParameter": { + "type": "TSTypeParameter", + "start": 110, + "end": 116, + "loc": { + "start": { + "line": 4, + "column": 3 + }, + "end": { + "line": 4, + "column": 9 + } + }, + "name": "K", + "constraint": { + "type": "TSTypeReference", + "start": 115, + "end": 116, + "loc": { + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 4, + "column": 9 + } + }, + "typeName": { + "type": "Identifier", + "start": 115, + "end": 116, + "loc": { + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 4, + "column": 9 + } + }, + "name": "T" + } + } + }, + "nameType": null, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 119, + "end": 120, + "loc": { + "start": { + "line": 4, + "column": 12 + }, + "end": { + "line": 4, + "column": 13 + } + }, + "typeName": { + "type": "Identifier", + "start": 119, + "end": 120, + "loc": { + "start": { + "line": 4, + "column": 12 + }, + "end": { + "line": 4, + "column": 13 + } + }, + "name": "V" + } + } + }, + "leadingComments": [ + { + "type": "Line", + "value": " a run of comments trailing a mapped member stays distinct, one per line", + "start": 20, + "end": 94 + } + ] + }, + { + "type": "TSTypeAliasDeclaration", + "start": 207, + "end": 253, + "loc": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 12, + "column": 3 + } + }, + "id": { + "type": "Identifier", + "start": 212, + "end": 213, + "loc": { + "start": { + "line": 9, + "column": 6 + }, + "end": { + "line": 9, + "column": 7 + } + }, + "name": "B" + }, + "typeAnnotation": { + "type": "TSMappedType", + "start": 216, + "end": 252, + "loc": { + "start": { + "line": 9, + "column": 10 + }, + "end": { + "line": 12, + "column": 2 + } + }, + "typeParameter": { + "type": "TSTypeParameter", + "start": 221, + "end": 227, + "loc": { + "start": { + "line": 10, + "column": 3 + }, + "end": { + "line": 10, + "column": 9 + } + }, + "name": "K", + "constraint": { + "type": "TSTypeReference", + "start": 226, + "end": 227, + "loc": { + "start": { + "line": 10, + "column": 8 + }, + "end": { + "line": 10, + "column": 9 + } + }, + "typeName": { + "type": "Identifier", + "start": 226, + "end": 227, + "loc": { + "start": { + "line": 10, + "column": 8 + }, + "end": { + "line": 10, + "column": 9 + } + }, + "name": "T" + } + } + }, + "nameType": null, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 230, + "end": 231, + "loc": { + "start": { + "line": 10, + "column": 12 + }, + "end": { + "line": 10, + "column": 13 + } + }, + "typeName": { + "type": "Identifier", + "start": 230, + "end": 231, + "loc": { + "start": { + "line": 10, + "column": 12 + }, + "end": { + "line": 10, + "column": 13 + } + }, + "name": "V" + }, + "trailingComments": [ + { + "type": "Block", + "value": " c3 ", + "start": 232, + "end": 240 + } + ] + } + }, + "leadingComments": [ + { + "type": "Line", + "value": " c1", + "start": 122, + "end": 127 + }, + { + "type": "Line", + "value": " c2", + "start": 130, + "end": 135 + }, + { + "type": "Line", + "value": " an own-line comment after an inline block keeps its own line", + "start": 142, + "end": 205 + } + ] + }, + { + "type": "TSTypeAliasDeclaration", + "start": 361, + "end": 407, + "loc": { + "start": { + "line": 16, + "column": 1 + }, + "end": { + "line": 19, + "column": 3 + } + }, + "id": { + "type": "Identifier", + "start": 366, + "end": 367, + "loc": { + "start": { + "line": 16, + "column": 6 + }, + "end": { + "line": 16, + "column": 7 + } + }, + "name": "C" + }, + "typeAnnotation": { + "type": "TSMappedType", + "start": 370, + "end": 406, + "loc": { + "start": { + "line": 16, + "column": 10 + }, + "end": { + "line": 19, + "column": 2 + } + }, + "typeParameter": { + "type": "TSTypeParameter", + "start": 375, + "end": 381, + "loc": { + "start": { + "line": 17, + "column": 3 + }, + "end": { + "line": 17, + "column": 9 + } + }, + "name": "K", + "constraint": { + "type": "TSTypeReference", + "start": 380, + "end": 381, + "loc": { + "start": { + "line": 17, + "column": 8 + }, + "end": { + "line": 17, + "column": 9 + } + }, + "typeName": { + "type": "Identifier", + "start": 380, + "end": 381, + "loc": { + "start": { + "line": 17, + "column": 8 + }, + "end": { + "line": 17, + "column": 9 + } + }, + "name": "T" + } + } + }, + "nameType": null, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 384, + "end": 385, + "loc": { + "start": { + "line": 17, + "column": 12 + }, + "end": { + "line": 17, + "column": 13 + } + }, + "typeName": { + "type": "Identifier", + "start": 384, + "end": 385, + "loc": { + "start": { + "line": 17, + "column": 12 + }, + "end": { + "line": 17, + "column": 13 + } + }, + "name": "V" + } + } + }, + "leadingComments": [ + { + "type": "Line", + "value": " c4", + "start": 244, + "end": 249 + }, + { + "type": "Line", + "value": " a block written on its own line defers behind the line comment ahead of it,", + "start": 256, + "end": 334 + }, + { + "type": "Line", + "value": " keeping source order", + "start": 336, + "end": 359 + } + ], + "trailingComments": [ + { + "type": "Line", + "value": " c5", + "start": 387, + "end": 392 + }, + { + "type": "Block", + "value": " c6 ", + "start": 395, + "end": 403 + } + ] + } + ], + "sourceType": "module" + }, + "attributes": [ + { + "type": "Attribute", + "start": 8, + "end": 17, + "name": "lang", + "name_loc": { + "start": { + "line": 1, + "column": 8, + "character": 8 + }, + "end": { + "line": 1, + "column": 12, + "character": 12 + } + }, + "value": [ + { + "start": 14, + "end": 16, + "type": "Text", + "raw": "ts", + "data": "ts" + } + ] + } + ] + } +} diff --git a/tests/fixtures/typescript/types/mapped_value_trailing_comment_run/input.svelte b/tests/fixtures/typescript/types/mapped_value_trailing_comment_run/input.svelte new file mode 100644 index 000000000..fd6a2bd56 --- /dev/null +++ b/tests/fixtures/typescript/types/mapped_value_trailing_comment_run/input.svelte @@ -0,0 +1,20 @@ + diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_run/input.svelte b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run/input.svelte deleted file mode 100644 index aeedde134..000000000 --- a/tests/fixtures/typescript/types/type_suffix_trailing_comment_run/input.svelte +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_run/unformatted_inside_suffix.svelte b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run/unformatted_inside_suffix.svelte deleted file mode 100644 index c17e58c75..000000000 --- a/tests/fixtures/typescript/types/type_suffix_trailing_comment_run/unformatted_inside_suffix.svelte +++ /dev/null @@ -1,20 +0,0 @@ - diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/README.md b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/README.md new file mode 100644 index 000000000..c1fed35e1 --- /dev/null +++ b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/README.md @@ -0,0 +1,35 @@ +# Divergence: type-suffix trailing comment **run** stays inside the brackets + +The multi-comment face of +[type_suffix_trailing_comment](../../declarations/variable/type_suffix_trailing_comment_prettier_divergence/), +in type-alias position: a run of line comments at the end of an indexed access's brackets +(`T[K // c1⏎// c2⏎]`) or of a retained paren shell (`(U // c4⏎// c5⏎)[]`). tsv keeps the whole +run inside the region, distinct, one comment per line. **Prettier carries the run out past the +`;`**, where the first comment trails the statement and the rest read as leading whatever +follows. + +```ts +// tsv (run stays inside) // prettier (carried out past the `;`) +type A1 = T[ type A1 = T[K]; // c1 + K // c1 // c2 + // c2 +]; +``` + +## Reason + +Same rule as the single-comment fixture — the four sibling bracketed type regions (`{}`, `<>`, +tuple `[]`, function-type `()`) already keep a trailing comment inside in **both** formatters, +so the indexed access and the stripped paren shell are prettier's two exceptions and tsv's +convergence targets. + +The run is where the cost of carrying comments out shows up directly: once a run is on a line +it shares with another construct's escaped comment, the two render back to back and the second +`//` becomes text of the first. Keeping the run inside the brackets means the collision cannot +be assembled in the first place. + +`unformatted_ours_flat.svelte` carries the flat authoring, which reaches `input` under tsv +only. + +See [conformance_prettier.md §Comment Position Philosophy](../../../../../docs/conformance_prettier.md#comment-position-philosophy) +and [conformance_prettier_ts_comments.md §Comment relocation](../../../../../docs/conformance_prettier_ts_comments.md#comment-relocation). diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_run/expected.json b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/expected.json similarity index 57% rename from tests/fixtures/typescript/types/type_suffix_trailing_comment_run/expected.json rename to tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/expected.json index ac443af43..44c9b1752 100644 --- a/tests/fixtures/typescript/types/type_suffix_trailing_comment_run/expected.json +++ b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/expected.json @@ -2,7 +2,7 @@ "css": null, "js": [], "start": 0, - "end": 474, + "end": 425, "type": "Root", "fragment": { "type": "Fragment", @@ -12,9 +12,9 @@ "comments": [ { "type": "Line", - "value": " a comment in an indexed-access index-to-`]` gap escapes the brackets and trails the", + "value": " a run at the end of an indexed access's brackets stays inside them, distinct,", "start": 20, - "end": 106, + "end": 100, "loc": { "start": { "line": 2, @@ -22,15 +22,15 @@ }, "end": { "line": 2, - "column": 87 + "column": 81 } } }, { "type": "Line", - "value": " statement; a run of them stays distinct, one per line", - "start": 108, - "end": 164, + "value": " one comment per line (unformatted_ours_flat carries the flat authoring)", + "start": 102, + "end": 176, "loc": { "start": { "line": 3, @@ -38,70 +38,54 @@ }, "end": { "line": 3, - "column": 57 - } - } - }, - { - "type": "Line", - "value": " (unformatted_inside_suffix carries the authored placement)", - "start": 166, - "end": 227, - "loc": { - "start": { - "line": 4, - "column": 1 - }, - "end": { - "line": 4, - "column": 62 + "column": 75 } } }, { "type": "Line", "value": " c1", - "start": 245, - "end": 250, + "start": 195, + "end": 200, "loc": { "start": { "line": 5, - "column": 17 + "column": 4 }, "end": { "line": 5, - "column": 22 + "column": 9 } } }, { "type": "Line", "value": " c2", - "start": 252, - "end": 257, + "start": 203, + "end": 208, "loc": { "start": { "line": 6, - "column": 1 + "column": 2 }, "end": { "line": 6, - "column": 6 + "column": 7 } } }, { "type": "Line", "value": " the same gap holding a single comment", - "start": 260, - "end": 300, + "start": 215, + "end": 255, "loc": { "start": { - "line": 8, + "line": 9, "column": 1 }, "end": { - "line": 8, + "line": 9, "column": 41 } } @@ -109,80 +93,80 @@ { "type": "Line", "value": " c3", - "start": 318, - "end": 323, + "start": 274, + "end": 279, "loc": { "start": { - "line": 9, - "column": 17 + "line": 11, + "column": 4 }, "end": { - "line": 9, - "column": 22 + "line": 11, + "column": 9 } } }, { "type": "Line", - "value": " a redundant paren's trailing gap escapes the same way once the parens are stripped", - "start": 326, - "end": 411, + "value": " a retained paren shell's trailing gap behaves the same way", + "start": 286, + "end": 347, "loc": { "start": { - "line": 11, + "line": 14, "column": 1 }, "end": { - "line": 11, - "column": 86 + "line": 14, + "column": 62 } } }, { "type": "Line", "value": " c4", - "start": 428, - "end": 433, + "start": 365, + "end": 370, "loc": { "start": { - "line": 12, - "column": 16 + "line": 16, + "column": 4 }, "end": { - "line": 12, - "column": 21 + "line": 16, + "column": 9 } } }, { "type": "Line", "value": " c5", - "start": 435, - "end": 440, + "start": 373, + "end": 378, "loc": { "start": { - "line": 13, - "column": 1 + "line": 17, + "column": 2 }, "end": { - "line": 13, - "column": 6 + "line": 17, + "column": 7 } } }, { "type": "Line", "value": " c6", - "start": 458, - "end": 463, + "start": 403, + "end": 408, "loc": { "start": { - "line": 15, - "column": 16 + "line": 21, + "column": 4 }, "end": { - "line": 15, - "column": 21 + "line": 21, + "column": 9 } } } @@ -190,48 +174,48 @@ "instance": { "type": "Script", "start": 0, - "end": 473, + "end": 424, "context": "default", "content": { "type": "Program", "start": 18, - "end": 464, + "end": 415, "loc": { "start": { "line": 1, "column": 0 }, "end": { - "line": 16, + "line": 23, "column": 9 } }, "body": [ { "type": "TSTypeAliasDeclaration", - "start": 229, - "end": 244, + "start": 178, + "end": 212, "loc": { "start": { - "line": 5, + "line": 4, "column": 1 }, "end": { - "line": 5, - "column": 16 + "line": 7, + "column": 3 } }, "id": { "type": "Identifier", - "start": 234, - "end": 236, + "start": 183, + "end": 185, "loc": { "start": { - "line": 5, + "line": 4, "column": 6 }, "end": { - "line": 5, + "line": 4, "column": 8 } }, @@ -239,43 +223,43 @@ }, "typeAnnotation": { "type": "TSIndexedAccessType", - "start": 239, - "end": 243, + "start": 188, + "end": 211, "loc": { "start": { - "line": 5, + "line": 4, "column": 11 }, "end": { - "line": 5, - "column": 15 + "line": 7, + "column": 2 } }, "objectType": { "type": "TSTypeReference", - "start": 239, - "end": 240, + "start": 188, + "end": 189, "loc": { "start": { - "line": 5, + "line": 4, "column": 11 }, "end": { - "line": 5, + "line": 4, "column": 12 } }, "typeName": { "type": "Identifier", - "start": 239, - "end": 240, + "start": 188, + "end": 189, "loc": { "start": { - "line": 5, + "line": 4, "column": 11 }, "end": { - "line": 5, + "line": 4, "column": 12 } }, @@ -284,90 +268,84 @@ }, "indexType": { "type": "TSTypeReference", - "start": 241, - "end": 242, + "start": 193, + "end": 194, "loc": { "start": { "line": 5, - "column": 13 + "column": 2 }, "end": { "line": 5, - "column": 14 + "column": 3 } }, "typeName": { "type": "Identifier", - "start": 241, - "end": 242, + "start": 193, + "end": 194, "loc": { "start": { "line": 5, - "column": 13 + "column": 2 }, "end": { "line": 5, - "column": 14 + "column": 3 } }, "name": "K" - } + }, + "trailingComments": [ + { + "type": "Line", + "value": " c1", + "start": 195, + "end": 200 + } + ] } }, "leadingComments": [ { "type": "Line", - "value": " a comment in an indexed-access index-to-`]` gap escapes the brackets and trails the", + "value": " a run at the end of an indexed access's brackets stays inside them, distinct,", "start": 20, - "end": 106 + "end": 100 }, { "type": "Line", - "value": " statement; a run of them stays distinct, one per line", - "start": 108, - "end": 164 - }, - { - "type": "Line", - "value": " (unformatted_inside_suffix carries the authored placement)", - "start": 166, - "end": 227 - } - ], - "trailingComments": [ - { - "type": "Line", - "value": " c1", - "start": 245, - "end": 250 + "value": " one comment per line (unformatted_ours_flat carries the flat authoring)", + "start": 102, + "end": 176 } ] }, { "type": "TSTypeAliasDeclaration", - "start": 302, - "end": 317, + "start": 257, + "end": 283, "loc": { "start": { - "line": 9, + "line": 10, "column": 1 }, "end": { - "line": 9, - "column": 16 + "line": 12, + "column": 3 } }, "id": { "type": "Identifier", - "start": 307, - "end": 309, + "start": 262, + "end": 264, "loc": { "start": { - "line": 9, + "line": 10, "column": 6 }, "end": { - "line": 9, + "line": 10, "column": 8 } }, @@ -375,43 +353,43 @@ }, "typeAnnotation": { "type": "TSIndexedAccessType", - "start": 312, - "end": 316, + "start": 267, + "end": 282, "loc": { "start": { - "line": 9, + "line": 10, "column": 11 }, "end": { - "line": 9, - "column": 15 + "line": 12, + "column": 2 } }, "objectType": { "type": "TSTypeReference", - "start": 312, - "end": 313, + "start": 267, + "end": 268, "loc": { "start": { - "line": 9, + "line": 10, "column": 11 }, "end": { - "line": 9, + "line": 10, "column": 12 } }, "typeName": { "type": "Identifier", - "start": 312, - "end": 313, + "start": 267, + "end": 268, "loc": { "start": { - "line": 9, + "line": 10, "column": 11 }, "end": { - "line": 9, + "line": 10, "column": 12 } }, @@ -420,84 +398,84 @@ }, "indexType": { "type": "TSTypeReference", - "start": 314, - "end": 315, + "start": 272, + "end": 273, "loc": { "start": { - "line": 9, - "column": 13 + "line": 11, + "column": 2 }, "end": { - "line": 9, - "column": 14 + "line": 11, + "column": 3 } }, "typeName": { "type": "Identifier", - "start": 314, - "end": 315, + "start": 272, + "end": 273, "loc": { "start": { - "line": 9, - "column": 13 + "line": 11, + "column": 2 }, "end": { - "line": 9, - "column": 14 + "line": 11, + "column": 3 } }, "name": "K" - } + }, + "trailingComments": [ + { + "type": "Line", + "value": " c3", + "start": 274, + "end": 279 + } + ] } }, "leadingComments": [ { "type": "Line", "value": " c2", - "start": 252, - "end": 257 + "start": 203, + "end": 208 }, { "type": "Line", "value": " the same gap holding a single comment", - "start": 260, - "end": 300 - } - ], - "trailingComments": [ - { - "type": "Line", - "value": " c3", - "start": 318, - "end": 323 + "start": 215, + "end": 255 } ] }, { "type": "TSTypeAliasDeclaration", - "start": 413, - "end": 427, + "start": 349, + "end": 384, "loc": { "start": { - "line": 12, + "line": 15, "column": 1 }, "end": { - "line": 12, - "column": 15 + "line": 18, + "column": 5 } }, "id": { "type": "Identifier", - "start": 418, - "end": 420, + "start": 354, + "end": 356, "loc": { "start": { - "line": 12, + "line": 15, "column": 6 }, "end": { - "line": 12, + "line": 15, "column": 8 } }, @@ -505,92 +483,107 @@ }, "typeAnnotation": { "type": "TSArrayType", - "start": 423, - "end": 426, + "start": 359, + "end": 383, "loc": { "start": { - "line": 12, + "line": 15, "column": 11 }, "end": { - "line": 12, - "column": 14 + "line": 18, + "column": 4 } }, "elementType": { - "type": "TSTypeReference", - "start": 423, - "end": 424, + "type": "TSParenthesizedType", + "start": 359, + "end": 381, "loc": { "start": { - "line": 12, + "line": 15, "column": 11 }, "end": { - "line": 12, - "column": 12 + "line": 18, + "column": 2 } }, - "typeName": { - "type": "Identifier", - "start": 423, - "end": 424, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 363, + "end": 364, "loc": { "start": { - "line": 12, - "column": 11 + "line": 16, + "column": 2 }, "end": { - "line": 12, - "column": 12 + "line": 16, + "column": 3 } }, - "name": "U" + "typeName": { + "type": "Identifier", + "start": 363, + "end": 364, + "loc": { + "start": { + "line": 16, + "column": 2 + }, + "end": { + "line": 16, + "column": 3 + } + }, + "name": "U" + }, + "trailingComments": [ + { + "type": "Line", + "value": " c4", + "start": 365, + "end": 370 + } + ] } } }, "leadingComments": [ { "type": "Line", - "value": " a redundant paren's trailing gap escapes the same way once the parens are stripped", - "start": 326, - "end": 411 - } - ], - "trailingComments": [ - { - "type": "Line", - "value": " c4", - "start": 428, - "end": 433 + "value": " a retained paren shell's trailing gap behaves the same way", + "start": 286, + "end": 347 } ] }, { "type": "TSTypeAliasDeclaration", - "start": 443, - "end": 457, + "start": 387, + "end": 414, "loc": { "start": { - "line": 15, + "line": 20, "column": 1 }, "end": { - "line": 15, - "column": 15 + "line": 22, + "column": 5 } }, "id": { "type": "Identifier", - "start": 448, - "end": 450, + "start": 392, + "end": 394, "loc": { "start": { - "line": 15, + "line": 20, "column": 6 }, "end": { - "line": 15, + "line": 20, "column": 8 } }, @@ -598,47 +591,70 @@ }, "typeAnnotation": { "type": "TSArrayType", - "start": 453, - "end": 456, + "start": 397, + "end": 413, "loc": { "start": { - "line": 15, + "line": 20, "column": 11 }, "end": { - "line": 15, - "column": 14 + "line": 22, + "column": 4 } }, "elementType": { - "type": "TSTypeReference", - "start": 453, - "end": 454, + "type": "TSParenthesizedType", + "start": 397, + "end": 411, "loc": { "start": { - "line": 15, + "line": 20, "column": 11 }, "end": { - "line": 15, - "column": 12 + "line": 22, + "column": 2 } }, - "typeName": { - "type": "Identifier", - "start": 453, - "end": 454, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 401, + "end": 402, "loc": { "start": { - "line": 15, - "column": 11 + "line": 21, + "column": 2 }, "end": { - "line": 15, - "column": 12 + "line": 21, + "column": 3 } }, - "name": "U" + "typeName": { + "type": "Identifier", + "start": 401, + "end": 402, + "loc": { + "start": { + "line": 21, + "column": 2 + }, + "end": { + "line": 21, + "column": 3 + } + }, + "name": "U" + }, + "trailingComments": [ + { + "type": "Line", + "value": " c6", + "start": 403, + "end": 408 + } + ] } } }, @@ -646,16 +662,8 @@ { "type": "Line", "value": " c5", - "start": 435, - "end": 440 - } - ], - "trailingComments": [ - { - "type": "Line", - "value": " c6", - "start": 458, - "end": 463 + "start": 373, + "end": 378 } ] } diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/input.svelte b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/input.svelte new file mode 100644 index 000000000..6beca5235 --- /dev/null +++ b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/input.svelte @@ -0,0 +1,23 @@ + diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/output_prettier.svelte b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/output_prettier.svelte new file mode 100644 index 000000000..5efef5afe --- /dev/null +++ b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/output_prettier.svelte @@ -0,0 +1,15 @@ + diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/unformatted_ours_flat.svelte b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/unformatted_ours_flat.svelte new file mode 100644 index 000000000..30b9eb114 --- /dev/null +++ b/tests/fixtures/typescript/types/type_suffix_trailing_comment_run_prettier_divergence/unformatted_ours_flat.svelte @@ -0,0 +1,19 @@ + diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/README.md b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/README.md new file mode 100644 index 000000000..66b92637c --- /dev/null +++ b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/README.md @@ -0,0 +1,43 @@ +# Divergence: member-position type-suffix trailing comment stays inside its region + +The member-position face of +[type_suffix_trailing_comment](../../declarations/variable/type_suffix_trailing_comment_prettier_divergence/): +a trailing line comment at the end of an indexed access's brackets where the access is a +**union member** (`T[K // c1⏎] | B`), or in a redundant paren shell around the **last** (or +sole) member of a union / intersection (`B | (A // c2⏎)`). tsv keeps the comment inside the +region and drops the closer to its own line. **Prettier carries it out** — to trail the +member (`| T[K] // c1`), or past the `;` for the last member (`type A2 = B | A; // c2`), +stripping the shell on the way. + +```ts +// tsv (comment stays inside) // prettier (carried out past the `;`) +type A2 = type A2 = B | A; // c2 + | B + | ( + A // c2 + ); +``` + +## Reason + +Same rule as the alias/annotation-position fixtures — the bracketed type regions keep a +trailing comment inside in both formatters, so the indexed access answers the question the +same way in member position; carrying it out re-binds the comment from the index to the whole +member. + +The **last-member paren shell** is where the carve-out that lets a member shell strip +(`(a // c⏎) | b` → `| a // c⏎| b`, matching prettier — +[union_intersection_parens_line_comment](../union_intersection_parens_line_comment/)) reaches +the end of its argument. That strip is lossless only because a per-member break ends the line +right after the member, flushing the deferred comment where it was written. No separator +follows the last member, so its line ends only after the `;` — the escaped comment re-binds +to the whole statement, and the break the shell forced is one the reparse cannot reproduce, +the parens being gone: prettier's own form here is **non-idempotent** (`type A2 =⏎↹| B⏎↹| A; // c2` +collapses to `type A2 = B | A; // c2` on its second pass). Retaining the shell keeps the +comment inside the construct it was written in and is a fixed point in one pass. + +`unformatted_ours_flat.svelte` carries the flat authorings (plus the one-member-union `|` +for `A4`), which reach `input` under tsv only. + +See [conformance_prettier.md §Comment Position Philosophy](../../../../../docs/conformance_prettier.md#comment-position-philosophy) +and [conformance_prettier_ts_comments.md §Comment relocation](../../../../../docs/conformance_prettier_ts_comments.md#comment-relocation). diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/audit_signature.txt b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/audit_signature.txt new file mode 100644 index 000000000..fdfdeb96d --- /dev/null +++ b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/audit_signature.txt @@ -0,0 +1,26 @@ +# Auto-generated prettier chain signature. Do not edit manually. +# Regenerate: deno task fixtures:update:formatted +# +# Each %%PASS=N%% section is exactly prettier^N(output_prettier.). +# This file exists when prettier is non-idempotent on output_prettier.* — it pins +# the full chain so the audit recognizes the case and the validator catches drift. +# See docs/fixture_overview.md (rule F4) and crates/tsv_debug/src/fixtures/audit_signature.rs. + +%%PASS=2 (fixed point)%% + diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/expected.json b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/expected.json new file mode 100644 index 000000000..e71bd1c30 --- /dev/null +++ b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/expected.json @@ -0,0 +1,784 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 592, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " an indexed access as a union member keeps a trailing line comment inside", + "start": 20, + "end": 95, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 76 + } + } + }, + { + "type": "Line", + "value": " its brackets, like every bracketed type region", + "start": 97, + "end": 146, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 50 + } + } + }, + { + "type": "Line", + "value": " c1", + "start": 171, + "end": 176, + "loc": { + "start": { + "line": 6, + "column": 6 + }, + "end": { + "line": 6, + "column": 11 + } + } + }, + { + "type": "Line", + "value": " the LAST member's redundant paren shell is retained: no separator follows", + "start": 192, + "end": 268, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 77 + } + } + }, + { + "type": "Line", + "value": " it, so a stripped shell's comment would escape past the `;`", + "start": 270, + "end": 332, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 63 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 362, + "end": 367, + "loc": { + "start": { + "line": 15, + "column": 6 + }, + "end": { + "line": 15, + "column": 11 + } + } + }, + { + "type": "Line", + "value": " c3", + "start": 400, + "end": 405, + "loc": { + "start": { + "line": 20, + "column": 5 + }, + "end": { + "line": 20, + "column": 10 + } + } + }, + { + "type": "Line", + "value": " a sole parenthesized member is retained the same way (the one-member", + "start": 413, + "end": 484, + "loc": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, + "column": 72 + } + } + }, + { + "type": "Line", + "value": " union's `|` drops — unformatted_ours_flat carries that authoring)", + "start": 486, + "end": 554, + "loc": { + "start": { + "line": 24, + "column": 1 + }, + "end": { + "line": 24, + "column": 69 + } + } + }, + { + "type": "Line", + "value": " c4", + "start": 572, + "end": 577, + "loc": { + "start": { + "line": 26, + "column": 4 + }, + "end": { + "line": 26, + "column": 9 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 591, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 582, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 28, + "column": 9 + } + }, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start": 148, + "end": 189, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 8, + "column": 6 + } + }, + "id": { + "type": "Identifier", + "start": 153, + "end": 155, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 8 + } + }, + "name": "A1" + }, + "typeAnnotation": { + "type": "TSUnionType", + "start": 160, + "end": 188, + "loc": { + "start": { + "line": 5, + "column": 2 + }, + "end": { + "line": 8, + "column": 5 + } + }, + "types": [ + { + "type": "TSIndexedAccessType", + "start": 162, + "end": 182, + "loc": { + "start": { + "line": 5, + "column": 4 + }, + "end": { + "line": 7, + "column": 5 + } + }, + "objectType": { + "type": "TSTypeReference", + "start": 162, + "end": 163, + "loc": { + "start": { + "line": 5, + "column": 4 + }, + "end": { + "line": 5, + "column": 5 + } + }, + "typeName": { + "type": "Identifier", + "start": 162, + "end": 163, + "loc": { + "start": { + "line": 5, + "column": 4 + }, + "end": { + "line": 5, + "column": 5 + } + }, + "name": "T" + } + }, + "indexType": { + "type": "TSTypeReference", + "start": 169, + "end": 170, + "loc": { + "start": { + "line": 6, + "column": 4 + }, + "end": { + "line": 6, + "column": 5 + } + }, + "typeName": { + "type": "Identifier", + "start": 169, + "end": 170, + "loc": { + "start": { + "line": 6, + "column": 4 + }, + "end": { + "line": 6, + "column": 5 + } + }, + "name": "K" + }, + "trailingComments": [ + { + "type": "Line", + "value": " c1", + "start": 171, + "end": 176 + } + ] + } + }, + { + "type": "TSTypeReference", + "start": 187, + "end": 188, + "loc": { + "start": { + "line": 8, + "column": 4 + }, + "end": { + "line": 8, + "column": 5 + } + }, + "typeName": { + "type": "Identifier", + "start": 187, + "end": 188, + "loc": { + "start": { + "line": 8, + "column": 4 + }, + "end": { + "line": 8, + "column": 5 + } + }, + "name": "B" + } + } + ] + }, + "leadingComments": [ + { + "type": "Line", + "value": " an indexed access as a union member keeps a trailing line comment inside", + "start": 20, + "end": 95 + }, + { + "type": "Line", + "value": " its brackets, like every bracketed type region", + "start": 97, + "end": 146 + } + ] + }, + { + "type": "TSTypeAliasDeclaration", + "start": 334, + "end": 374, + "loc": { + "start": { + "line": 12, + "column": 1 + }, + "end": { + "line": 16, + "column": 6 + } + }, + "id": { + "type": "Identifier", + "start": 339, + "end": 341, + "loc": { + "start": { + "line": 12, + "column": 6 + }, + "end": { + "line": 12, + "column": 8 + } + }, + "name": "A2" + }, + "typeAnnotation": { + "type": "TSUnionType", + "start": 346, + "end": 373, + "loc": { + "start": { + "line": 13, + "column": 2 + }, + "end": { + "line": 16, + "column": 5 + } + }, + "types": [ + { + "type": "TSTypeReference", + "start": 348, + "end": 349, + "loc": { + "start": { + "line": 13, + "column": 4 + }, + "end": { + "line": 13, + "column": 5 + } + }, + "typeName": { + "type": "Identifier", + "start": 348, + "end": 349, + "loc": { + "start": { + "line": 13, + "column": 4 + }, + "end": { + "line": 13, + "column": 5 + } + }, + "name": "B" + } + }, + { + "type": "TSParenthesizedType", + "start": 354, + "end": 373, + "loc": { + "start": { + "line": 14, + "column": 4 + }, + "end": { + "line": 16, + "column": 5 + } + }, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 360, + "end": 361, + "loc": { + "start": { + "line": 15, + "column": 4 + }, + "end": { + "line": 15, + "column": 5 + } + }, + "typeName": { + "type": "Identifier", + "start": 360, + "end": 361, + "loc": { + "start": { + "line": 15, + "column": 4 + }, + "end": { + "line": 15, + "column": 5 + } + }, + "name": "A" + }, + "trailingComments": [ + { + "type": "Line", + "value": " c2", + "start": 362, + "end": 367 + } + ] + } + } + ] + }, + "leadingComments": [ + { + "type": "Line", + "value": " the LAST member's redundant paren shell is retained: no separator follows", + "start": 192, + "end": 268 + }, + { + "type": "Line", + "value": " it, so a stripped shell's comment would escape past the `;`", + "start": 270, + "end": 332 + } + ] + }, + { + "type": "TSTypeAliasDeclaration", + "start": 377, + "end": 410, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 21, + "column": 4 + } + }, + "id": { + "type": "Identifier", + "start": 382, + "end": 384, + "loc": { + "start": { + "line": 18, + "column": 6 + }, + "end": { + "line": 18, + "column": 8 + } + }, + "name": "A3" + }, + "typeAnnotation": { + "type": "TSIntersectionType", + "start": 387, + "end": 409, + "loc": { + "start": { + "line": 18, + "column": 11 + }, + "end": { + "line": 21, + "column": 3 + } + }, + "types": [ + { + "type": "TSTypeReference", + "start": 387, + "end": 388, + "loc": { + "start": { + "line": 18, + "column": 11 + }, + "end": { + "line": 18, + "column": 12 + } + }, + "typeName": { + "type": "Identifier", + "start": 387, + "end": 388, + "loc": { + "start": { + "line": 18, + "column": 11 + }, + "end": { + "line": 18, + "column": 12 + } + }, + "name": "B" + } + }, + { + "type": "TSParenthesizedType", + "start": 393, + "end": 409, + "loc": { + "start": { + "line": 19, + "column": 2 + }, + "end": { + "line": 21, + "column": 3 + } + }, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 398, + "end": 399, + "loc": { + "start": { + "line": 20, + "column": 3 + }, + "end": { + "line": 20, + "column": 4 + } + }, + "typeName": { + "type": "Identifier", + "start": 398, + "end": 399, + "loc": { + "start": { + "line": 20, + "column": 3 + }, + "end": { + "line": 20, + "column": 4 + } + }, + "name": "A" + }, + "trailingComments": [ + { + "type": "Line", + "value": " c3", + "start": 400, + "end": 405 + } + ] + } + } + ] + } + }, + { + "type": "TSTypeAliasDeclaration", + "start": 556, + "end": 581, + "loc": { + "start": { + "line": 25, + "column": 1 + }, + "end": { + "line": 27, + "column": 3 + } + }, + "id": { + "type": "Identifier", + "start": 561, + "end": 563, + "loc": { + "start": { + "line": 25, + "column": 6 + }, + "end": { + "line": 25, + "column": 8 + } + }, + "name": "A4" + }, + "typeAnnotation": { + "type": "TSParenthesizedType", + "start": 566, + "end": 580, + "loc": { + "start": { + "line": 25, + "column": 11 + }, + "end": { + "line": 27, + "column": 2 + } + }, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 570, + "end": 571, + "loc": { + "start": { + "line": 26, + "column": 2 + }, + "end": { + "line": 26, + "column": 3 + } + }, + "typeName": { + "type": "Identifier", + "start": 570, + "end": 571, + "loc": { + "start": { + "line": 26, + "column": 2 + }, + "end": { + "line": 26, + "column": 3 + } + }, + "name": "A" + }, + "trailingComments": [ + { + "type": "Line", + "value": " c4", + "start": 572, + "end": 577 + } + ] + } + }, + "leadingComments": [ + { + "type": "Line", + "value": " a sole parenthesized member is retained the same way (the one-member", + "start": 413, + "end": 484 + }, + { + "type": "Line", + "value": " union's `|` drops — unformatted_ours_flat carries that authoring)", + "start": 486, + "end": 554 + } + ] + } + ], + "sourceType": "module" + }, + "attributes": [ + { + "type": "Attribute", + "start": 8, + "end": 17, + "name": "lang", + "name_loc": { + "start": { + "line": 1, + "column": 8, + "character": 8 + }, + "end": { + "line": 1, + "column": 12, + "character": 12 + } + }, + "value": [ + { + "start": 14, + "end": 16, + "type": "Text", + "raw": "ts", + "data": "ts" + } + ] + } + ] + } +} diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/input.svelte b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/input.svelte new file mode 100644 index 000000000..5b85de5f3 --- /dev/null +++ b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/input.svelte @@ -0,0 +1,28 @@ + diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/output_prettier.svelte b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/output_prettier.svelte new file mode 100644 index 000000000..695e23740 --- /dev/null +++ b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/output_prettier.svelte @@ -0,0 +1,20 @@ + diff --git a/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/unformatted_ours_flat.svelte b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/unformatted_ours_flat.svelte new file mode 100644 index 000000000..36fa4fcfd --- /dev/null +++ b/tests/fixtures/typescript/types/type_suffix_trailing_comment_union_member_prettier_divergence/unformatted_ours_flat.svelte @@ -0,0 +1,19 @@ + From abf0b54e9f278b523fabad16a7821f6b5ec9c765 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Wed, 5 Aug 2026 00:12:54 -0400 Subject: [PATCH 2/2] repin gap count --- crates/tsv_debug/src/cli/commands/gap_audit_known.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/tsv_debug/src/cli/commands/gap_audit_known.txt b/crates/tsv_debug/src/cli/commands/gap_audit_known.txt index 44968c8a5..5df7c5029 100644 --- a/crates/tsv_debug/src/cli/commands/gap_audit_known.txt +++ b/crates/tsv_debug/src/cli/commands/gap_audit_known.txt @@ -14,7 +14,7 @@ # the gate rather than being pinned. # # Format: KINDSHAPEPAYLOADS -# shapes: 522 +# shapes: 508 DROPPED !)⟨⟩!. annotation,block,jsdoc_cast DROPPED &&⟨⟩␣ annotation,block,jsdoc_cast,multiline DROPPED &⟨⟩␣ annotation,block,jsdoc_cast,line,multiline