From 35252b8d569e3a763c22c10017e48a5cfe272579 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Wed, 17 Jun 2026 20:35:07 -0400 Subject: [PATCH] fix: collapsed comments in various structures --- CLAUDE.md | 2 +- README.md | 8 +- .../tsv_ts/src/printer/calls/arg_comments.rs | 46 + .../tsv_ts/src/printer/calls/arg_wrapping.rs | 49 +- .../src/printer/calls/call_formatting.rs | 117 ++- crates/tsv_ts/src/printer/calls/chain_args.rs | 30 +- .../src/printer/calls/new_expression.rs | 12 +- .../src/printer/chain/builder/helpers.rs | 165 +-- .../src/printer/chain/builder/member_only.rs | 78 +- .../tsv_ts/src/printer/chain/builder/mod.rs | 12 +- .../src/printer/expressions/assignment.rs | 24 +- .../src/printer/expressions/conditional.rs | 84 +- .../tsv_ts/src/printer/statements/variable.rs | 13 +- docs/architecture.md | 4 +- docs/conformance_prettier.md | 4 +- .../expected.json | 567 ++++++++++ .../input.svelte | 18 + .../unformatted_compact.svelte | 11 + .../README.md | 21 + .../expected.json | 559 ++++++++++ .../input.svelte | 19 + .../output_prettier.svelte | 17 + .../unformatted_ours_compact.svelte | 17 + .../expected.json | 874 ++++++++++++++++ .../input.svelte | 36 + .../expected.json | 482 +++++++++ .../input.svelte | 18 + .../unformatted_compact.svelte | 14 + .../expected.json | 611 +++++++++++ .../input.svelte | 23 + .../unformatted_compact.svelte | 17 + .../expected.json | 608 +++++++++++ .../input.svelte | 22 + .../unformatted_compact.svelte | 15 + .../README.md | 25 + .../expected.json | 981 ++++++++++++++++++ .../input.svelte | 31 + .../output_prettier.svelte | 31 + .../unformatted_ours_compact.svelte | 31 + 39 files changed, 5502 insertions(+), 194 deletions(-) create mode 100644 tests/fixtures/typescript/expressions/calls/chained/last_arg_block_then_line_comment/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/chained/last_arg_block_then_line_comment/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/chained/last_arg_block_then_line_comment/unformatted_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/README.md create mode 100644 tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/output_prettier.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/unformatted_ours_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/last_arg_trailing_then_dangling_comment/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/last_arg_trailing_then_dangling_comment/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_block_then_line_comment/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_block_then_line_comment/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_block_then_line_comment/unformatted_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/new_nonlast_arg_block_then_line_comment/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/new_nonlast_arg_block_then_line_comment/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/new_nonlast_arg_block_then_line_comment/unformatted_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_block_then_line_comment/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_block_then_line_comment/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_block_then_line_comment/unformatted_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/README.md create mode 100644 tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/expected.json create mode 100644 tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/input.svelte create mode 100644 tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/output_prettier.svelte create mode 100644 tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/unformatted_ours_compact.svelte diff --git a/CLAUDE.md b/CLAUDE.md index 72ae4a4b..eb80a3bc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ > a formatter, parser, and future linter + more for Svelte, TypeScript, and CSS -High-performance Rust parser as a drop-in replacement for Svelte's modern parser (acorn + acorn-typescript), paired with a formatter that took Prettier as its initial guide and still tracks it closely for the common case — while making deliberate, cataloged choices to diverge where tsv's own judgment is more defensible. +High-performance Rust parser as a drop-in replacement for Svelte's modern parser (acorn + acorn-typescript), paired with a formatter that took Prettier as its initial guide and still tracks it for the common case — while making deliberate, cataloged choices to diverge where tsv's own judgment is more defensible. **Non-configurable by design**: formatting options are fixed at Prettier's defaults except printWidth=100, useTabs=true, singleQuote=true, and bracketSpacing=false — no config files, CLI flags, or runtime options, ever (opinionated like `gofmt` and Black). See [Configuration](#configuration). diff --git a/README.md b/README.md index 917c4ca5..1e87d6cd 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ > a formatter, parser, and future linter + more for Svelte, TypeScript, and CSS - [tsv.fuz.dev](https://tsv.fuz.dev/) tsv is a toolchain for Svelte, TypeScript, and CSS, written in Rust. -The first release has a near-[Prettier](https://prettier.io/) formatter that -closely follows [prettier-plugin-svelte](https://github.com/sveltejs/prettier-plugin-svelte), +The first release has a near-[Prettier](https://prettier.io/) formatter, +similar to [prettier-plugin-svelte](https://github.com/sveltejs/prettier-plugin-svelte), and a drop-in replacement for [Svelte](https://svelte.dev/)'s parser + [acorn](https://github.com/acornjs/acorn) + [acorn-typescript](https://github.com/sveltejs/acorn-typescript). @@ -62,8 +62,8 @@ from anything that speaks C FFI. Deno's FFI is used in the benchmarks. ## Design - supports Svelte, TypeScript, CSS, JS, and HTML -- formatting tracks Prettier and prettier-plugin-svelte closely, but intentionally diverges - in some cases - see [docs/conformance_prettier.md](docs/conformance_prettier.md) +- formatting tracks Prettier and prettier-plugin-svelte for the common case, but intentionally + diverges in some cases - see [docs/conformance_prettier.md](docs/conformance_prettier.md) - tsv can generate a public JSON AST that should exactly match Svelte 5's modern AST with acorn and acorn-typescript (see [docs/conformance_svelte.md](docs/conformance_svelte.md)), diff --git a/crates/tsv_ts/src/printer/calls/arg_comments.rs b/crates/tsv_ts/src/printer/calls/arg_comments.rs index c5875783..d77bff00 100644 --- a/crates/tsv_ts/src/printer/calls/arg_comments.rs +++ b/crates/tsv_ts/src/printer/calls/arg_comments.rs @@ -446,6 +446,11 @@ where /// - `leading`: Comments on their own lines (not on same line as reference_pos) /// /// Uses `SmallVec` to avoid heap allocations for the common case (0-2 comments per range). +// TODO: this same-line-vs-own-line partition + its emit_* helpers are the call-arg +// instance of a rule also implemented in `conditional.rs` split_pre_operator_comments +// and `chain/builder/helpers.rs` push_gap_comments_and_break. Three parallel copies; +// unify if/when the Printer/ChainPrinter trait split and the differing emission +// shapes (operator / comma / dot) allow. pub(crate) struct PartitionedComments<'a> { pub trailing_line: SmallVec<[&'a internal::Comment; 2]>, pub trailing_block: SmallVec<[&'a internal::Comment; 2]>, @@ -538,6 +543,47 @@ impl<'a> PartitionedComments<'a> { } } + /// Emit a non-last arg's trailing comments split around its comma, then push the + /// comma itself: before-comma block comments trail the arg (`arg /* c */,`), + /// after-comma blocks and the same-line line comment follow the comma + /// (`arg, /* c */ // c2`). The caller adds the line break after. + /// + /// Unlike [`emit_trailing_comments`] (which the caller invokes *after* pushing + /// the comma, so every block lands after it), this keeps a before-comma block in + /// its authored position. Shared by the `new`-argument non-last paths + /// (`build_new_doc_with_wrapping` and `build_args_with_blank_lines`) so they + /// can't drift — both used to relocate the block past the comma. + pub fn emit_trailing_comments_around_comma( + &self, + parts: &mut Vec, + printer: &Printer<'_>, + arg_end: u32, + next_arg_start: u32, + ) { + let d = printer.d(); + let comma_pos = find_comma_pos(printer.source, arg_end, next_arg_start); + if let Some(cpos) = comma_pos { + for comment in &self.trailing_block { + if is_comment_before_comma(comment, cpos) { + parts.push(d.text(" ")); + parts.push(printer.build_comment_doc(comment)); + } + } + } + parts.push(d.text(",")); + if let Some(cpos) = comma_pos { + for comment in &self.trailing_block { + if is_comment_after_comma(comment, cpos) { + parts.push(d.text(" ")); + parts.push(printer.build_comment_doc(comment)); + } + } + } + for comment in &self.trailing_line { + parts.push(printer.build_trailing_line_comment_doc(comment)); + } + } + /// Emit own-line ("leading") comments after the last argument, past its /// trailing comma — each on its own line (hardline before). /// diff --git a/crates/tsv_ts/src/printer/calls/arg_wrapping.rs b/crates/tsv_ts/src/printer/calls/arg_wrapping.rs index c5ef75f9..6da235b4 100644 --- a/crates/tsv_ts/src/printer/calls/arg_wrapping.rs +++ b/crates/tsv_ts/src/printer/calls/arg_wrapping.rs @@ -702,15 +702,11 @@ pub(crate) fn build_args_joined_with_comments( ); let comma_pos = find_comma_pos(printer.source, arg_end, next_arg_start); - if pc.has_trailing_line() { - // Trailing line comments always force hardline: `arg, // comment\n` - parts.push(d.text(",")); - for comment in &pc.trailing_line { - parts.push(d.text(" ")); - parts.push(printer.build_comment_doc(comment)); - } - parts.push(d.hardline()); - } else if pc.has_trailing_block() { + if pc.has_trailing_line() || pc.has_trailing_block() { + // Block and line comments are emitted together (not either/or) so an + // arg carrying both — `a /* c */, // c2` — never drops the block. + let has_line = pc.has_trailing_line(); + // Before-comma block comments: `arg /* c */,` if let Some(cpos) = comma_pos { for comment in &pc.trailing_block { @@ -721,8 +717,26 @@ pub(crate) fn build_args_joined_with_comments( } } parts.push(d.text(",")); - if use_hardline { - // Hardline: break first, comment starts next line + + if has_line { + // A line comment runs to EOL and forces a hardline. After-comma + // blocks and the line comment stay on the comma line, in order: + // `arg, /* after */ // comment`. + if let Some(cpos) = comma_pos { + for comment in &pc.trailing_block { + if is_comment_after_comma(comment, cpos) { + parts.push(d.text(" ")); + parts.push(printer.build_comment_doc(comment)); + } + } + } + for comment in &pc.trailing_line { + parts.push(d.text(" ")); + parts.push(printer.build_comment_doc(comment)); + } + parts.push(d.hardline()); + } else if use_hardline { + // Block-only, hardline: break first, comment starts next line parts.push(d.hardline()); if let Some(cpos) = comma_pos { for comment in &pc.trailing_block { @@ -733,7 +747,7 @@ pub(crate) fn build_args_joined_with_comments( } } } else { - // Soft: comment stays inline after comma, break follows + // Block-only, soft: comment stays inline after comma, break follows if let Some(cpos) = comma_pos { for comment in &pc.trailing_block { if is_comment_after_comma(comment, cpos) { @@ -943,8 +957,15 @@ pub(super) fn build_args_with_blank_lines( next_start, ); - arg_parts.push(d.text(",")); - pc.emit_trailing_comments(&mut arg_parts, printer); + // Split trailing comments around the comma (shared with the `new` + // non-last path) so a before-comma block stays put instead of being + // relocated past the comma. + pc.emit_trailing_comments_around_comma( + &mut arg_parts, + printer, + arg_end, + next_start, + ); let next_has_blank = pc.has_blank_line_in_gap( printer.source, diff --git a/crates/tsv_ts/src/printer/calls/call_formatting.rs b/crates/tsv_ts/src/printer/calls/call_formatting.rs index 75c13150..bff76624 100644 --- a/crates/tsv_ts/src/printer/calls/call_formatting.rs +++ b/crates/tsv_ts/src/printer/calls/call_formatting.rs @@ -1093,23 +1093,13 @@ pub(super) fn build_call_doc_with_wrapping( force_expansion = true; } - if pc.has_trailing_line() { - // Trailing line comments: comma, comment, hardline. The comment - // goes through `line_suffix` (zero width) so it never counts - // against the argument's own group — a long trailing comment - // can't force a binary/conditional arg to break (prettier's - // `lineSuffix`). It still renders after the comma at end-of-line. - force_expansion = true; - arg_parts.push(d.text(",")); - for comment in &pc.trailing_line { - arg_parts.push(printer.build_trailing_line_comment_doc(comment)); - } - if has_blank_line { - arg_parts.push(d.literalline()); - } - arg_parts.push(d.hardline()); - } else if pc.has_trailing_block() { - // Trailing block comments: place relative to comma based on source position + if pc.has_trailing_line() || pc.has_trailing_block() { + // Trailing comments after this arg, in source order. Block and + // line comments are emitted together (not either/or) so an arg + // carrying both — `a /* c */, // c2` — never drops the block. + let has_line = pc.has_trailing_line(); + + // Before-comma block comments trail the arg, before the comma. if let Some(cpos) = comma_pos { for comment in &pc.trailing_block { if is_comment_before_comma(comment, cpos) { @@ -1119,10 +1109,25 @@ pub(super) fn build_call_doc_with_wrapping( } } arg_parts.push(d.text(",")); + + // Same-line line comments, after the comma. The comment goes + // through `line_suffix` (zero width) so it never counts against + // the argument's own group — a long trailing comment can't force + // a binary/conditional arg to break (prettier's `lineSuffix`). It + // forces the call to expand and renders after the comma at EOL. + if has_line { + force_expansion = true; + for comment in &pc.trailing_line { + arg_parts.push(printer.build_trailing_line_comment_doc(comment)); + } + } if has_blank_line { arg_parts.push(d.literalline()); } - arg_parts.push(d.line()); + // A line comment forces a hard break (it runs to EOL); a + // block-only arg uses a soft line so it can stay inline. + arg_parts.push(if has_line { d.hardline() } else { d.line() }); + // After-comma block comments (e.g., `arg1, /** @type {T} */ arg2`) // go AFTER the line break so they stay with the next arg when breaking. if let Some(cpos) = comma_pos { @@ -1176,21 +1181,36 @@ pub(super) fn build_call_doc_with_wrapping( paren_close, ); - // Own-line comments (block or line) after the last arg (before closing - // paren). These appear as siblings after the trailing comma, forcing - // expansion. Also handles spread with stripped parens via effective_arg_end. - if !pc.leading.is_empty() { - force_expansion = true; - pc.emit_last_arg_dangling_comments( - &mut arg_parts, - printer, - &mut has_trailing_comma_on_last, - ); + // Trailing comments after the last arg, before the closing paren, in + // source order: same-line block comments first (split around the source + // comma), then the same-line line comment (after the comma, via + // `line_suffix`), then own-line comments (each on its own line). Emitting + // same-line comments before own-line ones — and never dropping a block — + // avoids merging consecutive comments onto one line (which reverses their + // order) and content loss. The `new`/member-chain last-arg paths do the + // same via the shared emit_* helpers; this path keeps its own loop only + // for the block comma-split (`b /* c */,` vs past the trailing comma). + + // (1) Same-line block comments: before-comma blocks trail the arg, after- + // comma blocks are preserved past the trailing comma. Don't force + // expansion on their own — let width/source newlines decide. + // e.g., fn({short} /* c */) stays inline, fn({long...} /* c */) expands. + let comma_pos = find_comma_pos(printer.source, effective_arg_end, paren_close); + for comment in &pc.trailing_block { + if comma_pos.is_some_and(|cp| is_comment_after_comma(comment, cp)) { + last_after_comma.push(d.text(" ")); + last_after_comma.push(printer.build_comment_doc(comment)); + } else { + arg_parts.push(d.text(" ")); + arg_parts.push(printer.build_comment_doc(comment)); + } } + // (2) Same-line line comment, after the comma, via `line_suffix`. if pc.has_trailing_line() { if !has_trailing_comma_on_last { arg_parts.push(d.text(",")); + has_trailing_comma_on_last = true; } // Build comment docs: " // comment" for each @@ -1199,34 +1219,27 @@ pub(super) fn build_call_doc_with_wrapping( .iter() .flat_map(|c| [d.text(" "), printer.build_comment_doc(c)]) .collect(); - let comments = d.concat(&comment_docs); // Line comments always force the CALL to expand - the newline after the - // comment means the call must break to multiple lines. + // comment means the call must break to multiple lines. A trailing line + // comment never counts toward width (prettier's `lineSuffix`), so the + // argument's own group (array/object, binary, conditional, …) can stay + // inline even when the comment exceeds print_width; force_expansion + // ensures the call expands. force_expansion = true; + arg_parts.push(d.line_suffix(d.concat(&comment_docs))); + } - // A trailing line comment never counts toward width (prettier's - // `lineSuffix`), so the argument's own group (array/object, binary, - // conditional, …) can stay inline even when the comment exceeds - // print_width. The force_expansion above ensures the call expands. - arg_parts.push(d.line_suffix(comments)); - has_trailing_comma_on_last = true; - } else if pc.has_trailing_block() { - // Trailing block comments: place relative to the source comma. - // Before-comma stay after the arg; after-comma are preserved past - // the trailing comma (emitted by the wrappers below). Don't force - // expansion - let content decide based on width/source newlines. - // e.g., fn({short} /* c */) stays inline, fn({long...} /* c */) expands - let comma_pos = find_comma_pos(printer.source, effective_arg_end, paren_close); - for comment in &pc.trailing_block { - if comma_pos.is_some_and(|cp| is_comment_after_comma(comment, cp)) { - last_after_comma.push(d.text(" ")); - last_after_comma.push(printer.build_comment_doc(comment)); - } else { - arg_parts.push(d.text(" ")); - arg_parts.push(printer.build_comment_doc(comment)); - } - } + // (3) Own-line comments (block or line) after the last arg, before the + // closing paren — emitted after the trailing comma, each on its own line. + // Also handles spread with stripped parens via effective_arg_end. + if !pc.leading.is_empty() { + force_expansion = true; + pc.emit_last_arg_dangling_comments( + &mut arg_parts, + printer, + &mut has_trailing_comma_on_last, + ); } } } diff --git a/crates/tsv_ts/src/printer/calls/chain_args.rs b/crates/tsv_ts/src/printer/calls/chain_args.rs index 4e7c3c25..5973aa41 100644 --- a/crates/tsv_ts/src/printer/calls/chain_args.rs +++ b/crates/tsv_ts/src/printer/calls/chain_args.rs @@ -572,6 +572,11 @@ fn build_call_args_doc_for_chain_impl( } // Emit trailing block comments that are AFTER the comma (as leading on next arg) + // TODO: when a line comment is also present, the line above emits it first + // and a `//` runs to EOL — so an after-comma block here is swallowed + // (`a, /* c */ // c2` → `a, // c2 /* c */`, content loss). Fold into the + // cross-path "after-comma trailing block+line" pass (see TODO_REFACTORING.md); + // not fixed in isolation because the four arg paths diverge on this case. if let Some(cpos) = comma_pos { for comment in &pc.trailing_block { if is_comment_after_comma(comment, cpos) { @@ -613,11 +618,32 @@ fn build_call_args_doc_for_chain_impl( // blank line preservation at top of next iteration adds literalline + hardline pc.emit_leading_comments_inline_aware(&mut arg_parts, printer, next_arg_start); } else { - // Last argument - same-line trailing comments before closing paren + // Last argument - same-line trailing comments before closing paren. + // Split block comments around the trailing comma (like the non-last + // path above) so a before-comma block isn't relocated past it + // (`a /* c */, // c2` → `a, /* c */ // c2`). When the source has no + // trailing comma, every block precedes the synthetic one. if pc.has_trailing_line() || pc.has_trailing_block() { + let comma_pos = find_comma_pos(printer.source, arg_end, next_boundary); + for comment in &pc.trailing_block { + if comma_pos.is_none_or(|cpos| is_comment_before_comma(comment, cpos)) { + arg_parts.push(d.text(" ")); + arg_parts.push(printer.build_comment_doc(comment)); + } + } arg_parts.push(d.text(",")); - pc.emit_trailing_comments(&mut arg_parts, printer); trailing_comma_already_added = true; + if let Some(cpos) = comma_pos { + for comment in &pc.trailing_block { + if is_comment_after_comma(comment, cpos) { + arg_parts.push(d.text(" ")); + arg_parts.push(printer.build_comment_doc(comment)); + } + } + } + for comment in &pc.trailing_line { + arg_parts.push(printer.build_trailing_line_comment_doc(comment)); + } } // Own-line comments (block or line) after the last arg, before the diff --git a/crates/tsv_ts/src/printer/calls/new_expression.rs b/crates/tsv_ts/src/printer/calls/new_expression.rs index c4db1534..ffad6b09 100644 --- a/crates/tsv_ts/src/printer/calls/new_expression.rs +++ b/crates/tsv_ts/src/printer/calls/new_expression.rs @@ -408,8 +408,16 @@ impl<'a> Printer<'a> { next_arg_start, ); - arg_parts.push(d.text(",")); - pc.emit_trailing_comments(&mut arg_parts, self); + // Split trailing comments around the comma so a before-comma block + // (`a /* c */, // c2`) stays put instead of being pushed past the + // comma (`emit_trailing_comments` would, since the comma is emitted + // first). Shared with the blank-line args path. + pc.emit_trailing_comments_around_comma( + &mut arg_parts, + self, + arg_end, + next_arg_start, + ); arg_parts.push(d.hardline()); pc.emit_leading_comments(&mut arg_parts, self); } else { diff --git a/crates/tsv_ts/src/printer/chain/builder/helpers.rs b/crates/tsv_ts/src/printer/chain/builder/helpers.rs index 6eb391a1..84fe280b 100644 --- a/crates/tsv_ts/src/printer/chain/builder/helpers.rs +++ b/crates/tsv_ts/src/printer/chain/builder/helpers.rs @@ -11,6 +11,86 @@ use super::super::types::ChainGroup; use tsv_lang::doc::arena::DocId; use tsv_lang::printing::has_blank_line_between_strict; +/// Emit a chain gap's comments and the line break into `parts`, for the gap +/// between `object_end` and `property_start` (i.e. before a `.member`). +/// +/// Order: trailing block comments (`prev /* c */`), trailing line comments (same +/// line, via `line_suffix`), the line break (blank-line aware), then leading block +/// and line comments on their own lines — with blank-line preservation around the +/// leading run. Uses single-pass classification (one binary search). +/// +/// This is the single definition of "how a forced chain break renders the comments +/// in its gap", shared by the call-chain group path ([`ChainPartsBuilder`]) and the +/// member-only breaking path, so the two cannot drift (the historical member-only +/// `line_suffix`-everything approach was exactly such a drift — it merged/reversed +/// consecutive mid-chain line comments). +// TODO: same gap-comment rule as `conditional.rs` split_pre_operator_comments and +// `calls/arg_comments.rs` PartitionedComments — three parallel implementations of +// "same-line comments trail, later-line ones break to their own line". Unify if/when +// the Printer/ChainPrinter trait boundary and the differing emission shapes allow. +pub(crate) fn push_gap_comments_and_break( + parts: &mut Vec, + printer: &P, + object_end: u32, + property_start: u32, + use_hardline: bool, +) { + // Classify all comments in one pass (single binary search) + let classified = printer.classify_comments(object_end, property_start); + + // Trailing block comments (same line as previous element): `method() /* c */` + parts.push(printer.build_trailing_block_doc(&classified.trailing_block)); + // Trailing line comments (same line as previous element), via line_suffix + parts.push(printer.build_trailing_line_doc(&classified.trailing_line)); + // Line break with blank line preservation + parts.push(build_chain_line_break( + printer, + object_end, + property_start, + use_hardline, + )); + + // When comments exist, build_chain_line_break skips blank line detection. + // Check for blank lines before the first comment and after the last comment. + let has_leading_comments = + !classified.leading_block.is_empty() || !classified.leading_line.is_empty(); + + // Blank line before first leading comment + if use_hardline && has_leading_comments { + let first_start = classified + .leading_block + .first() + .map(|c| c.span.start) + .into_iter() + .chain(classified.leading_line.first().map(|c| c.span.start)) + .min(); + if let Some(start) = first_start + && has_blank_line_between_strict(printer.get_source(), object_end, start) + { + parts.push(printer.arena().hardline()); + } + } + + // Leading block comments (on their own line) + parts.push(printer.build_leading_comments_doc(&classified.leading_block)); + // Leading line comments (on their own line) + parts.push(printer.build_leading_comments_doc(&classified.leading_line)); + + // Blank line after last leading comment (before property) + if use_hardline && has_leading_comments { + let last_end = classified + .leading_line + .last() + .or_else(|| classified.leading_block.last()) + .map(|c| c.span.end); + if let Some(end) = last_end + && has_blank_line_between_strict(printer.get_source(), end, property_start) + { + parts.push(printer.arena().hardline()); + } + } +} + /// Builder for constructing chain parts with proper comment handling. /// /// Encapsulates the logic for interleaving comments, line breaks, and groups @@ -88,93 +168,18 @@ impl<'a, P: ChainPrinter> ChainPartsBuilder<'a, P> { } } - /// Add trailing comments, line break, and leading comments before a group - /// - /// Emits comments in this order: - /// 1. Trailing block comments (same line as previous element) - before line break - /// 2. Trailing line comments (same line) - via line_suffix - /// 3. Line break - /// 4. Leading block comments (on their own line) - /// 5. Leading line comments (on their own line) - /// - /// Uses single-pass comment classification (O(log n + k)) instead of 4 separate - /// filter calls (O(4 log n + 4k)). + /// Add trailing comments, line break, and leading comments before a group. + /// Delegates to the shared [`push_gap_comments_and_break`] so this group path + /// and the member-only breaking path render gap comments identically. fn add_comments_and_break(&mut self, group: &ChainGroup<'_>) { if let Some((object_end, property_start)) = group.first_member_range() { - // Classify all comments in one pass (single binary search) - let classified = self.printer.classify_comments(object_end, property_start); - - // Trailing block comments (same line as previous element) - // Use Leading spacing (space before comment): `method() /* c */` - self.parts.push( - self.printer - .build_trailing_block_doc(&classified.trailing_block), - ); - - // Trailing line comments (same line as previous element) - self.parts.push( - self.printer - .build_trailing_line_doc(&classified.trailing_line), - ); - - // Line break with blank line preservation - self.parts.push(build_chain_line_break( + push_gap_comments_and_break( + &mut self.parts, self.printer, object_end, property_start, self.use_hardline, - )); - - // When comments exist, build_chain_line_break skips blank line detection. - // Check for blank lines before the first comment and after the last comment. - let has_leading_comments = - !classified.leading_block.is_empty() || !classified.leading_line.is_empty(); - - // Blank line before first leading comment - if self.use_hardline && has_leading_comments { - let first_start = classified - .leading_block - .first() - .map(|c| c.span.start) - .into_iter() - .chain(classified.leading_line.first().map(|c| c.span.start)) - .min(); - if let Some(start) = first_start { - let source = self.printer.get_source(); - if has_blank_line_between_strict(source, object_end, start) { - let d = self.printer.arena(); - self.parts.push(d.hardline()); - } - } - } - - // Leading block comments (on their own line) - self.parts.push( - self.printer - .build_leading_comments_doc(&classified.leading_block), ); - - // Leading line comments (on their own line) - self.parts.push( - self.printer - .build_leading_comments_doc(&classified.leading_line), - ); - - // Blank line after last leading comment (before property) - if self.use_hardline && has_leading_comments { - let last_end = classified - .leading_line - .last() - .or_else(|| classified.leading_block.last()) - .map(|c| c.span.end); - if let Some(end) = last_end { - let source = self.printer.get_source(); - if has_blank_line_between_strict(source, end, property_start) { - let d = self.printer.arena(); - self.parts.push(d.hardline()); - } - } - } } else { // No member range - just add line break let d = self.printer.arena(); diff --git a/crates/tsv_ts/src/printer/chain/builder/member_only.rs b/crates/tsv_ts/src/printer/chain/builder/member_only.rs index 98f33b47..e063d82d 100644 --- a/crates/tsv_ts/src/printer/chain/builder/member_only.rs +++ b/crates/tsv_ts/src/printer/chain/builder/member_only.rs @@ -3,11 +3,87 @@ // Handles chains that contain only member accesses (no calls) using // fill() for greedy packing of segments. -use super::super::printing::{ChainPrinter, print_node}; +use super::super::printing::{ChainPrinter, print_node, print_node_inner}; use super::super::types::{ChainGroup, ChainNode}; +use super::helpers::push_gap_comments_and_break; use crate::ast::internal::Expression; use tsv_lang::doc::arena::DocId; +/// True if a member-only chain has a line comment in any inter-member gap. +/// +/// Block-only comments stay on the fill path (they format inline without forcing a +/// break); a line comment must end its line, so it forces the chain to break to +/// preserve the comment where the author wrote it — see +/// [`build_member_only_chain_with_comments_doc`]. +pub(super) fn member_only_has_interior_line_comments<'a, P: ChainPrinter>( + groups: &[ChainGroup<'a>], + printer: &P, +) -> bool { + groups + .iter() + .flat_map(|g| g.nodes.iter()) + .any(|node| match node.comment_range() { + Some((start, end)) => { + let c = printer.classify_comments(start, end); + !c.trailing_line.is_empty() || !c.leading_line.is_empty() + } + None => false, + }) +} + +/// Build a member-only chain that has interior line comments. +/// +/// Reverses the historical "emit every mid-chain comment via `line_suffix`" +/// approach, which deferred line comments to end of line — merging and reversing +/// consecutive ones (`a.b // c1⏎// c2⏎.c` → `a.b.c; // c2 // c1`) and dropping +/// nothing only by luck. Instead the chain breaks at every member (the same shape a +/// call in the chain already forces) and each gap's comments are emitted in place +/// via the shared [`push_gap_comments_and_break`] — the exact primitive the +/// call-chain breaking path uses. Comments stay where the author wrote them. +/// +/// Prettier hoists the own-line comment before the whole expression and trails the +/// rest; tsv preserves position. Documented divergence +/// (`chained/member_only_interior_line_comment`). +pub(super) fn build_member_only_chain_with_comments_doc<'a, P: ChainPrinter>( + groups: &[ChainGroup<'a>], + printer: &P, +) -> DocId { + let d = printer.arena(); + let all_nodes: Vec<&ChainNode<'a>> = groups.iter().flat_map(|g| g.nodes.iter()).collect(); + + // first_doc = base + any leading non-member nodes (e.g. a non-null on the base). + let first_doc_end = all_nodes + .iter() + .take_while(|n| !n.is_member()) + .count() + .max(1); + let first_doc_nodes: Vec = all_nodes + .iter() + .take(first_doc_end) + .map(|n| print_node(n, printer)) + .collect(); + let first_doc = d.concat(&first_doc_nodes); + + // Each remaining node breaks onto its own line. When its gap carries comments, + // emit them in place (trailing on the previous line, leading on their own) and + // print the node skipping its own comments; otherwise just break before it. + let mut rest = Vec::new(); + for node in &all_nodes[first_doc_end..] { + match node.comment_range() { + Some((obj_end, prop_start)) if printer.has_comments_between(obj_end, prop_start) => { + push_gap_comments_and_break(&mut rest, printer, obj_end, prop_start, true); + rest.push(print_node_inner(node, printer, false, true)); + } + _ => { + rest.push(d.hardline()); + rest.push(print_node(node, printer)); + } + } + } + + d.concat(&[first_doc, d.indent(d.concat(&rest))]) +} + /// Build doc for member-only chains using fill for greedy packing /// /// Break points are ONLY at member access (`.foo`), not at non-null (`!`). diff --git a/crates/tsv_ts/src/printer/chain/builder/mod.rs b/crates/tsv_ts/src/printer/chain/builder/mod.rs index e3e2781e..f40386cc 100644 --- a/crates/tsv_ts/src/printer/chain/builder/mod.rs +++ b/crates/tsv_ts/src/printer/chain/builder/mod.rs @@ -22,7 +22,10 @@ use helpers::{ build_expanded_doc, build_first_groups_doc, build_first_groups_expanded_doc, build_rest_parts_with_comments, }; -use member_only::build_member_only_chain_doc; +use member_only::{ + build_member_only_chain_doc, build_member_only_chain_with_comments_doc, + member_only_has_interior_line_comments, +}; use super::analysis::should_merge_first_groups; use super::printing::{ @@ -169,6 +172,13 @@ pub fn build_chain_doc<'a, P: ChainPrinter>(groups: &[ChainGroup<'a>], printer: .any(|n| has_inside_bracket_comments(n, printer)); if !has_calls && !first_has_parens && !has_bracket_comments { + // Member-only chain with interior line comments: break the chain and emit + // each comment in place (shared comment-aware path), instead of the fill + // path's line_suffix — which defers mid-chain line comments to end of line, + // merging/reversing multiple. Prettier hoists these; tsv preserves position. + if member_only_has_interior_line_comments(groups, printer) { + return build_member_only_chain_with_comments_doc(groups, printer); + } // Member-only chain: use fill for greedy packing return build_member_only_chain_doc(groups, printer); } diff --git a/crates/tsv_ts/src/printer/expressions/assignment.rs b/crates/tsv_ts/src/printer/expressions/assignment.rs index 5d311871..a565bb9e 100644 --- a/crates/tsv_ts/src/printer/expressions/assignment.rs +++ b/crates/tsv_ts/src/printer/expressions/assignment.rs @@ -852,19 +852,15 @@ impl<'a> Printer<'a> { { layout = AssignmentLayout::BreakAfterOperator; } - // For member-only chains with line comments, force BreakAfterOperator. - // Line comments cause Prettier's first pass to break at `=`. - // - // For call chains with line comments, do NOT use BreakAfterOperator. - // The chain formatter handles breaking at the comment location. - // Use NeverBreakAfterOperator so the chain stays with `=` and breaks internally. - if layout != AssignmentLayout::BreakAfterOperator - && self.has_line_comments_in_member_chain(right_expr) - { - layout = AssignmentLayout::BreakAfterOperator; - } else if layout == AssignmentLayout::BreakAfterOperator - && matches!(right_expr, Expression::CallExpression(_)) - && self.has_line_comments_in_call_chain(right_expr) + // Member-only AND call chains with line comments break internally at the + // comment location (the chain formatter does this — see + // build_member_only_chain_with_comments_doc and the call-chain breaking path). + // Keep the chain with `=` (NeverBreakAfterOperator) so it doesn't also break + // after the operator, which would double-indent the broken chain. + if self.has_line_comments_in_member_chain(right_expr) + || (layout == AssignmentLayout::BreakAfterOperator + && matches!(right_expr, Expression::CallExpression(_)) + && self.has_line_comments_in_call_chain(right_expr)) { layout = AssignmentLayout::NeverBreakAfterOperator; } @@ -954,7 +950,7 @@ impl<'a> Printer<'a> { /// /// Member-only chains with line comments between segments should force /// BreakAfterOperator layout to match Prettier's first-pass behavior. - fn has_line_comments_in_member_chain(&self, expr: &Expression) -> bool { + pub(crate) fn has_line_comments_in_member_chain(&self, expr: &Expression) -> bool { // Only check member-only chains (no calls) if !is_member_only_chain(expr) { return false; diff --git a/crates/tsv_ts/src/printer/expressions/conditional.rs b/crates/tsv_ts/src/printer/expressions/conditional.rs index f1ee2fdd..2633fd0e 100644 --- a/crates/tsv_ts/src/printer/expressions/conditional.rs +++ b/crates/tsv_ts/src/printer/expressions/conditional.rs @@ -305,17 +305,22 @@ impl<'a> Printer<'a> { let mut parts = vec![test]; - // Comments between test and ? (inline after test). A line comment goes - // through `line_suffix` (zero width), so a long trailing comment never - // forces the test (e.g. a binary expression) to break — matching - // prettier's `lineSuffix`. Block comments stay inline, width counted. + // Comments between test and ? (see split_pre_operator_comments): same-line + // comments trail the test, later-line comments precede the `?` on their own + // lines. let comments_before_q_end = question_pos.unwrap_or(consequent_start); - for comment in tsv_lang::comments_in_range(self.comments, test_end, comments_before_q_end) { - parts.push(self.build_trailing_comment_doc(comment)); - } + let mut pre_question_own_line = Vec::new(); + self.split_pre_operator_comments( + test_end, + comments_before_q_end, + &mut parts, + &mut pre_question_own_line, + ); - // Start the indented part with ? on new line - let mut q_parts = vec![d.hardline(), d.text("?")]; + // Start the indented part: own-line pre-? comments, then ? on a new line + let mut q_parts = pre_question_own_line; + q_parts.push(d.hardline()); + q_parts.push(d.text("?")); // Comments between ? and consequent // When multiple comments exist, each subsequent one goes on its own line @@ -366,13 +371,19 @@ impl<'a> Printer<'a> { } } - // Comments between consequent and : (inline after consequent) + // Comments between consequent and :. Mirrors the test→? handling above + // (same shared helper): same-line comments trail the consequent, later-line + // comments precede the `:` on their own lines — both flow into q_parts in + // source order (trailing run first, then own-line run). let comments_before_colon_end = colon_pos.unwrap_or(alternate_start); - for comment in - tsv_lang::comments_in_range(self.comments, consequent_end, comments_before_colon_end) - { - q_parts.push(self.build_trailing_comment_doc(comment)); - } + let mut colon_own_line = Vec::new(); + self.split_pre_operator_comments( + consequent_end, + comments_before_colon_end, + &mut q_parts, + &mut colon_own_line, + ); + q_parts.append(&mut colon_own_line); // : on new line q_parts.push(d.hardline()); @@ -422,6 +433,49 @@ impl<'a> Printer<'a> { d.concat(&parts) } + /// Split the comments in a ternary operand→operator gap into trailing vs + /// own-line docs, shared by the test→`?` and consequent→`:` sites. + /// + /// A comment on the operand's own source line trails it (a block stays inline + /// with its width counted; a line comment uses `line_suffix`, zero width, so a + /// long trailing comment never forces a binary operand to break — see + /// `test_trailing_long_comment`) and is pushed to `trailing`. A comment the + /// author placed on a *later* line drops to its own line, aligned with the + /// operator it precedes, and is pushed to `own_line` (a `d.hardline()` then the + /// comment). A `//` ends its line, so a same-line run trails at most one line + /// comment; everything after it already starts on a later line. + /// + /// This preserves the author's "before the operator" placement — prettier + /// instead relocates later-line comments across the operator — and never merges + /// consecutive line comments onto the operand line, which would reverse their + /// order and fuse them into one node (the property-signature `// c2 // c1` + /// quirk, here in a ternary). The two before-operator sites share this helper + /// so they cannot drift apart (the original merge bug was exactly such a drift + /// from the correct after-operator handling). + // TODO: this "classify a gap's comments — same-line ones trail, later-line ones + // break to their own line, never merge" rule is reimplemented for call args + // (`calls/arg_comments.rs` PartitionedComments + emit_*) and for member chains + // (`chain/builder/helpers.rs` push_gap_comments_and_break). Worth unifying into + // one primitive once the Printer/ChainPrinter trait split allows (the three + // emission shapes — operator / comma / dot — differ, so it's non-trivial). + fn split_pre_operator_comments( + &self, + operand_end: u32, + gap_end: u32, + trailing: &mut Vec, + own_line: &mut Vec, + ) { + let d = self.d(); + for comment in tsv_lang::comments_in_range(self.comments, operand_end, gap_end) { + if self.is_same_line(operand_end, comment.span.start) { + trailing.push(self.build_trailing_comment_doc(comment)); + } else { + own_line.push(d.hardline()); + own_line.push(self.build_comment_doc(comment)); + } + } + } + /// Build expression doc for a ternary branch (consequent/alternate). /// /// When `indent_binary` is true (grandparent is return/throw/call/new), diff --git a/crates/tsv_ts/src/printer/statements/variable.rs b/crates/tsv_ts/src/printer/statements/variable.rs index 1d84ed7d..48f414f9 100644 --- a/crates/tsv_ts/src/printer/statements/variable.rs +++ b/crates/tsv_ts/src/printer/statements/variable.rs @@ -386,11 +386,14 @@ impl<'a> Printer<'a> { // (handles `await import('./x' // comment)`) let is_import_with_trailing_comments = self.has_import_with_trailing_comments(init); - // Call chains with line comments should NOT be treated as fluid. - // The chain formatter handles breaking at the comment location. - // E.g., `const a = items // comment\n .foo()` keeps `= items // comment` together. - let has_line_comments_in_chain = matches!(init, Expression::CallExpression(_)) - && self.has_line_comments_in_call_chain(init); + // Call chains AND member-only chains with line comments should NOT be + // treated as fluid / break-after-operator. The chain formatter breaks + // internally at the comment location, so keep the chain with `=` + // (otherwise it breaks after `=` too → double indent). E.g. + // `const a = items // comment\n .foo()` and `const b = foo.bar // c\n .baz`. + let has_line_comments_in_chain = (matches!(init, Expression::CallExpression(_)) + && self.has_line_comments_in_call_chain(init)) + || self.has_line_comments_in_member_chain(init); // Combined flag for expressions with trailing comments that expand internally let has_trailing_comment_expansion = diff --git a/docs/architecture.md b/docs/architecture.md index 642a9f16..ab207aa8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -212,7 +212,7 @@ Language-agnostic primitives shared across all implementations: | `Span` | Source positions (u32 for memory efficiency) | | `LocationTracker` | Lazy line/column computation (O(log n) binary search) | | `ParseError` | Language-agnostic errors (String-based for flexibility) | -| `doc` | **Document builder for prettier-compatible formatting** | +| `doc` | **Document builder for Prettier-style formatting** | | `printing` | Shared formatting utilities (string literals, whitespace) | | `OutputBuffer` | Pre-allocated output string building with column tracking | | `config` | `PRINT_WIDTH` / `TAB_WIDTH` / `INDENT` consts, `EmbedContext`, `LayoutMode` (no runtime config) | @@ -659,7 +659,7 @@ Scales at O(features) rather than O(tools × features). | Pratt parsing | Clean operator precedence for TS expressions | | Shared interner | Identifiers deduplicated across embedded regions of a file | | Detached comments | Simple AST, O(log n) lookup, matches prettier | -| Doc builder | Prettier-compatible declarative formatting | +| Doc builder | Prettier-style declarative formatting | | Source threading | Preserve escapes without AST duplication | | Lazy locations | Parse-time speed, serialize-time computation | | Fixtures as data | Reusable across tools, O(features) scaling | diff --git a/docs/conformance_prettier.md b/docs/conformance_prettier.md index 3a5926a6..f552a852 100644 --- a/docs/conformance_prettier.md +++ b/docs/conformance_prettier.md @@ -1,6 +1,6 @@ # Prettier Conformance -Prettier was tsv's initial guide, and the formatter still tracks it closely for the common case — but tsv has its own identity and makes **intentional, cataloged choices** to diverge where they're more defensible. This document catalogs those divergences along with bugs that tsv does not replicate. +Prettier was tsv's initial guide, and the formatter still tracks it for the common case — but tsv has its own identity and makes **intentional, cataloged choices** to diverge where they're more defensible. This document catalogs those divergences along with bugs that tsv does not replicate. ## Terminology @@ -436,6 +436,7 @@ Prettier relocates certain comments during formatting. tsv preserves comments wh Prettier moves comments between syntactic boundaries into adjacent blocks, parens, or other positions. tsv preserves them where the user placed them. - Conditional type after `:` → Trailing on true branch — [comment_after_colon](../tests/fixtures/typescript/types/conditional/comment_after_colon_prettier_divergence/) +- Ternary operand to operator (≥2 line comments; test→`?`, consequent→`:`) → Every comment after the first relocated across the operator (`cond // c1⏎ ? // c2`); tsv keeps each before the operator on its own line — [consecutive_operand_comment](../tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/) - Switch empty body → Discriminant parens — [empty_comment](../tests/fixtures/typescript/statements/switch/empty_comment_prettier_divergence/) - Switch case before `{` → After opening brace — [case_block_comment](../tests/fixtures/typescript/statements/switch/case_block_comment_prettier_divergence/) - Switch discriminant trailing → Switch body — [discriminant_trailing_comment](../tests/fixtures/typescript/statements/switch/discriminant_trailing_comment_prettier_divergence/) @@ -455,6 +456,7 @@ Prettier moves comments between syntactic boundaries into adjacent blocks, paren - While before `{}` (block/line) → Into block body (expands block) — [absorbed_body_comment](../tests/fixtures/typescript/statements/while/absorbed_body_comment_prettier_divergence/) - Do-while between `}` and `while` → Into while condition — [line_before_while_comment](../tests/fixtures/typescript/statements/do_while/line_before_while_comment_prettier_divergence/), [while_leading_block_comment](../tests/fixtures/typescript/statements/do_while/while_leading_block_comment_prettier_divergence/) - Trailing member chain → After `=` — [trailing_member_comment](../tests/fixtures/typescript/expressions/calls/chained/trailing_member_comment_prettier_divergence/) +- Member-only chain interior line comment (no calls) → Hoisted before the expression / trailed on the statement (merging consecutive); tsv breaks the chain and keeps each comment in place — [member_only_interior_line_comment](../tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/) - Block comment in computed `[]` → Before member chain (hoisted) — [block_comment_computed_member_long](../tests/fixtures/typescript/syntax/comments/block_comment_computed_member_long_prettier_divergence/) - Switch case colon comment → Before colon or into body — [case_colon_comment](../tests/fixtures/typescript/statements/switch/case_colon_comment_prettier_divergence/) - Class property definite `!` → Before `!` modifier — [property_definite_comment](../tests/fixtures/typescript/statements/class/property_definite_comment_prettier_divergence/) diff --git a/tests/fixtures/typescript/expressions/calls/chained/last_arg_block_then_line_comment/expected.json b/tests/fixtures/typescript/expressions/calls/chained/last_arg_block_then_line_comment/expected.json new file mode 100644 index 00000000..5561d1d3 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/last_arg_block_then_line_comment/expected.json @@ -0,0 +1,567 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 406, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " The last argument of a chained call, carrying a block comment before the comma", + "start": 20, + "end": 101, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 82 + } + } + }, + { + "type": "Line", + "value": " and a line comment after it: the block trails the argument (before the trailing", + "start": 103, + "end": 185, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 83 + } + } + }, + { + "type": "Line", + "value": " comma), the line comment follows. The block is not relocated past the comma.", + "start": 187, + "end": 266, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 80 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 291, + "end": 299, + "loc": { + "start": { + "line": 8, + "column": 5 + }, + "end": { + "line": 8, + "column": 13 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 301, + "end": 306, + "loc": { + "start": { + "line": 8, + "column": 15 + }, + "end": { + "line": 8, + "column": 20 + } + } + }, + { + "type": "Line", + "value": " Single argument form.", + "start": 323, + "end": 347, + "loc": { + "start": { + "line": 12, + "column": 1 + }, + "end": { + "line": 12, + "column": 25 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 366, + "end": 374, + "loc": { + "start": { + "line": 15, + "column": 5 + }, + "end": { + "line": 15, + "column": 13 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 376, + "end": 381, + "loc": { + "start": { + "line": 15, + "column": 15 + }, + "end": { + "line": 15, + "column": 20 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 405, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 396, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 18, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 268, + "end": 320, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 10, + "column": 9 + } + }, + "expression": { + "type": "CallExpression", + "start": 268, + "end": 319, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 10, + "column": 8 + } + }, + "callee": { + "type": "MemberExpression", + "start": 268, + "end": 317, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 10, + "column": 6 + } + }, + "object": { + "type": "CallExpression", + "start": 268, + "end": 310, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 9, + "column": 3 + } + }, + "callee": { + "type": "MemberExpression", + "start": 268, + "end": 278, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 6, + "column": 6 + } + }, + "object": { + "type": "Identifier", + "start": 268, + "end": 271, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 4 + } + }, + "name": "foo" + }, + "property": { + "type": "Identifier", + "start": 275, + "end": 278, + "loc": { + "start": { + "line": 6, + "column": 3 + }, + "end": { + "line": 6, + "column": 6 + } + }, + "name": "bar" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 283, + "end": 284, + "loc": { + "start": { + "line": 7, + "column": 3 + }, + "end": { + "line": 7, + "column": 4 + } + }, + "name": "x" + }, + { + "type": "Identifier", + "start": 289, + "end": 290, + "loc": { + "start": { + "line": 8, + "column": 3 + }, + "end": { + "line": 8, + "column": 4 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 291, + "end": 299 + } + ] + } + ], + "optional": false + }, + "property": { + "type": "Identifier", + "start": 314, + "end": 317, + "loc": { + "start": { + "line": 10, + "column": 3 + }, + "end": { + "line": 10, + "column": 6 + } + }, + "name": "baz", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 301, + "end": 306 + } + ] + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " The last argument of a chained call, carrying a block comment before the comma", + "start": 20, + "end": 101 + }, + { + "type": "Line", + "value": " and a line comment after it: the block trails the argument (before the trailing", + "start": 103, + "end": 185 + }, + { + "type": "Line", + "value": " comma), the line comment follows. The block is not relocated past the comma.", + "start": 187, + "end": 266 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 349, + "end": 395, + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 17, + "column": 9 + } + }, + "expression": { + "type": "CallExpression", + "start": 349, + "end": 394, + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 17, + "column": 8 + } + }, + "callee": { + "type": "MemberExpression", + "start": 349, + "end": 392, + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 17, + "column": 6 + } + }, + "object": { + "type": "CallExpression", + "start": 349, + "end": 385, + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 16, + "column": 3 + } + }, + "callee": { + "type": "MemberExpression", + "start": 349, + "end": 359, + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 14, + "column": 6 + } + }, + "object": { + "type": "Identifier", + "start": 349, + "end": 352, + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 13, + "column": 4 + } + }, + "name": "foo" + }, + "property": { + "type": "Identifier", + "start": 356, + "end": 359, + "loc": { + "start": { + "line": 14, + "column": 3 + }, + "end": { + "line": 14, + "column": 6 + } + }, + "name": "bar" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 364, + "end": 365, + "loc": { + "start": { + "line": 15, + "column": 3 + }, + "end": { + "line": 15, + "column": 4 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 366, + "end": 374 + } + ] + } + ], + "optional": false + }, + "property": { + "type": "Identifier", + "start": 389, + "end": 392, + "loc": { + "start": { + "line": 17, + "column": 3 + }, + "end": { + "line": 17, + "column": 6 + } + }, + "name": "baz", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 376, + "end": 381 + } + ] + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " Single argument form.", + "start": 323, + "end": 347 + } + ] + } + ], + "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/expressions/calls/chained/last_arg_block_then_line_comment/input.svelte b/tests/fixtures/typescript/expressions/calls/chained/last_arg_block_then_line_comment/input.svelte new file mode 100644 index 00000000..635da700 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/last_arg_block_then_line_comment/input.svelte @@ -0,0 +1,18 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/chained/last_arg_block_then_line_comment/unformatted_compact.svelte b/tests/fixtures/typescript/expressions/calls/chained/last_arg_block_then_line_comment/unformatted_compact.svelte new file mode 100644 index 00000000..c4425319 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/last_arg_block_then_line_comment/unformatted_compact.svelte @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/README.md b/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/README.md new file mode 100644 index 00000000..390d4b3c --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/README.md @@ -0,0 +1,21 @@ +# Member-only chain with interior line comments + +A member-only chain (pure property access, **no calls** — `foo.bar.baz`) with a +line comment in a gap between members. + +- **tsv**: breaks the chain at every member and keeps each comment where the + author wrote it — a same-line comment trails its member (`.bar // c1`), an + own-line comment stays on its own line before the next member (`// c2` above + `.baz`). This is the same break shape a call in the chain already forces (see + `trailing_member_comment`), now applied to call-free chains too. +- **prettier**: hoists the own-line comment before the whole expression and + trails the rest on the statement, merging consecutive line comments + (`const a =⏎\t// c2⏎\tfoo.bar.baz; // c1`). + +A `//` must end its line, so a member-only chain with an interior line comment +cannot stay inline without either relocating the comment (prettier) or fusing it +into the line below (the historical tsv bug: `foo.bar.baz; // c2 // c1`). tsv +breaks the chain instead, applying the [comment-position +philosophy](../../../../../../docs/conformance_prettier.md#comment-position-philosophy) +— comments stay where the author placed them. Block-only comments don't force +this (they format inline on the fill path), so only line comments route here. diff --git a/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/expected.json b/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/expected.json new file mode 100644 index 00000000..701868ec --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/expected.json @@ -0,0 +1,559 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 651, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " A member-only chain (no calls) with line comments between members: tsv breaks", + "start": 20, + "end": 100, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 81 + } + } + }, + { + "type": "Line", + "value": " the chain and keeps each comment where the author wrote it. Prettier instead", + "start": 102, + "end": 181, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 80 + } + } + }, + { + "type": "Line", + "value": " hoists the own-line comment before the whole expression and trails the rest,", + "start": 183, + "end": 262, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 80 + } + } + }, + { + "type": "Line", + "value": " merging consecutive line comments. tsv preserves position (no merge/reverse).", + "start": 264, + "end": 344, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 81 + } + } + }, + { + "type": "Line", + "value": " Two comments before `.baz`: the first trails `.bar`, the second gets its own", + "start": 347, + "end": 426, + "loc": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 80 + } + } + }, + { + "type": "Line", + "value": " line. The chain breaks at every member (as it does once a call forces a break).", + "start": 428, + "end": 510, + "loc": { + "start": { + "line": 8, + "column": 1 + }, + "end": { + "line": 8, + "column": 83 + } + } + }, + { + "type": "Line", + "value": " c1", + "start": 533, + "end": 538, + "loc": { + "start": { + "line": 10, + "column": 7 + }, + "end": { + "line": 10, + "column": 12 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 541, + "end": 546, + "loc": { + "start": { + "line": 11, + "column": 2 + }, + "end": { + "line": 11, + "column": 7 + } + } + }, + { + "type": "Line", + "value": " A single own-line comment before a member.", + "start": 557, + "end": 602, + "loc": { + "start": { + "line": 14, + "column": 1 + }, + "end": { + "line": 14, + "column": 46 + } + } + }, + { + "type": "Line", + "value": " c1", + "start": 627, + "end": 632, + "loc": { + "start": { + "line": 17, + "column": 2 + }, + "end": { + "line": 17, + "column": 7 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 650, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 641, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 19, + "column": 9 + } + }, + "body": [ + { + "type": "VariableDeclaration", + "start": 512, + "end": 554, + "loc": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 12, + "column": 7 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 518, + "end": 553, + "loc": { + "start": { + "line": 9, + "column": 7 + }, + "end": { + "line": 12, + "column": 6 + } + }, + "id": { + "type": "Identifier", + "start": 518, + "end": 519, + "loc": { + "start": { + "line": 9, + "column": 7 + }, + "end": { + "line": 9, + "column": 8 + } + }, + "name": "a" + }, + "init": { + "type": "MemberExpression", + "start": 522, + "end": 553, + "loc": { + "start": { + "line": 9, + "column": 11 + }, + "end": { + "line": 12, + "column": 6 + } + }, + "object": { + "type": "MemberExpression", + "start": 522, + "end": 532, + "loc": { + "start": { + "line": 9, + "column": 11 + }, + "end": { + "line": 10, + "column": 6 + } + }, + "object": { + "type": "Identifier", + "start": 522, + "end": 525, + "loc": { + "start": { + "line": 9, + "column": 11 + }, + "end": { + "line": 9, + "column": 14 + } + }, + "name": "foo" + }, + "property": { + "type": "Identifier", + "start": 529, + "end": 532, + "loc": { + "start": { + "line": 10, + "column": 3 + }, + "end": { + "line": 10, + "column": 6 + } + }, + "name": "bar" + }, + "computed": false, + "optional": false, + "trailingComments": [ + { + "type": "Line", + "value": " c1", + "start": 533, + "end": 538 + } + ] + }, + "property": { + "type": "Identifier", + "start": 550, + "end": 553, + "loc": { + "start": { + "line": 12, + "column": 3 + }, + "end": { + "line": 12, + "column": 6 + } + }, + "name": "baz", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 541, + "end": 546 + } + ] + }, + "computed": false, + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " A member-only chain (no calls) with line comments between members: tsv breaks", + "start": 20, + "end": 100 + }, + { + "type": "Line", + "value": " the chain and keeps each comment where the author wrote it. Prettier instead", + "start": 102, + "end": 181 + }, + { + "type": "Line", + "value": " hoists the own-line comment before the whole expression and trails the rest,", + "start": 183, + "end": 262 + }, + { + "type": "Line", + "value": " merging consecutive line comments. tsv preserves position (no merge/reverse).", + "start": 264, + "end": 344 + }, + { + "type": "Line", + "value": " Two comments before `.baz`: the first trails `.bar`, the second gets its own", + "start": 347, + "end": 426 + }, + { + "type": "Line", + "value": " line. The chain breaks at every member (as it does once a call forces a break).", + "start": 428, + "end": 510 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 604, + "end": 640, + "loc": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 18, + "column": 7 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 610, + "end": 639, + "loc": { + "start": { + "line": 15, + "column": 7 + }, + "end": { + "line": 18, + "column": 6 + } + }, + "id": { + "type": "Identifier", + "start": 610, + "end": 611, + "loc": { + "start": { + "line": 15, + "column": 7 + }, + "end": { + "line": 15, + "column": 8 + } + }, + "name": "b" + }, + "init": { + "type": "MemberExpression", + "start": 614, + "end": 639, + "loc": { + "start": { + "line": 15, + "column": 11 + }, + "end": { + "line": 18, + "column": 6 + } + }, + "object": { + "type": "MemberExpression", + "start": 614, + "end": 624, + "loc": { + "start": { + "line": 15, + "column": 11 + }, + "end": { + "line": 16, + "column": 6 + } + }, + "object": { + "type": "Identifier", + "start": 614, + "end": 617, + "loc": { + "start": { + "line": 15, + "column": 11 + }, + "end": { + "line": 15, + "column": 14 + } + }, + "name": "foo" + }, + "property": { + "type": "Identifier", + "start": 621, + "end": 624, + "loc": { + "start": { + "line": 16, + "column": 3 + }, + "end": { + "line": 16, + "column": 6 + } + }, + "name": "bar" + }, + "computed": false, + "optional": false + }, + "property": { + "type": "Identifier", + "start": 636, + "end": 639, + "loc": { + "start": { + "line": 18, + "column": 3 + }, + "end": { + "line": 18, + "column": 6 + } + }, + "name": "baz", + "leadingComments": [ + { + "type": "Line", + "value": " c1", + "start": 627, + "end": 632 + } + ] + }, + "computed": false, + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " A single own-line comment before a member.", + "start": 557, + "end": 602 + } + ] + } + ], + "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/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/input.svelte b/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/input.svelte new file mode 100644 index 00000000..a07132e2 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/input.svelte @@ -0,0 +1,19 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/output_prettier.svelte b/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/output_prettier.svelte new file mode 100644 index 00000000..4a5045ab --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/output_prettier.svelte @@ -0,0 +1,17 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/unformatted_ours_compact.svelte b/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/unformatted_ours_compact.svelte new file mode 100644 index 00000000..82597349 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/member_only_interior_line_comment_prettier_divergence/unformatted_ours_compact.svelte @@ -0,0 +1,17 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/last_arg_trailing_then_dangling_comment/expected.json b/tests/fixtures/typescript/expressions/calls/last_arg_trailing_then_dangling_comment/expected.json new file mode 100644 index 00000000..edf1902b --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/last_arg_trailing_then_dangling_comment/expected.json @@ -0,0 +1,874 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 697, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " Line comment trailing the last arg, then a line comment on its own line:", + "start": 20, + "end": 95, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 76 + } + } + }, + { + "type": "Line", + "value": " the first trails (after the comma), the second stays on its own line.", + "start": 97, + "end": 169, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 73 + } + } + }, + { + "type": "Line", + "value": " c1", + "start": 185, + "end": 190, + "loc": { + "start": { + "line": 6, + "column": 5 + }, + "end": { + "line": 6, + "column": 10 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 193, + "end": 198, + "loc": { + "start": { + "line": 7, + "column": 2 + }, + "end": { + "line": 7, + "column": 7 + } + } + }, + { + "type": "Line", + "value": " Same, with three: only the first trails; the rest each get their own line.", + "start": 205, + "end": 282, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 78 + } + } + }, + { + "type": "Line", + "value": " c1", + "start": 298, + "end": 303, + "loc": { + "start": { + "line": 13, + "column": 5 + }, + "end": { + "line": 13, + "column": 10 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 306, + "end": 311, + "loc": { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 14, + "column": 7 + } + } + }, + { + "type": "Line", + "value": " c3", + "start": 314, + "end": 319, + "loc": { + "start": { + "line": 15, + "column": 2 + }, + "end": { + "line": 15, + "column": 7 + } + } + }, + { + "type": "Line", + "value": " Block comment then line comment, both on the last arg's line: the block", + "start": 326, + "end": 400, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 18, + "column": 75 + } + } + }, + { + "type": "Line", + "value": " trails before the comma, the line trails after it (neither is dropped).", + "start": 402, + "end": 476, + "loc": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 75 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 491, + "end": 499, + "loc": { + "start": { + "line": 22, + "column": 4 + }, + "end": { + "line": 22, + "column": 12 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 501, + "end": 506, + "loc": { + "start": { + "line": 22, + "column": 14 + }, + "end": { + "line": 22, + "column": 19 + } + } + }, + { + "type": "Line", + "value": " Single arg: block + line on the arg's line.", + "start": 513, + "end": 559, + "loc": { + "start": { + "line": 25, + "column": 1 + }, + "end": { + "line": 25, + "column": 47 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 569, + "end": 577, + "loc": { + "start": { + "line": 27, + "column": 4 + }, + "end": { + "line": 27, + "column": 12 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 579, + "end": 584, + "loc": { + "start": { + "line": 27, + "column": 14 + }, + "end": { + "line": 27, + "column": 19 + } + } + }, + { + "type": "Line", + "value": " Block trailing the arg, then a block on its own line.", + "start": 591, + "end": 647, + "loc": { + "start": { + "line": 30, + "column": 1 + }, + "end": { + "line": 30, + "column": 57 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 662, + "end": 670, + "loc": { + "start": { + "line": 33, + "column": 4 + }, + "end": { + "line": 33, + "column": 12 + } + } + }, + { + "type": "Block", + "value": " c2 ", + "start": 674, + "end": 682, + "loc": { + "start": { + "line": 34, + "column": 2 + }, + "end": { + "line": 34, + "column": 10 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 696, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 687, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 36, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 171, + "end": 202, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 8, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 171, + "end": 201, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 8, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 171, + "end": 173, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 177, + "end": 178, + "loc": { + "start": { + "line": 5, + "column": 2 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "name": "a" + }, + { + "type": "Identifier", + "start": 182, + "end": 183, + "loc": { + "start": { + "line": 6, + "column": 2 + }, + "end": { + "line": 6, + "column": 3 + } + }, + "name": "b", + "trailingComments": [ + { + "type": "Line", + "value": " c1", + "start": 185, + "end": 190 + } + ] + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " Line comment trailing the last arg, then a line comment on its own line:", + "start": 20, + "end": 95 + }, + { + "type": "Line", + "value": " the first trails (after the comma), the second stays on its own line.", + "start": 97, + "end": 169 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 284, + "end": 323, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 16, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 284, + "end": 322, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 16, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 284, + "end": 286, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 290, + "end": 291, + "loc": { + "start": { + "line": 12, + "column": 2 + }, + "end": { + "line": 12, + "column": 3 + } + }, + "name": "a" + }, + { + "type": "Identifier", + "start": 295, + "end": 296, + "loc": { + "start": { + "line": 13, + "column": 2 + }, + "end": { + "line": 13, + "column": 3 + } + }, + "name": "b", + "trailingComments": [ + { + "type": "Line", + "value": " c1", + "start": 298, + "end": 303 + } + ] + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 193, + "end": 198 + }, + { + "type": "Line", + "value": " Same, with three: only the first trails; the rest each get their own line.", + "start": 205, + "end": 282 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 478, + "end": 510, + "loc": { + "start": { + "line": 20, + "column": 1 + }, + "end": { + "line": 23, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 478, + "end": 509, + "loc": { + "start": { + "line": 20, + "column": 1 + }, + "end": { + "line": 23, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 478, + "end": 480, + "loc": { + "start": { + "line": 20, + "column": 1 + }, + "end": { + "line": 20, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 484, + "end": 485, + "loc": { + "start": { + "line": 21, + "column": 2 + }, + "end": { + "line": 21, + "column": 3 + } + }, + "name": "a" + }, + { + "type": "Identifier", + "start": 489, + "end": 490, + "loc": { + "start": { + "line": 22, + "column": 2 + }, + "end": { + "line": 22, + "column": 3 + } + }, + "name": "b", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 491, + "end": 499 + } + ] + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 306, + "end": 311 + }, + { + "type": "Line", + "value": " c3", + "start": 314, + "end": 319 + }, + { + "type": "Line", + "value": " Block comment then line comment, both on the last arg's line: the block", + "start": 326, + "end": 400 + }, + { + "type": "Line", + "value": " trails before the comma, the line trails after it (neither is dropped).", + "start": 402, + "end": 476 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 561, + "end": 588, + "loc": { + "start": { + "line": 26, + "column": 1 + }, + "end": { + "line": 28, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 561, + "end": 587, + "loc": { + "start": { + "line": 26, + "column": 1 + }, + "end": { + "line": 28, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 561, + "end": 563, + "loc": { + "start": { + "line": 26, + "column": 1 + }, + "end": { + "line": 26, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 567, + "end": 568, + "loc": { + "start": { + "line": 27, + "column": 2 + }, + "end": { + "line": 27, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 569, + "end": 577 + } + ] + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 501, + "end": 506 + }, + { + "type": "Line", + "value": " Single arg: block + line on the arg's line.", + "start": 513, + "end": 559 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 649, + "end": 686, + "loc": { + "start": { + "line": 31, + "column": 1 + }, + "end": { + "line": 35, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 649, + "end": 685, + "loc": { + "start": { + "line": 31, + "column": 1 + }, + "end": { + "line": 35, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 649, + "end": 651, + "loc": { + "start": { + "line": 31, + "column": 1 + }, + "end": { + "line": 31, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 655, + "end": 656, + "loc": { + "start": { + "line": 32, + "column": 2 + }, + "end": { + "line": 32, + "column": 3 + } + }, + "name": "a" + }, + { + "type": "Identifier", + "start": 660, + "end": 661, + "loc": { + "start": { + "line": 33, + "column": 2 + }, + "end": { + "line": 33, + "column": 3 + } + }, + "name": "b", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 662, + "end": 670 + } + ] + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 579, + "end": 584 + }, + { + "type": "Line", + "value": " Block trailing the arg, then a block on its own line.", + "start": 591, + "end": 647 + } + ], + "trailingComments": [ + { + "type": "Block", + "value": " c2 ", + "start": 674, + "end": 682 + } + ] + } + ], + "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/expressions/calls/last_arg_trailing_then_dangling_comment/input.svelte b/tests/fixtures/typescript/expressions/calls/last_arg_trailing_then_dangling_comment/input.svelte new file mode 100644 index 00000000..2ab42645 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/last_arg_trailing_then_dangling_comment/input.svelte @@ -0,0 +1,36 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_block_then_line_comment/expected.json b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_block_then_line_comment/expected.json new file mode 100644 index 00000000..09319b27 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_block_then_line_comment/expected.json @@ -0,0 +1,482 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 452, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " When a call has a multiline-content argument (here a multiline template), the", + "start": 20, + "end": 100, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 81 + } + } + }, + { + "type": "Line", + "value": " args route through the shared joined-args path. A non-last argument carrying a", + "start": 102, + "end": 183, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 82 + } + } + }, + { + "type": "Line", + "value": " block comment before the comma and a line comment after it must keep both — the", + "start": 185, + "end": 267, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 83 + } + } + }, + { + "type": "Line", + "value": " block was dropped when a line comment coexisted.", + "start": 269, + "end": 320, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 52 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 330, + "end": 338, + "loc": { + "start": { + "line": 7, + "column": 4 + }, + "end": { + "line": 7, + "column": 12 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 340, + "end": 345, + "loc": { + "start": { + "line": 7, + "column": 14 + }, + "end": { + "line": 7, + "column": 19 + } + } + }, + { + "type": "Line", + "value": " Same path for `new`.", + "start": 369, + "end": 392, + "loc": { + "start": { + "line": 12, + "column": 1 + }, + "end": { + "line": 12, + "column": 24 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 405, + "end": 413, + "loc": { + "start": { + "line": 14, + "column": 4 + }, + "end": { + "line": 14, + "column": 12 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 415, + "end": 420, + "loc": { + "start": { + "line": 14, + "column": 14 + }, + "end": { + "line": 14, + "column": 19 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 451, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 442, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 18, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 322, + "end": 366, + "loc": { + "start": { + "line": 6, + "column": 1 + }, + "end": { + "line": 10, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 322, + "end": 365, + "loc": { + "start": { + "line": 6, + "column": 1 + }, + "end": { + "line": 10, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 322, + "end": 324, + "loc": { + "start": { + "line": 6, + "column": 1 + }, + "end": { + "line": 6, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 328, + "end": 329, + "loc": { + "start": { + "line": 7, + "column": 2 + }, + "end": { + "line": 7, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 330, + "end": 338 + } + ] + }, + { + "type": "TemplateLiteral", + "start": 348, + "end": 361, + "loc": { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 9, + "column": 6 + } + }, + "expressions": [], + "quasis": [ + { + "type": "TemplateElement", + "start": 349, + "end": 360, + "loc": { + "start": { + "line": 8, + "column": 3 + }, + "end": { + "line": 9, + "column": 5 + } + }, + "value": { + "raw": "line1\nline2", + "cooked": "line1\nline2" + }, + "tail": true + } + ], + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 340, + "end": 345 + } + ] + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " When a call has a multiline-content argument (here a multiline template), the", + "start": 20, + "end": 100 + }, + { + "type": "Line", + "value": " args route through the shared joined-args path. A non-last argument carrying a", + "start": 102, + "end": 183 + }, + { + "type": "Line", + "value": " block comment before the comma and a line comment after it must keep both — the", + "start": 185, + "end": 267 + }, + { + "type": "Line", + "value": " block was dropped when a line comment coexisted.", + "start": 269, + "end": 320 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 394, + "end": 441, + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 17, + "column": 3 + } + }, + "expression": { + "type": "NewExpression", + "start": 394, + "end": 440, + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 17, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 398, + "end": 399, + "loc": { + "start": { + "line": 13, + "column": 5 + }, + "end": { + "line": 13, + "column": 6 + } + }, + "name": "A" + }, + "arguments": [ + { + "type": "Identifier", + "start": 403, + "end": 404, + "loc": { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 14, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 405, + "end": 413 + } + ] + }, + { + "type": "TemplateLiteral", + "start": 423, + "end": 436, + "loc": { + "start": { + "line": 15, + "column": 2 + }, + "end": { + "line": 16, + "column": 6 + } + }, + "expressions": [], + "quasis": [ + { + "type": "TemplateElement", + "start": 424, + "end": 435, + "loc": { + "start": { + "line": 15, + "column": 3 + }, + "end": { + "line": 16, + "column": 5 + } + }, + "value": { + "raw": "line1\nline2", + "cooked": "line1\nline2" + }, + "tail": true + } + ], + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 415, + "end": 420 + } + ] + } + ] + }, + "leadingComments": [ + { + "type": "Line", + "value": " Same path for `new`.", + "start": 369, + "end": 392 + } + ] + } + ], + "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/expressions/calls/multiline_arg_nonlast_block_then_line_comment/input.svelte b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_block_then_line_comment/input.svelte new file mode 100644 index 00000000..94d5297c --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_block_then_line_comment/input.svelte @@ -0,0 +1,18 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_block_then_line_comment/unformatted_compact.svelte b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_block_then_line_comment/unformatted_compact.svelte new file mode 100644 index 00000000..ef324539 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_block_then_line_comment/unformatted_compact.svelte @@ -0,0 +1,14 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_block_then_line_comment/expected.json b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_block_then_line_comment/expected.json new file mode 100644 index 00000000..d100a20c --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_block_then_line_comment/expected.json @@ -0,0 +1,611 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 597, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " A non-last `new` argument with a block comment before the comma and a line", + "start": 20, + "end": 97, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 78 + } + } + }, + { + "type": "Line", + "value": " comment after it: the block trails the argument, the line comment trails the", + "start": 99, + "end": 178, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 80 + } + } + }, + { + "type": "Line", + "value": " comma. The block is neither dropped nor moved past the comma.", + "start": 180, + "end": 244, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 65 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 257, + "end": 265, + "loc": { + "start": { + "line": 6, + "column": 4 + }, + "end": { + "line": 6, + "column": 12 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 267, + "end": 272, + "loc": { + "start": { + "line": 6, + "column": 14 + }, + "end": { + "line": 6, + "column": 19 + } + } + }, + { + "type": "Line", + "value": " Two block comments before the comma, then a line comment after it.", + "start": 284, + "end": 353, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 70 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 366, + "end": 374, + "loc": { + "start": { + "line": 12, + "column": 4 + }, + "end": { + "line": 12, + "column": 12 + } + } + }, + { + "type": "Block", + "value": " c2 ", + "start": 375, + "end": 383, + "loc": { + "start": { + "line": 12, + "column": 13 + }, + "end": { + "line": 12, + "column": 21 + } + } + }, + { + "type": "Line", + "value": " c3", + "start": 385, + "end": 390, + "loc": { + "start": { + "line": 12, + "column": 23 + }, + "end": { + "line": 12, + "column": 28 + } + } + }, + { + "type": "Line", + "value": " A blank line after the commented arg routes through the blank-line args path;", + "start": 402, + "end": 482, + "loc": { + "start": { + "line": 16, + "column": 1 + }, + "end": { + "line": 16, + "column": 81 + } + } + }, + { + "type": "Line", + "value": " the block must still be preserved before the comma there too.", + "start": 484, + "end": 548, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 65 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 561, + "end": 569, + "loc": { + "start": { + "line": 19, + "column": 4 + }, + "end": { + "line": 19, + "column": 12 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 571, + "end": 576, + "loc": { + "start": { + "line": 19, + "column": 14 + }, + "end": { + "line": 19, + "column": 19 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 596, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 587, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 23, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 246, + "end": 281, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 8, + "column": 3 + } + }, + "expression": { + "type": "NewExpression", + "start": 246, + "end": 280, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 8, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 250, + "end": 251, + "loc": { + "start": { + "line": 5, + "column": 5 + }, + "end": { + "line": 5, + "column": 6 + } + }, + "name": "A" + }, + "arguments": [ + { + "type": "Identifier", + "start": 255, + "end": 256, + "loc": { + "start": { + "line": 6, + "column": 2 + }, + "end": { + "line": 6, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 257, + "end": 265 + } + ] + }, + { + "type": "Identifier", + "start": 275, + "end": 276, + "loc": { + "start": { + "line": 7, + "column": 2 + }, + "end": { + "line": 7, + "column": 3 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 267, + "end": 272 + } + ] + } + ] + }, + "leadingComments": [ + { + "type": "Line", + "value": " A non-last `new` argument with a block comment before the comma and a line", + "start": 20, + "end": 97 + }, + { + "type": "Line", + "value": " comment after it: the block trails the argument, the line comment trails the", + "start": 99, + "end": 178 + }, + { + "type": "Line", + "value": " comma. The block is neither dropped nor moved past the comma.", + "start": 180, + "end": 244 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 355, + "end": 399, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 14, + "column": 3 + } + }, + "expression": { + "type": "NewExpression", + "start": 355, + "end": 398, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 14, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 359, + "end": 360, + "loc": { + "start": { + "line": 11, + "column": 5 + }, + "end": { + "line": 11, + "column": 6 + } + }, + "name": "A" + }, + "arguments": [ + { + "type": "Identifier", + "start": 364, + "end": 365, + "loc": { + "start": { + "line": 12, + "column": 2 + }, + "end": { + "line": 12, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 366, + "end": 374 + } + ] + }, + { + "type": "Identifier", + "start": 393, + "end": 394, + "loc": { + "start": { + "line": 13, + "column": 2 + }, + "end": { + "line": 13, + "column": 3 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Block", + "value": " c2 ", + "start": 375, + "end": 383 + }, + { + "type": "Line", + "value": " c3", + "start": 385, + "end": 390 + } + ] + } + ] + }, + "leadingComments": [ + { + "type": "Line", + "value": " Two block comments before the comma, then a line comment after it.", + "start": 284, + "end": 353 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 550, + "end": 586, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 22, + "column": 3 + } + }, + "expression": { + "type": "NewExpression", + "start": 550, + "end": 585, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 22, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 554, + "end": 555, + "loc": { + "start": { + "line": 18, + "column": 5 + }, + "end": { + "line": 18, + "column": 6 + } + }, + "name": "A" + }, + "arguments": [ + { + "type": "Identifier", + "start": 559, + "end": 560, + "loc": { + "start": { + "line": 19, + "column": 2 + }, + "end": { + "line": 19, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 561, + "end": 569 + } + ] + }, + { + "type": "Identifier", + "start": 580, + "end": 581, + "loc": { + "start": { + "line": 21, + "column": 2 + }, + "end": { + "line": 21, + "column": 3 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 571, + "end": 576 + } + ] + } + ] + }, + "leadingComments": [ + { + "type": "Line", + "value": " A blank line after the commented arg routes through the blank-line args path;", + "start": 402, + "end": 482 + }, + { + "type": "Line", + "value": " the block must still be preserved before the comma there too.", + "start": 484, + "end": 548 + } + ] + } + ], + "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/expressions/calls/new_nonlast_arg_block_then_line_comment/input.svelte b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_block_then_line_comment/input.svelte new file mode 100644 index 00000000..1e95f9fa --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_block_then_line_comment/input.svelte @@ -0,0 +1,23 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_block_then_line_comment/unformatted_compact.svelte b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_block_then_line_comment/unformatted_compact.svelte new file mode 100644 index 00000000..020afe56 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_block_then_line_comment/unformatted_compact.svelte @@ -0,0 +1,17 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_block_then_line_comment/expected.json b/tests/fixtures/typescript/expressions/calls/nonlast_arg_block_then_line_comment/expected.json new file mode 100644 index 00000000..d66b9b8b --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_block_then_line_comment/expected.json @@ -0,0 +1,608 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 490, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " A non-last argument with a block comment before the comma and a line comment", + "start": 20, + "end": 99, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 80 + } + } + }, + { + "type": "Line", + "value": " after it: the block trails the argument, the line comment trails the comma.", + "start": 101, + "end": 179, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 79 + } + } + }, + { + "type": "Line", + "value": " Neither is dropped.", + "start": 181, + "end": 203, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 23 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 213, + "end": 221, + "loc": { + "start": { + "line": 6, + "column": 4 + }, + "end": { + "line": 6, + "column": 12 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 223, + "end": 228, + "loc": { + "start": { + "line": 6, + "column": 14 + }, + "end": { + "line": 6, + "column": 19 + } + } + }, + { + "type": "Line", + "value": " Two block comments before the comma, then a line comment after it: all kept.", + "start": 240, + "end": 319, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 80 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 329, + "end": 337, + "loc": { + "start": { + "line": 12, + "column": 4 + }, + "end": { + "line": 12, + "column": 12 + } + } + }, + { + "type": "Block", + "value": " c2 ", + "start": 338, + "end": 346, + "loc": { + "start": { + "line": 12, + "column": 13 + }, + "end": { + "line": 12, + "column": 21 + } + } + }, + { + "type": "Line", + "value": " c3", + "start": 348, + "end": 353, + "loc": { + "start": { + "line": 12, + "column": 23 + }, + "end": { + "line": 12, + "column": 28 + } + } + }, + { + "type": "Line", + "value": " The block+line argument in the middle of three args (not just the last).", + "start": 365, + "end": 440, + "loc": { + "start": { + "line": 16, + "column": 1 + }, + "end": { + "line": 16, + "column": 76 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 450, + "end": 458, + "loc": { + "start": { + "line": 18, + "column": 4 + }, + "end": { + "line": 18, + "column": 12 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 460, + "end": 465, + "loc": { + "start": { + "line": 18, + "column": 14 + }, + "end": { + "line": 18, + "column": 19 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 489, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 480, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 22, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 205, + "end": 237, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 8, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 205, + "end": 236, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 8, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 205, + "end": 207, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 211, + "end": 212, + "loc": { + "start": { + "line": 6, + "column": 2 + }, + "end": { + "line": 6, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 213, + "end": 221 + } + ] + }, + { + "type": "Identifier", + "start": 231, + "end": 232, + "loc": { + "start": { + "line": 7, + "column": 2 + }, + "end": { + "line": 7, + "column": 3 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 223, + "end": 228 + } + ] + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " A non-last argument with a block comment before the comma and a line comment", + "start": 20, + "end": 99 + }, + { + "type": "Line", + "value": " after it: the block trails the argument, the line comment trails the comma.", + "start": 101, + "end": 179 + }, + { + "type": "Line", + "value": " Neither is dropped.", + "start": 181, + "end": 203 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 321, + "end": 362, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 14, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 321, + "end": 361, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 14, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 321, + "end": 323, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 327, + "end": 328, + "loc": { + "start": { + "line": 12, + "column": 2 + }, + "end": { + "line": 12, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 329, + "end": 337 + } + ] + }, + { + "type": "Identifier", + "start": 356, + "end": 357, + "loc": { + "start": { + "line": 13, + "column": 2 + }, + "end": { + "line": 13, + "column": 3 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Block", + "value": " c2 ", + "start": 338, + "end": 346 + }, + { + "type": "Line", + "value": " c3", + "start": 348, + "end": 353 + } + ] + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " Two block comments before the comma, then a line comment after it: all kept.", + "start": 240, + "end": 319 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 442, + "end": 479, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 21, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 442, + "end": 478, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 21, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 442, + "end": 444, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 448, + "end": 449, + "loc": { + "start": { + "line": 18, + "column": 2 + }, + "end": { + "line": 18, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 450, + "end": 458 + } + ] + }, + { + "type": "Identifier", + "start": 468, + "end": 469, + "loc": { + "start": { + "line": 19, + "column": 2 + }, + "end": { + "line": 19, + "column": 3 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 460, + "end": 465 + } + ] + }, + { + "type": "Identifier", + "start": 473, + "end": 474, + "loc": { + "start": { + "line": 20, + "column": 2 + }, + "end": { + "line": 20, + "column": 3 + } + }, + "name": "c" + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " The block+line argument in the middle of three args (not just the last).", + "start": 365, + "end": 440 + } + ] + } + ], + "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/expressions/calls/nonlast_arg_block_then_line_comment/input.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_block_then_line_comment/input.svelte new file mode 100644 index 00000000..939b7453 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_block_then_line_comment/input.svelte @@ -0,0 +1,22 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_block_then_line_comment/unformatted_compact.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_block_then_line_comment/unformatted_compact.svelte new file mode 100644 index 00000000..1ced64c0 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_block_then_line_comment/unformatted_compact.svelte @@ -0,0 +1,15 @@ + diff --git a/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/README.md b/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/README.md new file mode 100644 index 00000000..33fcdef6 --- /dev/null +++ b/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/README.md @@ -0,0 +1,25 @@ +# Consecutive line comments before a ternary operator + +Two or more line comments in the gap between a ternary operand and the +following operator — between the **test** and `?`, or between the +**consequent** and `:`. + +- **tsv**: the first comment trails its operand (`cond // c1`); each subsequent + comment drops to its own line, aligned with the operator it precedes + (`// c2` on the line above `?`). Position preserved, none merged. +- **prettier**: relocates every comment after the first to the *other* side of + the operator (`? // c2`), moving it from "before `?`" to "after `?`" — a + change of syntactic association. + +A single trailing comment (`cond // c1 ? …`) is a **match** and stays a plain +fixture (see `basic_comment`, `test_trailing_long_comment`). The divergence +only appears once a second comment forces the question of where it lands. + +This is the before-operator mirror of `consecutive_branch_comment` (which keeps +consecutive comments *after* `?`/`:` in place — a match) and a direct +application of the [comment-position +philosophy](../../../../../../docs/conformance_prettier.md#comment-position-philosophy) +principle 1: "comments between an operator and its operand stay there." It is +also the ternary face of prettier's information-destructive comment motion +documented for property signatures (two leading line comments `merge and +reverse`); tsv preserves each comment as its own node. diff --git a/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/expected.json b/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/expected.json new file mode 100644 index 00000000..08db4724 --- /dev/null +++ b/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/expected.json @@ -0,0 +1,981 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 896, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " Two line comments between the test and `?`: the first trails the test, the", + "start": 20, + "end": 97, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 78 + } + } + }, + { + "type": "Line", + "value": " second drops to its own line aligned with `?`. A trailing comment on the", + "start": 99, + "end": 174, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 76 + } + } + }, + { + "type": "Line", + "value": " consequent (`// c3`) stays put. Neither merges nor reverses.", + "start": 176, + "end": 239, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 64 + } + } + }, + { + "type": "Line", + "value": " c1", + "start": 256, + "end": 261, + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 21 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 264, + "end": 269, + "loc": { + "start": { + "line": 6, + "column": 2 + }, + "end": { + "line": 6, + "column": 7 + } + } + }, + { + "type": "Line", + "value": " c3", + "start": 276, + "end": 281, + "loc": { + "start": { + "line": 7, + "column": 6 + }, + "end": { + "line": 7, + "column": 11 + } + } + }, + { + "type": "Line", + "value": " Three line comments between the test and `?`: each after the first gets its", + "start": 291, + "end": 369, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 79 + } + } + }, + { + "type": "Line", + "value": " own line.", + "start": 371, + "end": 383, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 13 + } + } + }, + { + "type": "Line", + "value": " c1", + "start": 400, + "end": 405, + "loc": { + "start": { + "line": 12, + "column": 16 + }, + "end": { + "line": 12, + "column": 21 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 408, + "end": 413, + "loc": { + "start": { + "line": 13, + "column": 2 + }, + "end": { + "line": 13, + "column": 7 + } + } + }, + { + "type": "Line", + "value": " c3", + "start": 416, + "end": 421, + "loc": { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 14, + "column": 7 + } + } + }, + { + "type": "Line", + "value": " Two line comments between the consequent and `:`: the first trails the", + "start": 437, + "end": 510, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 18, + "column": 74 + } + } + }, + { + "type": "Line", + "value": " consequent, the second drops to its own line aligned with `:`.", + "start": 512, + "end": 577, + "loc": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 66 + } + } + }, + { + "type": "Line", + "value": " c1", + "start": 600, + "end": 605, + "loc": { + "start": { + "line": 21, + "column": 6 + }, + "end": { + "line": 21, + "column": 11 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 608, + "end": 613, + "loc": { + "start": { + "line": 22, + "column": 2 + }, + "end": { + "line": 22, + "column": 7 + } + } + }, + { + "type": "Line", + "value": " Boundary (a MATCH, not a divergence): a block + line comment authored on the", + "start": 623, + "end": 702, + "loc": { + "start": { + "line": 25, + "column": 1 + }, + "end": { + "line": 25, + "column": 80 + } + } + }, + { + "type": "Line", + "value": " test's own line both stay trailing — only later-line comments move. A `//`", + "start": 704, + "end": 781, + "loc": { + "start": { + "line": 26, + "column": 1 + }, + "end": { + "line": 26, + "column": 78 + } + } + }, + { + "type": "Line", + "value": " ends its line, so it is always last in a same-line run.", + "start": 783, + "end": 841, + "loc": { + "start": { + "line": 27, + "column": 1 + }, + "end": { + "line": 27, + "column": 59 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 858, + "end": 866, + "loc": { + "start": { + "line": 28, + "column": 16 + }, + "end": { + "line": 28, + "column": 24 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 867, + "end": 872, + "loc": { + "start": { + "line": 28, + "column": 25 + }, + "end": { + "line": 28, + "column": 30 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 895, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 886, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 31, + "column": 9 + } + }, + "body": [ + { + "type": "VariableDeclaration", + "start": 241, + "end": 288, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 8, + "column": 6 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 247, + "end": 287, + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 8, + "column": 5 + } + }, + "id": { + "type": "Identifier", + "start": 247, + "end": 248, + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 8 + } + }, + "name": "a" + }, + "init": { + "type": "ConditionalExpression", + "start": 251, + "end": 287, + "loc": { + "start": { + "line": 5, + "column": 11 + }, + "end": { + "line": 8, + "column": 5 + } + }, + "test": { + "type": "Identifier", + "start": 251, + "end": 255, + "loc": { + "start": { + "line": 5, + "column": 11 + }, + "end": { + "line": 5, + "column": 15 + } + }, + "name": "cond", + "trailingComments": [ + { + "type": "Line", + "value": " c1", + "start": 256, + "end": 261 + } + ] + }, + "consequent": { + "type": "Identifier", + "start": 274, + "end": 275, + "loc": { + "start": { + "line": 7, + "column": 4 + }, + "end": { + "line": 7, + "column": 5 + } + }, + "name": "x", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 264, + "end": 269 + } + ], + "trailingComments": [ + { + "type": "Line", + "value": " c3", + "start": 276, + "end": 281 + } + ] + }, + "alternate": { + "type": "Identifier", + "start": 286, + "end": 287, + "loc": { + "start": { + "line": 8, + "column": 4 + }, + "end": { + "line": 8, + "column": 5 + } + }, + "name": "y" + } + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " Two line comments between the test and `?`: the first trails the test, the", + "start": 20, + "end": 97 + }, + { + "type": "Line", + "value": " second drops to its own line aligned with `?`. A trailing comment on the", + "start": 99, + "end": 174 + }, + { + "type": "Line", + "value": " consequent (`// c3`) stays put. Neither merges nor reverses.", + "start": 176, + "end": 239 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 385, + "end": 434, + "loc": { + "start": { + "line": 12, + "column": 1 + }, + "end": { + "line": 16, + "column": 6 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 391, + "end": 433, + "loc": { + "start": { + "line": 12, + "column": 7 + }, + "end": { + "line": 16, + "column": 5 + } + }, + "id": { + "type": "Identifier", + "start": 391, + "end": 392, + "loc": { + "start": { + "line": 12, + "column": 7 + }, + "end": { + "line": 12, + "column": 8 + } + }, + "name": "b" + }, + "init": { + "type": "ConditionalExpression", + "start": 395, + "end": 433, + "loc": { + "start": { + "line": 12, + "column": 11 + }, + "end": { + "line": 16, + "column": 5 + } + }, + "test": { + "type": "Identifier", + "start": 395, + "end": 399, + "loc": { + "start": { + "line": 12, + "column": 11 + }, + "end": { + "line": 12, + "column": 15 + } + }, + "name": "cond", + "trailingComments": [ + { + "type": "Line", + "value": " c1", + "start": 400, + "end": 405 + } + ] + }, + "consequent": { + "type": "Identifier", + "start": 426, + "end": 427, + "loc": { + "start": { + "line": 15, + "column": 4 + }, + "end": { + "line": 15, + "column": 5 + } + }, + "name": "x", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 408, + "end": 413 + }, + { + "type": "Line", + "value": " c3", + "start": 416, + "end": 421 + } + ] + }, + "alternate": { + "type": "Identifier", + "start": 432, + "end": 433, + "loc": { + "start": { + "line": 16, + "column": 4 + }, + "end": { + "line": 16, + "column": 5 + } + }, + "name": "y" + } + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " Three line comments between the test and `?`: each after the first gets its", + "start": 291, + "end": 369 + }, + { + "type": "Line", + "value": " own line.", + "start": 371, + "end": 383 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 579, + "end": 620, + "loc": { + "start": { + "line": 20, + "column": 1 + }, + "end": { + "line": 23, + "column": 6 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 585, + "end": 619, + "loc": { + "start": { + "line": 20, + "column": 7 + }, + "end": { + "line": 23, + "column": 5 + } + }, + "id": { + "type": "Identifier", + "start": 585, + "end": 586, + "loc": { + "start": { + "line": 20, + "column": 7 + }, + "end": { + "line": 20, + "column": 8 + } + }, + "name": "c" + }, + "init": { + "type": "ConditionalExpression", + "start": 589, + "end": 619, + "loc": { + "start": { + "line": 20, + "column": 11 + }, + "end": { + "line": 23, + "column": 5 + } + }, + "test": { + "type": "Identifier", + "start": 589, + "end": 593, + "loc": { + "start": { + "line": 20, + "column": 11 + }, + "end": { + "line": 20, + "column": 15 + } + }, + "name": "cond" + }, + "consequent": { + "type": "Identifier", + "start": 598, + "end": 599, + "loc": { + "start": { + "line": 21, + "column": 4 + }, + "end": { + "line": 21, + "column": 5 + } + }, + "name": "x", + "trailingComments": [ + { + "type": "Line", + "value": " c1", + "start": 600, + "end": 605 + } + ] + }, + "alternate": { + "type": "Identifier", + "start": 618, + "end": 619, + "loc": { + "start": { + "line": 23, + "column": 4 + }, + "end": { + "line": 23, + "column": 5 + } + }, + "name": "y", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 608, + "end": 613 + } + ] + } + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " Two line comments between the consequent and `:`: the first trails the", + "start": 437, + "end": 510 + }, + { + "type": "Line", + "value": " consequent, the second drops to its own line aligned with `:`.", + "start": 512, + "end": 577 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 843, + "end": 885, + "loc": { + "start": { + "line": 28, + "column": 1 + }, + "end": { + "line": 30, + "column": 6 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 849, + "end": 884, + "loc": { + "start": { + "line": 28, + "column": 7 + }, + "end": { + "line": 30, + "column": 5 + } + }, + "id": { + "type": "Identifier", + "start": 849, + "end": 850, + "loc": { + "start": { + "line": 28, + "column": 7 + }, + "end": { + "line": 28, + "column": 8 + } + }, + "name": "d" + }, + "init": { + "type": "ConditionalExpression", + "start": 853, + "end": 884, + "loc": { + "start": { + "line": 28, + "column": 11 + }, + "end": { + "line": 30, + "column": 5 + } + }, + "test": { + "type": "Identifier", + "start": 853, + "end": 857, + "loc": { + "start": { + "line": 28, + "column": 11 + }, + "end": { + "line": 28, + "column": 15 + } + }, + "name": "cond", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 858, + "end": 866 + } + ] + }, + "consequent": { + "type": "Identifier", + "start": 877, + "end": 878, + "loc": { + "start": { + "line": 29, + "column": 4 + }, + "end": { + "line": 29, + "column": 5 + } + }, + "name": "x", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 867, + "end": 872 + } + ] + }, + "alternate": { + "type": "Identifier", + "start": 883, + "end": 884, + "loc": { + "start": { + "line": 30, + "column": 4 + }, + "end": { + "line": 30, + "column": 5 + } + }, + "name": "y" + } + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " Boundary (a MATCH, not a divergence): a block + line comment authored on the", + "start": 623, + "end": 702 + }, + { + "type": "Line", + "value": " test's own line both stay trailing — only later-line comments move. A `//`", + "start": 704, + "end": 781 + }, + { + "type": "Line", + "value": " ends its line, so it is always last in a same-line run.", + "start": 783, + "end": 841 + } + ] + } + ], + "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/expressions/ternary/consecutive_operand_comment_prettier_divergence/input.svelte b/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/input.svelte new file mode 100644 index 00000000..48be7a57 --- /dev/null +++ b/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/input.svelte @@ -0,0 +1,31 @@ + diff --git a/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/output_prettier.svelte b/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/output_prettier.svelte new file mode 100644 index 00000000..9393874b --- /dev/null +++ b/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/output_prettier.svelte @@ -0,0 +1,31 @@ + diff --git a/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/unformatted_ours_compact.svelte b/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/unformatted_ours_compact.svelte new file mode 100644 index 00000000..027bcaeb --- /dev/null +++ b/tests/fixtures/typescript/expressions/ternary/consecutive_operand_comment_prettier_divergence/unformatted_ours_compact.svelte @@ -0,0 +1,31 @@ +