From 3efc163489feb7f2791e72af1e366a1324decf30 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Thu, 18 Jun 2026 01:11:10 -0400 Subject: [PATCH] fix: comments in function calls --- crates/tsv_lang/src/comment.rs | 60 ++ .../tsv_ts/src/printer/calls/arg_comments.rs | 122 ++-- .../tsv_ts/src/printer/calls/arg_wrapping.rs | 97 +-- .../src/printer/calls/call_formatting.rs | 87 +-- crates/tsv_ts/src/printer/calls/chain_args.rs | 132 ++-- .../src/printer/calls/new_expression.rs | 14 +- .../src/printer/chain/builder/helpers.rs | 10 +- .../src/printer/expressions/conditional.rs | 43 +- docs/conformance_prettier.md | 2 + .../expected.json | 255 ++++++++ .../input.svelte | 8 + .../unformatted_compact.svelte | 3 + .../README.md | 22 + .../expected.json | 347 ++++++++++ .../input.svelte | 11 + .../output_prettier.svelte | 11 + .../unformatted_ours_compact.svelte | 7 + .../README.md | 21 + .../expected.json | 286 +++++++++ .../input.svelte | 10 + .../output_prettier.svelte | 10 + .../unformatted_ours_compact.svelte | 9 + .../expected.json | 567 +++++++++++++++++ .../input.svelte | 26 + .../unformatted_compact.svelte | 13 + .../README.md | 22 + .../expected.json | 437 +++++++++++++ .../input.svelte | 17 + .../output_prettier.svelte | 17 + .../unformatted_ours_compact.svelte | 17 + .../expected.json | 293 +++++++++ .../input.svelte | 13 + .../unformatted_compact.svelte | 7 + .../README.md | 32 + .../expected.json | 593 ++++++++++++++++++ .../input.svelte | 23 + .../output_prettier.svelte | 23 + .../unformatted_ours_compact.svelte | 19 + .../README.md | 34 + .../expected.json | 433 +++++++++++++ .../input.svelte | 16 + .../output_prettier.svelte | 16 + .../unformatted_ours_compact.svelte | 11 + .../expected.json | 347 ++++++++++ .../input.svelte | 6 + .../unformatted_compact.svelte | 6 + 46 files changed, 4295 insertions(+), 260 deletions(-) create mode 100644 tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block/unformatted_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md create mode 100644 tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/README.md create mode 100644 tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/output_prettier.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block/unformatted_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md create mode 100644 tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block/unformatted_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/README.md create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/output_prettier.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/unformatted_ours_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_before_comma_block_inner_comma/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_before_comma_block_inner_comma/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/nonlast_arg_before_comma_block_inner_comma/unformatted_compact.svelte diff --git a/crates/tsv_lang/src/comment.rs b/crates/tsv_lang/src/comment.rs index 25993338..3fb61f7e 100644 --- a/crates/tsv_lang/src/comment.rs +++ b/crates/tsv_lang/src/comment.rs @@ -203,6 +203,33 @@ impl<'a> ClassifiedComments<'a> { && self.leading_block.is_empty() && self.leading_line.is_empty() } + + /// All leading (own-line) comments in source order, merging the `leading_block` + /// and `leading_line` buckets. + /// + /// `from_range` splits leading comments by kind because chain printers emit the + /// two runs separately (all blocks, then all lines). Callers that emit a gap's + /// leading comments in authored order — ternary operand→operator gaps, + /// call-argument gaps — use this instead, so an interleaved block/line sequence + /// keeps the order the author wrote it. Each bucket is already source-sorted, so + /// this is a linear two-way merge on `span.start`. + pub fn leading_in_source_order(&self) -> SmallVec<[&'a Comment; 2]> { + let (block, line) = (&self.leading_block, &self.leading_line); + let mut out: SmallVec<[&'a Comment; 2]> = SmallVec::with_capacity(block.len() + line.len()); + let (mut bi, mut li) = (0, 0); + while bi < block.len() && li < line.len() { + if block[bi].span.start <= line[li].span.start { + out.push(block[bi]); + bi += 1; + } else { + out.push(line[li]); + li += 1; + } + } + out.extend_from_slice(&block[bi..]); + out.extend_from_slice(&line[li..]); + out + } } // @@ -393,6 +420,39 @@ mod tests { )); } + #[test] + fn leading_in_source_order_merges_interleaved_block_and_line() { + // Each leading bucket is source-sorted; the merge must restore authored order + // across an interleaved line / block / line sequence. + let line1 = comment(2, 8, false, " l1"); + let block = comment(15, 22, true, " b "); + let line2 = comment(30, 36, false, " l2"); + let classified = ClassifiedComments { + trailing_block: SmallVec::new(), + trailing_line: SmallVec::new(), + leading_block: SmallVec::from_slice(&[&block]), + leading_line: SmallVec::from_slice(&[&line1, &line2]), + }; + let order: Vec = classified + .leading_in_source_order() + .iter() + .map(|c| c.span.start) + .collect(); + assert_eq!(order, vec![2, 15, 30]); + + // Single-bucket inputs pass through unchanged. + let only_line = ClassifiedComments { + leading_line: SmallVec::from_slice(&[&line1, &line2]), + ..Default::default() + }; + let starts: Vec = only_line + .leading_in_source_order() + .iter() + .map(|c| c.span.start) + .collect(); + assert_eq!(starts, vec![2, 30]); + } + #[test] fn classify_comment_slow_and_fast_agree() { // Offsets: 'x'=0, "// trail"=[2,10), '\n'=10, "/* own */"=[11,20), diff --git a/crates/tsv_ts/src/printer/calls/arg_comments.rs b/crates/tsv_ts/src/printer/calls/arg_comments.rs index d77bff00..544fb67a 100644 --- a/crates/tsv_ts/src/printer/calls/arg_comments.rs +++ b/crates/tsv_ts/src/printer/calls/arg_comments.rs @@ -17,11 +17,41 @@ use tsv_lang::doc::arena::DocId; /// Find the comma position between two argument spans /// -/// Returns the absolute position of the comma in the source, or None if not found. +/// Returns the absolute position of the separating comma in the source, or None +/// if not found. Commas inside comments are skipped: the gap between two argument +/// expressions only ever holds whitespace, comments, stripped parens, and the +/// separating comma — never strings or code — so skipping `/* … */` and `// …` +/// spans is enough to avoid mistaking a comment-internal comma (`a /* p, q */, b`) +/// for the separator. #[inline] pub(crate) fn find_comma_pos(source: &str, start: u32, end: u32) -> Option { - let between = &source[start as usize..end as usize]; - between.find(',').map(|offset| start as usize + offset) + // Byte scan is safe: `,`, `/`, `*`, `\n` are ASCII and never appear as a + // UTF-8 continuation byte, so multibyte content in a comment can't false-match. + let bytes = source.as_bytes(); + let (s, e) = (start as usize, end as usize); + let mut i = s; + while i < e { + match bytes[i] { + b',' => return Some(i), + b'/' if i + 1 < e && bytes[i + 1] == b'*' => { + // Skip a block comment, including its internal commas. + i += 2; + while i + 1 < e && !(bytes[i] == b'*' && bytes[i + 1] == b'/') { + i += 1; + } + i += 2; + } + b'/' if i + 1 < e && bytes[i + 1] == b'/' => { + // Skip a line comment to end of line. + i += 2; + while i < e && bytes[i] != b'\n' { + i += 1; + } + } + _ => i += 1, + } + } + None } /// Find the effective start position for blank-line checking before an arg. @@ -446,11 +476,12 @@ 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. +/// +/// `new` shares the same-line/later-line classification with the ternary +/// (`conditional.rs`) and member-chain (`chain/builder/helpers.rs`) gap printers via +/// `tsv_lang::ClassifiedComments`. This type adds the call-argument-specific emission +/// (`emit_*`) and comma-relative helpers on top; only the emission differs per shape +/// (operator / comma / dot), which is intentional. pub(crate) struct PartitionedComments<'a> { pub trailing_line: SmallVec<[&'a internal::Comment; 2]>, pub trailing_block: SmallVec<[&'a internal::Comment; 2]>, @@ -468,29 +499,57 @@ impl<'a> PartitionedComments<'a> { start: u32, end: u32, ) -> Self { - let mut trailing_line = SmallVec::new(); - let mut trailing_block = SmallVec::new(); - let mut leading = SmallVec::new(); - - for comment in tsv_lang::comments_in_range(comments, start, end) { - if tsv_lang::printing::is_same_line_fast(line_breaks, start, comment.span.start) { - if comment.is_block { - trailing_block.push(comment); - } else { - trailing_line.push(comment); - } - } else { - leading.push(comment); - } - } - + // Share the same-line/later-line classification with the chain and ternary + // gap printers (`tsv_lang::ClassifiedComments`). `leading` keeps the two + // own-line buckets merged in source order — the inline-aware emitter and its + // JSDoc-cast detection rely on the authored order. + let classified = + tsv_lang::ClassifiedComments::from_range(comments, start, end, line_breaks); + let leading = classified.leading_in_source_order(); Self { - trailing_line, - trailing_block, + trailing_line: classified.trailing_line, + trailing_block: classified.trailing_block, leading, } } + /// Respect-the-newline split for a non-last argument gap: move after-comma block + /// comments that **hug** the next argument out of `trailing_block` and into + /// `leading`, so they render as a leading comment on the next argument (`C`). + /// A **stranded** after-comma block (a newline separates it from the next argument) + /// stays in `trailing_block` and renders after the comma on the same line (`A`). + /// + /// The author's placement is preserved in both cases: a comment hugging the next + /// arg leads it; a comment left alone on the comma line stays there. Callers then + /// emit `trailing_block` (before-comma blocks + stranded after-comma) via + /// [`emit_trailing_comments_around_comma`], the line break, then `leading` (own-line + /// comments + hugged after-comma) via [`emit_leading_comments_inline_aware`] — so the + /// rule lives here once and every argument path inherits it. + pub fn route_after_comma_hugging_to_leading( + &mut self, + printer: &Printer<'_>, + arg_end: u32, + next_arg_start: u32, + ) { + let Some(comma_pos) = find_comma_pos(printer.source, arg_end, next_arg_start) else { + return; + }; + let mut kept: SmallVec<[&'a internal::Comment; 2]> = SmallVec::new(); + for comment in self.trailing_block.drain(..) { + if is_comment_after_comma(comment, comma_pos) + && is_comment_inline_with_next(printer, comment.span.end, next_arg_start) + { + // Hugs the next arg → leads it. Source order holds: the hug sits on the + // next arg's line, after any own-line leading comments, so appending keeps + // `leading` sorted. + self.leading.push(comment); + } else { + kept.push(comment); + } + } + self.trailing_block = kept; + } + pub fn has_trailing_line(&self) -> bool { !self.trailing_line.is_empty() } @@ -612,17 +671,6 @@ impl<'a> PartitionedComments<'a> { } } - /// Emit leading comments (on their own lines) with hardlines after each. - /// - /// Used for comments that precede an argument on separate lines. - pub fn emit_leading_comments(&self, parts: &mut Vec, printer: &Printer<'_>) { - let d = printer.d(); - for comment in &self.leading { - parts.push(printer.build_comment_doc(comment)); - parts.push(d.hardline()); - } - } - /// Emit leading comments, keeping inline block comments on the same line as `next_pos`. /// /// For comments on the same line as `next_pos`, emits them inline (comment + space). diff --git a/crates/tsv_ts/src/printer/calls/arg_wrapping.rs b/crates/tsv_ts/src/printer/calls/arg_wrapping.rs index 6da235b4..d2420447 100644 --- a/crates/tsv_ts/src/printer/calls/arg_wrapping.rs +++ b/crates/tsv_ts/src/printer/calls/arg_wrapping.rs @@ -11,8 +11,7 @@ use super::super::{ }; use super::arg_comments::{ PartitionedComments, emit_first_arg_leading_comments, find_comma_pos, - has_blank_line_between_args, is_comment_after_comma, is_comment_before_comma, - is_inline_block_after_comma, is_inline_block_before_comma, + has_blank_line_between_args, is_inline_block_after_comma, is_inline_block_before_comma, }; use super::arg_predicates::{is_block_function, is_short_second_arg_for_expand_first}; use crate::ast::internal; @@ -694,75 +693,30 @@ pub(crate) fn build_args_joined_with_comments( let next_arg_start = arguments[i + 1].span().start; if printer.has_comments_between(arg_end, next_arg_start) { - let pc = PartitionedComments::new( + let mut pc = PartitionedComments::new( printer.comments, printer.line_breaks, arg_end, next_arg_start, ); - let comma_pos = find_comma_pos(printer.source, arg_end, next_arg_start); - - 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 { - if is_comment_before_comma(comment, cpos) { - parts.push(d.text(" ")); - parts.push(printer.build_comment_doc(comment)); - } - } - } - parts.push(d.text(",")); - - 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 { - if is_comment_after_comma(comment, cpos) { - parts.push(printer.build_comment_doc(comment)); - parts.push(d.text(" ")); - } - } - } - } else { - // 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) { - parts.push(d.text(" ")); - parts.push(printer.build_comment_doc(comment)); - } - } - } - parts.push(d.line()); - } + // Respect-the-newline: an after-comma block hugging the next arg leads it + // (`C`); a stranded one stays on the comma line (`A`). + pc.route_after_comma_hugging_to_leading(printer, arg_end, next_arg_start); + // before-comma blocks trail the arg, the comma, stranded after-comma blocks + // (`A`), then a same-line line comment via `line_suffix` (zero width). + pc.emit_trailing_comments_around_comma( + &mut parts, + printer, + arg_end, + next_arg_start, + ); + // A line comment runs to EOL → hard-break; otherwise honor the caller's style. + parts.push(if pc.has_trailing_line() || use_hardline { + d.hardline() } else { - parts.push(no_comment_sep); - } - - // Leading comments for next arg (own-line comments) + d.line() + }); + // hugging after-comma + own-line comments lead the next arg (`C`). pc.emit_leading_comments_inline_aware(&mut parts, printer, next_arg_start); } else { parts.push(no_comment_sep); @@ -950,16 +904,18 @@ pub(super) fn build_args_with_blank_lines( let next_start = args[i + 1].span().start; if printer.has_comments_between(arg_end, next_start) { - let pc = PartitionedComments::new( + let mut pc = PartitionedComments::new( printer.comments, printer.line_breaks, arg_end, next_start, ); + // Respect-the-newline: an after-comma block hugging the next arg leads it + // (`C`); a stranded one stays on the comma line (`A`). + pc.route_after_comma_hugging_to_leading(printer, arg_end, next_start); - // 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. + // before-comma blocks trail the arg, the comma, stranded after-comma blocks + // (`A`), then a same-line line comment via `line_suffix`. pc.emit_trailing_comments_around_comma( &mut arg_parts, printer, @@ -977,7 +933,8 @@ pub(super) fn build_args_with_blank_lines( arg_parts.push(d.literalline()); } arg_parts.push(d.hardline()); - pc.emit_leading_comments(&mut arg_parts, printer); + // hugging after-comma + own-line comments lead the next arg (`C`). + pc.emit_leading_comments_inline_aware(&mut arg_parts, printer, next_start); } else { arg_parts.push(d.text(",")); // Skip hardline if next arg has blank line diff --git a/crates/tsv_ts/src/printer/calls/call_formatting.rs b/crates/tsv_ts/src/printer/calls/call_formatting.rs index bff76624..e99e0112 100644 --- a/crates/tsv_ts/src/printer/calls/call_formatting.rs +++ b/crates/tsv_ts/src/printer/calls/call_formatting.rs @@ -7,8 +7,7 @@ use super::super::{ParenContext, Printer, has_multiline_content, needs_parens}; use super::arg_comments::{ PartitionedComments, any_comment_forces_expansion, find_comma_pos, first_arg_has_any_comments, has_blank_line_between_args, has_inter_argument_comments, has_trailing_comments_on_args, - is_comment_after_comma, is_comment_before_comma, last_arg_has_comments, - should_force_expansion_for_comments, + is_comment_after_comma, last_arg_has_comments, should_force_expansion_for_comments, }; use super::arg_predicates::{ arrow_has_trailing_param_comments, is_array_or_object_unwrapped, is_concise_numeric_array, @@ -1074,14 +1073,16 @@ pub(super) fn build_call_doc_with_wrapping( force_expansion = true; } - let pc = PartitionedComments::new( + let mut pc = PartitionedComments::new( printer.comments, printer.line_breaks, arg_end, next_arg_start, ); - - let comma_pos = find_comma_pos(printer.source, arg_end, next_arg_start); + // Respect-the-newline: an after-comma block hugging the next arg leads it + // (`C`); a stranded one stays on the comma line (`A`). Reclassify here so + // the shared emit_* helpers below place each correctly. + pc.route_after_comma_hugging_to_leading(printer, arg_end, next_arg_start); let has_blank_line = pc.has_blank_line_in_gap( printer.source, @@ -1089,65 +1090,33 @@ pub(super) fn build_call_doc_with_wrapping( arg_end, next_arg_start, ); - if has_blank_line { + if has_blank_line || pc.has_trailing_line() { force_expansion = true; } - 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) { - arg_parts.push(d.text(" ")); - arg_parts.push(printer.build_comment_doc(comment)); - } - } - } - 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()); - } - // 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 { - for comment in &pc.trailing_block { - if is_comment_after_comma(comment, cpos) { - arg_parts.push(printer.build_comment_doc(comment)); - arg_parts.push(d.text(" ")); - } - } - } - } else { - // No trailing comments, add comma and line - arg_parts.push(d.text(",")); - if has_blank_line { - arg_parts.push(d.literalline()); - } - arg_parts.push(d.line()); + // before-comma blocks trail the arg, then the comma, then stranded + // after-comma blocks (`A`), then the same-line line comment via + // `line_suffix`. When there are no trailing comments this just emits the + // comma. + pc.emit_trailing_comments_around_comma( + &mut arg_parts, + printer, + arg_end, + next_arg_start, + ); + if has_blank_line { + arg_parts.push(d.literalline()); } + // A line comment runs to EOL → hard-break; otherwise a soft line so a + // block-only arg can still collapse inline. + arg_parts.push(if pc.has_trailing_line() { + d.hardline() + } else { + d.line() + }); - // Add leading comments - inline with next arg if on same line + // Leading: own-line comments + after-comma comments that hug the next arg + // (`C`), emitted inline with it. pc.emit_leading_comments_inline_aware(&mut arg_parts, printer, next_arg_start); } else { let has_blank_line = has_blank_line_between_args( diff --git a/crates/tsv_ts/src/printer/calls/chain_args.rs b/crates/tsv_ts/src/printer/calls/chain_args.rs index 5973aa41..2a613189 100644 --- a/crates/tsv_ts/src/printer/calls/chain_args.rs +++ b/crates/tsv_ts/src/printer/calls/chain_args.rs @@ -541,7 +541,7 @@ fn build_call_args_doc_for_chain_impl( call.span.end }; - let pc = PartitionedComments::new( + let mut pc = PartitionedComments::new( printer.comments, printer.line_breaks, arg_end, @@ -551,40 +551,21 @@ fn build_call_args_doc_for_chain_impl( if i < call.arguments.len() - 1 { // Not the last argument let next_arg_start = call.arguments[i + 1].span().start; - let comma_pos = find_comma_pos(printer.source, arg_end, next_arg_start); - // Emit trailing block comments that are BEFORE the comma - if let Some(cpos) = comma_pos { - for comment in &pc.trailing_block { - if is_comment_before_comma(comment, cpos) { - arg_parts.push(d.text(" ")); - arg_parts.push(printer.build_comment_doc(comment)); - } - } - } - - arg_parts.push(d.text(",")); + // Respect-the-newline: an after-comma block hugging the next arg leads it, + // a stranded one stays on the comma line. + pc.route_after_comma_hugging_to_leading(printer, arg_end, next_arg_start); - // Emit trailing line comments (always after comma) - for comment in &pc.trailing_line { - arg_parts.push(d.text(" ")); - arg_parts.push(printer.build_comment_doc(comment)); - } - - // 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) { - arg_parts.push(d.text(" ")); - arg_parts.push(printer.build_comment_doc(comment)); - } - } - } + // Trailing comments split around the comma via the shared helper: + // before-comma blocks trail the arg, stranded after-comma blocks stay on the + // comma line where the author wrote them, and a same-line line comment trails + // via `line_suffix` (so it can't run to EOL and swallow an after-comma block). + pc.emit_trailing_comments_around_comma( + &mut arg_parts, + printer, + arg_end, + next_arg_start, + ); // Skip hardline if next arg has blank line // (blank line preservation at the top of the loop handles the line break) @@ -1251,53 +1232,62 @@ fn build_call_args_doc_for_chain_impl( return d.concat(&parts); } - // Multiple arguments: wrap in group with softlines so they can break - // Comments are placed relative to commas: before-comma → trailing on current arg, - // after-comma → leading on next arg. - let mut arg_docs_with_comments = Vec::new(); + // Multiple arguments: wrap in group with softlines so they can break. Each gap's + // after-comma block comment follows the respect-the-newline rule — hugging the next + // arg → leads it (`C`); stranded on the comma line → stays there (`A`) — via the same + // shared emit_* helpers the force-expanded paths use. A comment-free gap takes the + // cheap `comma_line()` separator (no per-gap comment scan). + let mut arg_parts = Vec::new(); for (i, arg) in call.arguments.iter().enumerate() { - let arg_doc = printer.build_arg_expression_doc(arg); let arg_start = arg.span().start; let arg_end = arg.span().end; let is_first = i == 0; let is_last = i == call.arguments.len() - 1; - // Leading inline block comments - let leading = if !has_any_comments { - None - } else if is_first { - build_inline_leading_comments(printer, paren_open, arg_start) - } else { - build_after_comma_leading_comments( - printer, - call.arguments[i - 1].span().end, - arg_start, - ) - }; + // Leading inline block comments before the first arg (paren → arg gap). + if is_first + && has_any_comments + && let Some(l) = build_inline_leading_comments(printer, paren_open, arg_start) + { + arg_parts.push(l); + } - // Trailing inline block comments - let trailing = if !has_any_comments { - None - } else if is_last { - build_inline_trailing_comments(printer, arg_end, call.span.end) - } else { - build_before_comma_trailing_comments( - printer, - arg_end, - call.arguments[i + 1].span().start, - ) - }; + arg_parts.push(printer.build_arg_expression_doc(arg)); - let doc = match (leading, trailing) { - (Some(l), Some(t)) => d.concat(&[l, arg_doc, t]), - (Some(l), None) => d.concat(&[l, arg_doc]), - (None, Some(t)) => d.concat(&[arg_doc, t]), - (None, None) => arg_doc, - }; - arg_docs_with_comments.push(doc); + if is_last { + // Trailing inline block comments after the last arg (before `)`). + if has_any_comments + && let Some(t) = build_inline_trailing_comments(printer, arg_end, call.span.end) + { + arg_parts.push(t); + } + } else { + let next_arg_start = call.arguments[i + 1].span().start; + if has_any_comments && printer.has_comments_between(arg_end, next_arg_start) { + let mut pc = PartitionedComments::new( + printer.comments, + printer.line_breaks, + arg_end, + next_arg_start, + ); + pc.route_after_comma_hugging_to_leading(printer, arg_end, next_arg_start); + // before-comma blocks trail the arg, the comma, stranded after-comma + // blocks (`A`). + pc.emit_trailing_comments_around_comma( + &mut arg_parts, + printer, + arg_end, + next_arg_start, + ); + arg_parts.push(d.line()); + // hugging after-comma + own-line comments lead the next arg (`C`). + pc.emit_leading_comments_inline_aware(&mut arg_parts, printer, next_arg_start); + } else { + arg_parts.push(d.comma_line()); + } + } } - let arg_parts = d.join_doc(arg_docs_with_comments, d.comma_line()); - parts.push(wrap_args_with_soft_breaks(d, prefix, arg_parts)); + parts.push(wrap_args_with_soft_breaks(d, prefix, d.concat(&arg_parts))); d.concat(&parts) } } diff --git a/crates/tsv_ts/src/printer/calls/new_expression.rs b/crates/tsv_ts/src/printer/calls/new_expression.rs index ffad6b09..f36287b7 100644 --- a/crates/tsv_ts/src/printer/calls/new_expression.rs +++ b/crates/tsv_ts/src/printer/calls/new_expression.rs @@ -401,17 +401,18 @@ impl<'a> Printer<'a> { let arg_end = arg.span().end; let next_arg_start = new_expr.arguments[i + 1].span().start; - let pc = PartitionedComments::new( + let mut pc = PartitionedComments::new( self.comments, self.line_breaks, arg_end, next_arg_start, ); + // Respect-the-newline: an after-comma block hugging the next arg leads + // it (`C`); a stranded one stays on the comma line (`A`). + pc.route_after_comma_hugging_to_leading(self, arg_end, next_arg_start); - // 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. + // before-comma blocks trail the arg, the comma, stranded after-comma + // blocks (`A`), then a same-line line comment via `line_suffix`. pc.emit_trailing_comments_around_comma( &mut arg_parts, self, @@ -419,7 +420,8 @@ impl<'a> Printer<'a> { next_arg_start, ); arg_parts.push(d.hardline()); - pc.emit_leading_comments(&mut arg_parts, self); + // hugging after-comma + own-line comments lead the next arg (`C`). + pc.emit_leading_comments_inline_aware(&mut arg_parts, self, next_arg_start); } else { // Last argument - check for trailing comments before closing paren let arg_end = arg.span().end; diff --git a/crates/tsv_ts/src/printer/chain/builder/helpers.rs b/crates/tsv_ts/src/printer/chain/builder/helpers.rs index 84fe280b..de985eef 100644 --- a/crates/tsv_ts/src/printer/chain/builder/helpers.rs +++ b/crates/tsv_ts/src/printer/chain/builder/helpers.rs @@ -24,10 +24,12 @@ use tsv_lang::printing::has_blank_line_between_strict; /// 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. +// The same-line/later-line classification (`classify_comments` → +// `tsv_lang::ClassifiedComments`) is shared with `conditional.rs` +// split_pre_operator_comments and `calls/arg_comments.rs` PartitionedComments, so the +// "same-line trails, later-line breaks, never merge" rule lives in one place. Only the +// emission differs per shape — dot (here) / operator / comma — which is intentional +// (this dot path also owns blank-line preservation around the leading run). pub(crate) fn push_gap_comments_and_break( parts: &mut Vec, printer: &P, diff --git a/crates/tsv_ts/src/printer/expressions/conditional.rs b/crates/tsv_ts/src/printer/expressions/conditional.rs index 2633fd0e..616fcf30 100644 --- a/crates/tsv_ts/src/printer/expressions/conditional.rs +++ b/crates/tsv_ts/src/printer/expressions/conditional.rs @@ -452,12 +452,12 @@ impl<'a> Printer<'a> { /// 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). + // The same-line/later-line classification is shared via + // `tsv_lang::ClassifiedComments` (also used by `calls/arg_comments.rs` + // PartitionedComments and the member-chain `push_gap_comments_and_break`), so the + // "same-line trails, later-line breaks, never merge" rule lives in one place. Only + // the emission differs per shape — operator (here) / comma / dot — which is + // intentional (separator placement genuinely differs), not drift. fn split_pre_operator_comments( &self, operand_end: u32, @@ -466,13 +466,30 @@ impl<'a> Printer<'a> { 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)); - } + // Same shared same-line/later-line classification as the call-argument + // (`PartitionedComments`) and member-chain (`push_gap_comments_and_break`) + // gap printers. + let classified = tsv_lang::ClassifiedComments::from_range( + self.comments, + operand_end, + gap_end, + self.line_breaks, + ); + // Same-line comments (blocks, then the at-most-one line comment) trail the + // operand in source order; `build_trailing_comment_doc` keeps a block inline + // and routes a line comment through `line_suffix`. + for &comment in classified + .trailing_block + .iter() + .chain(&classified.trailing_line) + { + trailing.push(self.build_trailing_comment_doc(comment)); + } + // Later-line comments drop to their own line before the operator, in source + // order. + for comment in classified.leading_in_source_order() { + own_line.push(d.hardline()); + own_line.push(self.build_comment_doc(comment)); } } diff --git a/docs/conformance_prettier.md b/docs/conformance_prettier.md index f552a852..dccf39b9 100644 --- a/docs/conformance_prettier.md +++ b/docs/conformance_prettier.md @@ -540,6 +540,8 @@ Prettier moves comments between syntactic boundaries into adjacent blocks, paren - Mapped `:` to value (line) → Trailing the member `;` — [mapped_value_line_comment](../tests/fixtures/typescript/types/mapped_value_line_comment_prettier_divergence/) - Type predicate `is` to type (line) → Trailing the body `{` — [predicate_is_line_comment](../tests/fixtures/typescript/types/predicate_is_line_comment_prettier_divergence/) - After last list-member comma → Before the comma — [objects](../tests/fixtures/typescript/expressions/objects/trailing_comma_comment_prettier_divergence/), [patterns](../tests/fixtures/typescript/expressions/patterns/trailing_comma_comment_prettier_divergence/), [arrays](../tests/fixtures/typescript/expressions/arrays/trailing_comma_comment_prettier_divergence/), [destructuring](../tests/fixtures/typescript/expressions/destructuring/trailing_comma_comment_prettier_divergence/), [calls](../tests/fixtures/typescript/expressions/calls/trailing_comma_comment_prettier_divergence/), [imports](../tests/fixtures/typescript/modules/imports/trailing_comma_comment_prettier_divergence/), [exports](../tests/fixtures/typescript/modules/exports/trailing_comma_comment_prettier_divergence/), [params](../tests/fixtures/typescript/declarations/function/trailing_comma_comment_prettier_divergence/), [function_type](../tests/fixtures/typescript/types/function_type/trailing_comma_comment_prettier_divergence/), [tuple](../tests/fixtures/typescript/types/tuple/trailing_comma_comment_prettier_divergence/), [type_params](../tests/fixtures/typescript/types/type_params/trailing_comma_comment_prettier_divergence/) +- Call arg after-comma block + same-line line comment → Block relocated before the comma (tsv keeps it on the comma line) — [plain](../tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/), [new](../tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/), [joined](../tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/), [chain](../tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/) +- Call arg after-comma block, **stranded** (newline before the next arg) → Block relocated before the comma (tsv respects the newline and keeps the stranded block on the comma line). A block instead **hugging** the next arg leads it (`C`) and both formatters agree (plain match) — [stranded](../tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/) - Call open paren `(` trailing → Onto its own line — [open_paren_comment](../tests/fixtures/typescript/expressions/calls/open_paren_comment_prettier_divergence/), [chain](../tests/fixtures/typescript/expressions/calls/chain_open_paren_comment_prettier_divergence/), [new](../tests/fixtures/typescript/expressions/calls/new_open_paren_comment_prettier_divergence/) - Object literal `{` trailing → Onto its own line — [open_brace_comment](../tests/fixtures/typescript/expressions/objects/open_brace_comment_prettier_divergence/) - Array literal `[` trailing → Onto its own line — [open_bracket_comment](../tests/fixtures/typescript/expressions/arrays/open_bracket_comment_prettier_divergence/) diff --git a/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block/expected.json b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block/expected.json new file mode 100644 index 00000000..abfbbeaf --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block/expected.json @@ -0,0 +1,255 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 160, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Block", + "value": " c ", + "start": 70, + "end": 77, + "loc": { + "start": { + "line": 5, + "column": 3 + }, + "end": { + "line": 5, + "column": 10 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 159, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 150, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 8, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 20, + "end": 149, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 7, + "column": 9 + } + }, + "expression": { + "type": "CallExpression", + "start": 20, + "end": 148, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 7, + "column": 8 + } + }, + "callee": { + "type": "MemberExpression", + "start": 20, + "end": 146, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 7, + "column": 6 + } + }, + "object": { + "type": "CallExpression", + "start": 20, + "end": 139, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 6, + "column": 3 + } + }, + "callee": { + "type": "MemberExpression", + "start": 20, + "end": 30, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 3, + "column": 6 + } + }, + "object": { + "type": "Identifier", + "start": 20, + "end": 23, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 4 + } + }, + "name": "foo" + }, + "property": { + "type": "Identifier", + "start": 27, + "end": 30, + "loc": { + "start": { + "line": 3, + "column": 3 + }, + "end": { + "line": 3, + "column": 6 + } + }, + "name": "bar" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 35, + "end": 65, + "loc": { + "start": { + "line": 4, + "column": 3 + }, + "end": { + "line": 4, + "column": 33 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + { + "type": "Identifier", + "start": 78, + "end": 134, + "loc": { + "start": { + "line": 5, + "column": 11 + }, + "end": { + "line": 5, + "column": 67 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "leadingComments": [ + { + "type": "Block", + "value": " c ", + "start": 70, + "end": 77 + } + ] + } + ], + "optional": false + }, + "property": { + "type": "Identifier", + "start": 143, + "end": 146, + "loc": { + "start": { + "line": 7, + "column": 3 + }, + "end": { + "line": 7, + "column": 6 + } + }, + "name": "baz" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "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/nonlast_arg_after_comma_block/input.svelte b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block/input.svelte new file mode 100644 index 00000000..a3450c81 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block/input.svelte @@ -0,0 +1,8 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block/unformatted_compact.svelte b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block/unformatted_compact.svelte new file mode 100644 index 00000000..c422c4bf --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block/unformatted_compact.svelte @@ -0,0 +1,3 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md new file mode 100644 index 00000000..5ec20781 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md @@ -0,0 +1,22 @@ +# nonlast_arg_after_comma_block_then_line_prettier_divergence (chained) + +The member-callee-chain form of the after-comma block+line divergence: a +non-last argument of a chained call with a block comment after the comma plus a +line comment (`a, /* c1 */ // c2`). tsv keeps the block on the comma line, the +line comment trailing via `line_suffix`; Prettier relocates the block before the +comma. + +``` +// tsv // prettier +foo foo + .bar( .bar( + a, /* c1 */ // c2 a /* c1 */, // c2 + b, b, + ) ) + .baz(); .baz(); +``` + +See the plain-call sibling +([nonlast_arg_after_comma_block_then_line](../../nonlast_arg_after_comma_block_then_line_prettier_divergence/)) +for the full rationale, and +[conformance_prettier.md](../../../../../docs/conformance_prettier.md) §Comment relocation. diff --git a/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json new file mode 100644 index 00000000..4e87a282 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json @@ -0,0 +1,347 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 269, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " A non-last argument of a chained call with a block comment after the comma and", + "start": 20, + "end": 101, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 82 + } + } + }, + { + "type": "Line", + "value": " a line comment after it: the block stays on the comma line, the line comment", + "start": 103, + "end": 182, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 80 + } + } + }, + { + "type": "Line", + "value": " trails the comma.", + "start": 184, + "end": 204, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 21 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 224, + "end": 232, + "loc": { + "start": { + "line": 7, + "column": 6 + }, + "end": { + "line": 7, + "column": 14 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 233, + "end": 238, + "loc": { + "start": { + "line": 7, + "column": 15 + }, + "end": { + "line": 7, + "column": 20 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 268, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 259, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 11, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 206, + "end": 258, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 10, + "column": 9 + } + }, + "expression": { + "type": "CallExpression", + "start": 206, + "end": 257, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 10, + "column": 8 + } + }, + "callee": { + "type": "MemberExpression", + "start": 206, + "end": 255, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 10, + "column": 6 + } + }, + "object": { + "type": "CallExpression", + "start": 206, + "end": 248, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 9, + "column": 3 + } + }, + "callee": { + "type": "MemberExpression", + "start": 206, + "end": 216, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 6, + "column": 6 + } + }, + "object": { + "type": "Identifier", + "start": 206, + "end": 209, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 4 + } + }, + "name": "foo" + }, + "property": { + "type": "Identifier", + "start": 213, + "end": 216, + "loc": { + "start": { + "line": 6, + "column": 3 + }, + "end": { + "line": 6, + "column": 6 + } + }, + "name": "bar" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 221, + "end": 222, + "loc": { + "start": { + "line": 7, + "column": 3 + }, + "end": { + "line": 7, + "column": 4 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 224, + "end": 232 + } + ] + }, + { + "type": "Identifier", + "start": 242, + "end": 243, + "loc": { + "start": { + "line": 8, + "column": 3 + }, + "end": { + "line": 8, + "column": 4 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 233, + "end": 238 + } + ] + } + ], + "optional": false + }, + "property": { + "type": "Identifier", + "start": 252, + "end": 255, + "loc": { + "start": { + "line": 10, + "column": 3 + }, + "end": { + "line": 10, + "column": 6 + } + }, + "name": "baz" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " A non-last argument of a chained call with a block comment after the comma and", + "start": 20, + "end": 101 + }, + { + "type": "Line", + "value": " a line comment after it: the block stays on the comma line, the line comment", + "start": 103, + "end": 182 + }, + { + "type": "Line", + "value": " trails the comma.", + "start": 184, + "end": 204 + } + ] + } + ], + "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/nonlast_arg_after_comma_block_then_line_prettier_divergence/input.svelte b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/input.svelte new file mode 100644 index 00000000..1450b1f1 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/input.svelte @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte new file mode 100644 index 00000000..b26e9558 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte new file mode 100644 index 00000000..1686d605 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte @@ -0,0 +1,7 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/README.md b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/README.md new file mode 100644 index 00000000..a1ecfcd0 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/README.md @@ -0,0 +1,21 @@ +# multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence + +The joined-args-path form of the after-comma block+line divergence. A call with +a multiline-content argument (here a multiline template) routes its arguments +through the shared joined-args path; a non-last argument with a block comment +after the comma plus a line comment (`a, /* c1 */ // c2`) keeps the block on the +comma line. Prettier relocates it before the comma. + +``` +// tsv // prettier +fn( fn( + a, /* c1 */ // c2 a /* c1 */, // c2 + `line1 `line1 +line2`, line2`, +); ); +``` + +See the plain-call sibling +([nonlast_arg_after_comma_block_then_line](../nonlast_arg_after_comma_block_then_line_prettier_divergence/)) +for the full rationale, and +[conformance_prettier.md](../../../../docs/conformance_prettier.md) §Comment relocation. diff --git a/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/expected.json b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/expected.json new file mode 100644 index 00000000..343e0e49 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/expected.json @@ -0,0 +1,286 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 319, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " A call with a multiline-content argument routes through the shared joined-args", + "start": 20, + "end": 101, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 82 + } + } + }, + { + "type": "Line", + "value": " path. A non-last argument with a block comment after the comma and a line", + "start": 103, + "end": 179, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 77 + } + } + }, + { + "type": "Line", + "value": " comment after it keeps the block on the comma line, the line comment trailing.", + "start": 181, + "end": 262, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 82 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 273, + "end": 281, + "loc": { + "start": { + "line": 6, + "column": 5 + }, + "end": { + "line": 6, + "column": 13 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 282, + "end": 287, + "loc": { + "start": { + "line": 6, + "column": 14 + }, + "end": { + "line": 6, + "column": 19 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 318, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 309, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 10, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 264, + "end": 308, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 9, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 264, + "end": 307, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 9, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 264, + "end": 266, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 270, + "end": 271, + "loc": { + "start": { + "line": 6, + "column": 2 + }, + "end": { + "line": 6, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 273, + "end": 281 + } + ] + }, + { + "type": "TemplateLiteral", + "start": 290, + "end": 303, + "loc": { + "start": { + "line": 7, + "column": 2 + }, + "end": { + "line": 8, + "column": 6 + } + }, + "expressions": [], + "quasis": [ + { + "type": "TemplateElement", + "start": 291, + "end": 302, + "loc": { + "start": { + "line": 7, + "column": 3 + }, + "end": { + "line": 8, + "column": 5 + } + }, + "value": { + "raw": "line1\nline2", + "cooked": "line1\nline2" + }, + "tail": true + } + ], + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 282, + "end": 287 + } + ] + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " A call with a multiline-content argument routes through the shared joined-args", + "start": 20, + "end": 101 + }, + { + "type": "Line", + "value": " path. A non-last argument with a block comment after the comma and a line", + "start": 103, + "end": 179 + }, + { + "type": "Line", + "value": " comment after it keeps the block on the comma line, the line comment trailing.", + "start": 181, + "end": 262 + } + ] + } + ], + "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_after_comma_block_then_line_prettier_divergence/input.svelte b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/input.svelte new file mode 100644 index 00000000..a58e7deb --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/input.svelte @@ -0,0 +1,10 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/output_prettier.svelte b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/output_prettier.svelte new file mode 100644 index 00000000..13950412 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/output_prettier.svelte @@ -0,0 +1,10 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte new file mode 100644 index 00000000..03e698cd --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte @@ -0,0 +1,9 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block/expected.json b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block/expected.json new file mode 100644 index 00000000..adb13a18 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block/expected.json @@ -0,0 +1,567 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 398, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Block", + "value": " c ", + "start": 67, + "end": 74, + "loc": { + "start": { + "line": 4, + "column": 2 + }, + "end": { + "line": 4, + "column": 9 + } + } + }, + { + "type": "Block", + "value": " c ", + "start": 197, + "end": 204, + "loc": { + "start": { + "line": 9, + "column": 2 + }, + "end": { + "line": 9, + "column": 9 + } + } + }, + { + "type": "Block", + "value": " c ", + "start": 307, + "end": 314, + "loc": { + "start": { + "line": 15, + "column": 2 + }, + "end": { + "line": 15, + "column": 9 + } + } + }, + { + "type": "Block", + "value": " c ", + "start": 367, + "end": 374, + "loc": { + "start": { + "line": 22, + "column": 2 + }, + "end": { + "line": 22, + "column": 9 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 397, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 388, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 26, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 20, + "end": 147, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "expression": { + "type": "NewExpression", + "start": 20, + "end": 146, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 5, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 24, + "end": 25, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 6 + } + }, + "name": "A" + }, + "arguments": [ + { + "type": "Identifier", + "start": 29, + "end": 63, + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 36 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + { + "type": "Identifier", + "start": 75, + "end": 142, + "loc": { + "start": { + "line": 4, + "column": 10 + }, + "end": { + "line": 4, + "column": 77 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "leadingComments": [ + { + "type": "Block", + "value": " c ", + "start": 67, + "end": 74 + } + ] + } + ] + } + }, + { + "type": "ExpressionStatement", + "start": 150, + "end": 290, + "loc": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 11, + "column": 3 + } + }, + "expression": { + "type": "NewExpression", + "start": 150, + "end": 289, + "loc": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 11, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 154, + "end": 155, + "loc": { + "start": { + "line": 7, + "column": 5 + }, + "end": { + "line": 7, + "column": 6 + } + }, + "name": "A" + }, + "arguments": [ + { + "type": "Identifier", + "start": 159, + "end": 193, + "loc": { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 36 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + { + "type": "Identifier", + "start": 205, + "end": 247, + "loc": { + "start": { + "line": 9, + "column": 10 + }, + "end": { + "line": 9, + "column": 52 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "leadingComments": [ + { + "type": "Block", + "value": " c ", + "start": 197, + "end": 204 + } + ] + }, + { + "type": "Identifier", + "start": 251, + "end": 285, + "loc": { + "start": { + "line": 10, + "column": 2 + }, + "end": { + "line": 10, + "column": 36 + } + }, + "name": "cccccccccccccccccccccccccccccccccc" + } + ] + } + }, + { + "type": "ExpressionStatement", + "start": 293, + "end": 350, + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 18, + "column": 3 + } + }, + "expression": { + "type": "NewExpression", + "start": 293, + "end": 349, + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 18, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 297, + "end": 298, + "loc": { + "start": { + "line": 13, + "column": 5 + }, + "end": { + "line": 13, + "column": 6 + } + }, + "name": "A" + }, + "arguments": [ + { + "type": "Identifier", + "start": 302, + "end": 303, + "loc": { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 14, + "column": 3 + } + }, + "name": "a" + }, + { + "type": "ObjectExpression", + "start": 315, + "end": 345, + "loc": { + "start": { + "line": 15, + "column": 10 + }, + "end": { + "line": 17, + "column": 3 + } + }, + "properties": [ + { + "type": "Property", + "start": 320, + "end": 340, + "loc": { + "start": { + "line": 16, + "column": 3 + }, + "end": { + "line": 16, + "column": 23 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 320, + "end": 323, + "loc": { + "start": { + "line": 16, + "column": 3 + }, + "end": { + "line": 16, + "column": 6 + } + }, + "name": "key" + }, + "value": { + "type": "Identifier", + "start": 325, + "end": 340, + "loc": { + "start": { + "line": 16, + "column": 8 + }, + "end": { + "line": 16, + "column": 23 + } + }, + "name": "valuevaluevalue" + }, + "kind": "init" + } + ], + "leadingComments": [ + { + "type": "Block", + "value": " c ", + "start": 307, + "end": 314 + } + ] + } + ] + } + }, + { + "type": "ExpressionStatement", + "start": 353, + "end": 387, + "loc": { + "start": { + "line": 20, + "column": 1 + }, + "end": { + "line": 25, + "column": 3 + } + }, + "expression": { + "type": "NewExpression", + "start": 353, + "end": 386, + "loc": { + "start": { + "line": 20, + "column": 1 + }, + "end": { + "line": 25, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 357, + "end": 358, + "loc": { + "start": { + "line": 20, + "column": 5 + }, + "end": { + "line": 20, + "column": 6 + } + }, + "name": "A" + }, + "arguments": [ + { + "type": "Identifier", + "start": 362, + "end": 363, + "loc": { + "start": { + "line": 21, + "column": 2 + }, + "end": { + "line": 21, + "column": 3 + } + }, + "name": "a" + }, + { + "type": "Identifier", + "start": 375, + "end": 376, + "loc": { + "start": { + "line": 22, + "column": 10 + }, + "end": { + "line": 22, + "column": 11 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Block", + "value": " c ", + "start": 367, + "end": 374 + } + ] + }, + { + "type": "Identifier", + "start": 381, + "end": 382, + "loc": { + "start": { + "line": 24, + "column": 2 + }, + "end": { + "line": 24, + "column": 3 + } + }, + "name": "d" + } + ] + } + } + ], + "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_after_comma_block/input.svelte b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block/input.svelte new file mode 100644 index 00000000..c70e5e81 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block/input.svelte @@ -0,0 +1,26 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block/unformatted_compact.svelte b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block/unformatted_compact.svelte new file mode 100644 index 00000000..05d44b7b --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block/unformatted_compact.svelte @@ -0,0 +1,13 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md new file mode 100644 index 00000000..79542e3d --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md @@ -0,0 +1,22 @@ +# new_nonlast_arg_after_comma_block_then_line_prettier_divergence + +The `new`-argument form of the after-comma block+line divergence: a non-last +`new` argument with a block comment after the comma plus a line comment +(`a, /* c1 */ // c2`). tsv keeps the block on the comma line; Prettier relocates +it before the comma. + +``` +// tsv // prettier +new A( new A( + a, /* c1 */ // c2 a /* c1 */, // c2 + b, b, +); ); +``` + +The blank-line case routes through the blank-line args path, which must +preserve the after-comma position too. + +See the plain-call sibling +([nonlast_arg_after_comma_block_then_line](../nonlast_arg_after_comma_block_then_line_prettier_divergence/)) +for the full rationale, and +[conformance_prettier.md](../../../../docs/conformance_prettier.md) §Comment relocation. diff --git a/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json new file mode 100644 index 00000000..2a78b8a0 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json @@ -0,0 +1,437 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 428, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " A non-last `new` argument with a block comment after the comma and a line", + "start": 20, + "end": 96, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 77 + } + } + }, + { + "type": "Line", + "value": " comment after it: the block stays on the comma line, the line comment trails", + "start": 98, + "end": 177, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 80 + } + } + }, + { + "type": "Line", + "value": " the comma.", + "start": 179, + "end": 192, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 14 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 206, + "end": 214, + "loc": { + "start": { + "line": 6, + "column": 5 + }, + "end": { + "line": 6, + "column": 13 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 215, + "end": 220, + "loc": { + "start": { + "line": 6, + "column": 14 + }, + "end": { + "line": 6, + "column": 19 + } + } + }, + { + "type": "Line", + "value": " A blank line after the commented arg routes through the blank-line args path;", + "start": 232, + "end": 312, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 81 + } + } + }, + { + "type": "Line", + "value": " the after-comma block still stays on the comma line there too.", + "start": 314, + "end": 379, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 66 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 393, + "end": 401, + "loc": { + "start": { + "line": 13, + "column": 5 + }, + "end": { + "line": 13, + "column": 13 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 402, + "end": 407, + "loc": { + "start": { + "line": 13, + "column": 14 + }, + "end": { + "line": 13, + "column": 19 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 427, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 418, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 17, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 194, + "end": 229, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 8, + "column": 3 + } + }, + "expression": { + "type": "NewExpression", + "start": 194, + "end": 228, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 8, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 198, + "end": 199, + "loc": { + "start": { + "line": 5, + "column": 5 + }, + "end": { + "line": 5, + "column": 6 + } + }, + "name": "A" + }, + "arguments": [ + { + "type": "Identifier", + "start": 203, + "end": 204, + "loc": { + "start": { + "line": 6, + "column": 2 + }, + "end": { + "line": 6, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 206, + "end": 214 + } + ] + }, + { + "type": "Identifier", + "start": 223, + "end": 224, + "loc": { + "start": { + "line": 7, + "column": 2 + }, + "end": { + "line": 7, + "column": 3 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 215, + "end": 220 + } + ] + } + ] + }, + "leadingComments": [ + { + "type": "Line", + "value": " A non-last `new` argument with a block comment after the comma and a line", + "start": 20, + "end": 96 + }, + { + "type": "Line", + "value": " comment after it: the block stays on the comma line, the line comment trails", + "start": 98, + "end": 177 + }, + { + "type": "Line", + "value": " the comma.", + "start": 179, + "end": 192 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 381, + "end": 417, + "loc": { + "start": { + "line": 12, + "column": 1 + }, + "end": { + "line": 16, + "column": 3 + } + }, + "expression": { + "type": "NewExpression", + "start": 381, + "end": 416, + "loc": { + "start": { + "line": 12, + "column": 1 + }, + "end": { + "line": 16, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 385, + "end": 386, + "loc": { + "start": { + "line": 12, + "column": 5 + }, + "end": { + "line": 12, + "column": 6 + } + }, + "name": "A" + }, + "arguments": [ + { + "type": "Identifier", + "start": 390, + "end": 391, + "loc": { + "start": { + "line": 13, + "column": 2 + }, + "end": { + "line": 13, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 393, + "end": 401 + } + ] + }, + { + "type": "Identifier", + "start": 411, + "end": 412, + "loc": { + "start": { + "line": 15, + "column": 2 + }, + "end": { + "line": 15, + "column": 3 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 402, + "end": 407 + } + ] + } + ] + }, + "leadingComments": [ + { + "type": "Line", + "value": " A blank line after the commented arg routes through the blank-line args path;", + "start": 232, + "end": 312 + }, + { + "type": "Line", + "value": " the after-comma block still stays on the comma line there too.", + "start": 314, + "end": 379 + } + ] + } + ], + "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_after_comma_block_then_line_prettier_divergence/input.svelte b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/input.svelte new file mode 100644 index 00000000..e5933d0b --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/input.svelte @@ -0,0 +1,17 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte new file mode 100644 index 00000000..0d7e4f3a --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte @@ -0,0 +1,17 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte new file mode 100644 index 00000000..9f5f1dd2 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/new_nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte @@ -0,0 +1,17 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block/expected.json b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block/expected.json new file mode 100644 index 00000000..ea1b857c --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block/expected.json @@ -0,0 +1,293 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 191, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Block", + "value": " c ", + "start": 66, + "end": 73, + "loc": { + "start": { + "line": 4, + "column": 2 + }, + "end": { + "line": 4, + "column": 9 + } + } + }, + { + "type": "Block", + "value": " c ", + "start": 160, + "end": 167, + "loc": { + "start": { + "line": 9, + "column": 2 + }, + "end": { + "line": 9, + "column": 9 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 190, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 181, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 13, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 20, + "end": 146, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 20, + "end": 145, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 5, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 20, + "end": 22, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 26, + "end": 62, + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 38 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + { + "type": "Identifier", + "start": 74, + "end": 141, + "loc": { + "start": { + "line": 4, + "column": 10 + }, + "end": { + "line": 4, + "column": 77 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "leadingComments": [ + { + "type": "Block", + "value": " c ", + "start": 66, + "end": 73 + } + ] + } + ], + "optional": false + } + }, + { + "type": "ExpressionStatement", + "start": 149, + "end": 180, + "loc": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 12, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 149, + "end": 179, + "loc": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 12, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 149, + "end": 151, + "loc": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 155, + "end": 156, + "loc": { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 3 + } + }, + "name": "a" + }, + { + "type": "Identifier", + "start": 168, + "end": 169, + "loc": { + "start": { + "line": 9, + "column": 10 + }, + "end": { + "line": 9, + "column": 11 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Block", + "value": " c ", + "start": 160, + "end": 167 + } + ] + }, + { + "type": "Identifier", + "start": 174, + "end": 175, + "loc": { + "start": { + "line": 11, + "column": 2 + }, + "end": { + "line": 11, + "column": 3 + } + }, + "name": "d" + } + ], + "optional": false + } + } + ], + "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_after_comma_block/input.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block/input.svelte new file mode 100644 index 00000000..95bae4e5 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block/input.svelte @@ -0,0 +1,13 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block/unformatted_compact.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block/unformatted_compact.svelte new file mode 100644 index 00000000..e76ecd82 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block/unformatted_compact.svelte @@ -0,0 +1,7 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/README.md b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/README.md new file mode 100644 index 00000000..07d34561 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/README.md @@ -0,0 +1,32 @@ +# nonlast_arg_after_comma_block_stranded_prettier_divergence + +A non-last argument's block comment **stranded** after the comma — the author put +a newline between the comment and the next argument (`a, /* c */⏎ b`). tsv respects +that newline and keeps the comment where it was written (trailing the comma line); +prettier attaches it to the preceding argument and relocates it **before** the comma. + +``` +// input (author's placement) // tsv (preserve) // prettier (relocate) +fn( fn( fn( + a, /* c */ a, /* c */ a /* c */, + b, b, b, +); ); ); +``` + +This is the stranded counterpart of the hugging case: when the comment instead +**hugs** the next argument (`a, /* c */ b`, no newline between them), tsv leads the +next argument with it (`C`) and both formatters agree — see the plain-match +siblings ([new](../new_nonlast_arg_after_comma_block/), +[plain](../nonlast_arg_after_comma_block/), +[chain](../chained/nonlast_arg_after_comma_block/)). The single rule across all +argument paths: *a comment hugging the next arg leads it; a stranded comment stays +on the comma line.* + +The second `fn` example combines a **before-comma** block with a **stranded** +after-comma block in the same gap (`a /* b1 */, /* s */⏎ b`). Each stays where the +author wrote it — `/* b1 */` before the comma (trailing the arg), `/* s */` after it +— while prettier relocates **both** before the comma (`a /* b1 */ /* s */,`). The +two halves compose: the before-comma rule and the stranded rule hold independently. + +Covers the plain-call, `new`, and chained-call argument paths. See +[conformance_prettier.md](../../../../docs/conformance_prettier.md) §Comment relocation. diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/expected.json b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/expected.json new file mode 100644 index 00000000..57ef9f00 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/expected.json @@ -0,0 +1,593 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 540, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Block", + "value": " c ", + "start": 64, + "end": 71, + "loc": { + "start": { + "line": 3, + "column": 40 + }, + "end": { + "line": 3, + "column": 47 + } + } + }, + { + "type": "Block", + "value": " b1 ", + "start": 186, + "end": 194, + "loc": { + "start": { + "line": 8, + "column": 33 + }, + "end": { + "line": 8, + "column": 41 + } + } + }, + { + "type": "Block", + "value": " s ", + "start": 196, + "end": 203, + "loc": { + "start": { + "line": 8, + "column": 43 + }, + "end": { + "line": 8, + "column": 50 + } + } + }, + { + "type": "Block", + "value": " c ", + "start": 315, + "end": 322, + "loc": { + "start": { + "line": 13, + "column": 40 + }, + "end": { + "line": 13, + "column": 47 + } + } + }, + { + "type": "Block", + "value": " c ", + "start": 447, + "end": 454, + "loc": { + "start": { + "line": 19, + "column": 35 + }, + "end": { + "line": 19, + "column": 42 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 539, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 530, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 23, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 20, + "end": 146, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 20, + "end": 145, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 5, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 20, + "end": 22, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 26, + "end": 62, + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 38 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trailingComments": [ + { + "type": "Block", + "value": " c ", + "start": 64, + "end": 71 + } + ] + }, + { + "type": "Identifier", + "start": 74, + "end": 141, + "loc": { + "start": { + "line": 4, + "column": 2 + }, + "end": { + "line": 4, + "column": 69 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + } + ], + "optional": false + } + }, + { + "type": "ExpressionStatement", + "start": 149, + "end": 265, + "loc": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 10, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 149, + "end": 264, + "loc": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 10, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 149, + "end": 151, + "loc": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 155, + "end": 185, + "loc": { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 32 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trailingComments": [ + { + "type": "Block", + "value": " b1 ", + "start": 186, + "end": 194 + } + ] + }, + { + "type": "Identifier", + "start": 206, + "end": 260, + "loc": { + "start": { + "line": 9, + "column": 2 + }, + "end": { + "line": 9, + "column": 56 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "leadingComments": [ + { + "type": "Block", + "value": " s ", + "start": 196, + "end": 203 + } + ] + } + ], + "optional": false + } + }, + { + "type": "ExpressionStatement", + "start": 268, + "end": 397, + "loc": { + "start": { + "line": 12, + "column": 1 + }, + "end": { + "line": 15, + "column": 3 + } + }, + "expression": { + "type": "NewExpression", + "start": 268, + "end": 396, + "loc": { + "start": { + "line": 12, + "column": 1 + }, + "end": { + "line": 15, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 272, + "end": 273, + "loc": { + "start": { + "line": 12, + "column": 5 + }, + "end": { + "line": 12, + "column": 6 + } + }, + "name": "A" + }, + "arguments": [ + { + "type": "Identifier", + "start": 277, + "end": 313, + "loc": { + "start": { + "line": 13, + "column": 2 + }, + "end": { + "line": 13, + "column": 38 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trailingComments": [ + { + "type": "Block", + "value": " c ", + "start": 315, + "end": 322 + } + ] + }, + { + "type": "Identifier", + "start": 325, + "end": 392, + "loc": { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 14, + "column": 69 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + } + ] + } + }, + { + "type": "ExpressionStatement", + "start": 400, + "end": 529, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 22, + "column": 9 + } + }, + "expression": { + "type": "CallExpression", + "start": 400, + "end": 528, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 22, + "column": 8 + } + }, + "callee": { + "type": "MemberExpression", + "start": 400, + "end": 526, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 22, + "column": 6 + } + }, + "object": { + "type": "CallExpression", + "start": 400, + "end": 519, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 21, + "column": 3 + } + }, + "callee": { + "type": "MemberExpression", + "start": 400, + "end": 410, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 18, + "column": 6 + } + }, + "object": { + "type": "Identifier", + "start": 400, + "end": 403, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 4 + } + }, + "name": "foo" + }, + "property": { + "type": "Identifier", + "start": 407, + "end": 410, + "loc": { + "start": { + "line": 18, + "column": 3 + }, + "end": { + "line": 18, + "column": 6 + } + }, + "name": "bar" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 415, + "end": 445, + "loc": { + "start": { + "line": 19, + "column": 3 + }, + "end": { + "line": 19, + "column": 33 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trailingComments": [ + { + "type": "Block", + "value": " c ", + "start": 447, + "end": 454 + } + ] + }, + { + "type": "Identifier", + "start": 458, + "end": 514, + "loc": { + "start": { + "line": 20, + "column": 3 + }, + "end": { + "line": 20, + "column": 59 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + } + ], + "optional": false + }, + "property": { + "type": "Identifier", + "start": 523, + "end": 526, + "loc": { + "start": { + "line": 22, + "column": 3 + }, + "end": { + "line": 22, + "column": 6 + } + }, + "name": "baz" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "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_after_comma_block_stranded_prettier_divergence/input.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/input.svelte new file mode 100644 index 00000000..87be747f --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/input.svelte @@ -0,0 +1,23 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/output_prettier.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/output_prettier.svelte new file mode 100644 index 00000000..b189ed82 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/output_prettier.svelte @@ -0,0 +1,23 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/unformatted_ours_compact.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/unformatted_ours_compact.svelte new file mode 100644 index 00000000..5ec7c71a --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_stranded_prettier_divergence/unformatted_ours_compact.svelte @@ -0,0 +1,19 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md new file mode 100644 index 00000000..f456afb3 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/README.md @@ -0,0 +1,34 @@ +# nonlast_arg_after_comma_block_then_line_prettier_divergence + +A non-last call argument with a block comment **after** the comma plus a line +comment after it (`a, /* c1 */ // c2`). tsv keeps the block on the comma line +where the author wrote it (the line comment trails via `line_suffix`); Prettier +relocates the block to **before** the comma. + +``` +// tsv // prettier +fn( fn( + a, /* c1 */ // c2 a /* c1 */, // c2 + b, b, +); ); +``` + +## Reason + +tsv treats comment placement as intentional (see Comment Position Philosophy). +The author parked the block after the comma; moving it before the comma is a +syntactic-position change. tsv preserves it in place, idempotently. + +This is the dual of the before-comma case (`a /* c1 */, // c2`), where both +formatters already agree and keep the block before the comma +([nonlast_arg_block_then_line_comment](../nonlast_arg_block_then_line_comment/)). +Prettier canonicalizes both authored positions to before-comma; tsv preserves +whichever the author chose. The same after-comma preservation applies across +every argument path — `new` +([new_nonlast_arg_after_comma_block_then_line](../new_nonlast_arg_after_comma_block_then_line_prettier_divergence/)), +the joined-args path +([multiline_arg_nonlast_after_comma_block_then_line](../multiline_arg_nonlast_after_comma_block_then_line_prettier_divergence/)), +and member-callee chains +([chained/nonlast_arg_after_comma_block_then_line](../chained/nonlast_arg_after_comma_block_then_line_prettier_divergence/)). + +See [conformance_prettier.md](../../../../docs/conformance_prettier.md) §Comment relocation. diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json new file mode 100644 index 00000000..02a1f8c0 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/expected.json @@ -0,0 +1,433 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 373, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " A non-last argument with a block comment after the comma and a line comment", + "start": 20, + "end": 98, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 79 + } + } + }, + { + "type": "Line", + "value": " after it: the block stays on the comma line where it was written, the line", + "start": 100, + "end": 177, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 78 + } + } + }, + { + "type": "Line", + "value": " comment trails the comma.", + "start": 179, + "end": 207, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 29 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 218, + "end": 226, + "loc": { + "start": { + "line": 6, + "column": 5 + }, + "end": { + "line": 6, + "column": 13 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 227, + "end": 232, + "loc": { + "start": { + "line": 6, + "column": 14 + }, + "end": { + "line": 6, + "column": 19 + } + } + }, + { + "type": "Line", + "value": " The after-comma block+line in the middle of three args (not just the first).", + "start": 244, + "end": 323, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 80 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 334, + "end": 342, + "loc": { + "start": { + "line": 12, + "column": 5 + }, + "end": { + "line": 12, + "column": 13 + } + } + }, + { + "type": "Line", + "value": " c2", + "start": 343, + "end": 348, + "loc": { + "start": { + "line": 12, + "column": 14 + }, + "end": { + "line": 12, + "column": 19 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 372, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 363, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 16, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 209, + "end": 241, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 8, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 209, + "end": 240, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 8, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 209, + "end": 211, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 215, + "end": 216, + "loc": { + "start": { + "line": 6, + "column": 2 + }, + "end": { + "line": 6, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 218, + "end": 226 + } + ] + }, + { + "type": "Identifier", + "start": 235, + "end": 236, + "loc": { + "start": { + "line": 7, + "column": 2 + }, + "end": { + "line": 7, + "column": 3 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 227, + "end": 232 + } + ] + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " A non-last argument with a block comment after the comma and a line comment", + "start": 20, + "end": 98 + }, + { + "type": "Line", + "value": " after it: the block stays on the comma line where it was written, the line", + "start": 100, + "end": 177 + }, + { + "type": "Line", + "value": " comment trails the comma.", + "start": 179, + "end": 207 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 325, + "end": 362, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 15, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 325, + "end": 361, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 15, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 325, + "end": 327, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 331, + "end": 332, + "loc": { + "start": { + "line": 12, + "column": 2 + }, + "end": { + "line": 12, + "column": 3 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 334, + "end": 342 + } + ] + }, + { + "type": "Identifier", + "start": 351, + "end": 352, + "loc": { + "start": { + "line": 13, + "column": 2 + }, + "end": { + "line": 13, + "column": 3 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Line", + "value": " c2", + "start": 343, + "end": 348 + } + ] + }, + { + "type": "Identifier", + "start": 356, + "end": 357, + "loc": { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 14, + "column": 3 + } + }, + "name": "c" + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " The after-comma block+line in the middle of three args (not just the first).", + "start": 244, + "end": 323 + } + ] + } + ], + "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_after_comma_block_then_line_prettier_divergence/input.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/input.svelte new file mode 100644 index 00000000..539eb92d --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/input.svelte @@ -0,0 +1,16 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte new file mode 100644 index 00000000..59b09454 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/output_prettier.svelte @@ -0,0 +1,16 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte new file mode 100644 index 00000000..5a2802e8 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_after_comma_block_then_line_prettier_divergence/unformatted_ours_compact.svelte @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_before_comma_block_inner_comma/expected.json b/tests/fixtures/typescript/expressions/calls/nonlast_arg_before_comma_block_inner_comma/expected.json new file mode 100644 index 00000000..cbf74da9 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_before_comma_block_inner_comma/expected.json @@ -0,0 +1,347 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 204, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " A comma inside a block comment is not the argument separator: both", + "start": 20, + "end": 89, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 70 + } + } + }, + { + "type": "Line", + "value": " before-comma blocks stay before the real comma.", + "start": 91, + "end": 141, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 51 + } + } + }, + { + "type": "Block", + "value": " p, q ", + "start": 148, + "end": 158, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 16 + } + } + }, + { + "type": "Block", + "value": " p, q ", + "start": 170, + "end": 180, + "loc": { + "start": { + "line": 5, + "column": 6 + }, + "end": { + "line": 5, + "column": 16 + } + } + }, + { + "type": "Block", + "value": " r ", + "start": 181, + "end": 188, + "loc": { + "start": { + "line": 5, + "column": 17 + }, + "end": { + "line": 5, + "column": 24 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 203, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 194, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 6, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 143, + "end": 163, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 21 + } + }, + "expression": { + "type": "CallExpression", + "start": 143, + "end": 162, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 20 + } + }, + "callee": { + "type": "Identifier", + "start": 143, + "end": 145, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 146, + "end": 147, + "loc": { + "start": { + "line": 4, + "column": 4 + }, + "end": { + "line": 4, + "column": 5 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " p, q ", + "start": 148, + "end": 158 + } + ] + }, + { + "type": "Identifier", + "start": 160, + "end": 161, + "loc": { + "start": { + "line": 4, + "column": 18 + }, + "end": { + "line": 4, + "column": 19 + } + }, + "name": "b" + } + ], + "optional": false + }, + "leadingComments": [ + { + "type": "Line", + "value": " A comma inside a block comment is not the argument separator: both", + "start": 20, + "end": 89 + }, + { + "type": "Line", + "value": " before-comma blocks stay before the real comma.", + "start": 91, + "end": 141 + } + ] + }, + { + "type": "ExpressionStatement", + "start": 165, + "end": 193, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 29 + } + }, + "expression": { + "type": "CallExpression", + "start": 165, + "end": 192, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 28 + } + }, + "callee": { + "type": "Identifier", + "start": 165, + "end": 167, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "Identifier", + "start": 168, + "end": 169, + "loc": { + "start": { + "line": 5, + "column": 4 + }, + "end": { + "line": 5, + "column": 5 + } + }, + "name": "a", + "trailingComments": [ + { + "type": "Block", + "value": " p, q ", + "start": 170, + "end": 180 + } + ] + }, + { + "type": "Identifier", + "start": 190, + "end": 191, + "loc": { + "start": { + "line": 5, + "column": 26 + }, + "end": { + "line": 5, + "column": 27 + } + }, + "name": "b", + "leadingComments": [ + { + "type": "Block", + "value": " r ", + "start": 181, + "end": 188 + } + ] + } + ], + "optional": false + } + } + ], + "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_before_comma_block_inner_comma/input.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_before_comma_block_inner_comma/input.svelte new file mode 100644 index 00000000..48b7ce04 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_before_comma_block_inner_comma/input.svelte @@ -0,0 +1,6 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/nonlast_arg_before_comma_block_inner_comma/unformatted_compact.svelte b/tests/fixtures/typescript/expressions/calls/nonlast_arg_before_comma_block_inner_comma/unformatted_compact.svelte new file mode 100644 index 00000000..e7989c64 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/nonlast_arg_before_comma_block_inner_comma/unformatted_compact.svelte @@ -0,0 +1,6 @@ +