Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 1 addition & 12 deletions benches/js/lib/gate_counts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,18 +210,7 @@ export const CORPUS_FORMAT_UNKNOWN_PIN: Record<Language, number> = {
*/
export const CORPUS_FORMAT_PARTIAL_PIN: Record<Language, number> = {
svelte: 1,
// 38 → 36: two reproducible files, `prettier/tests/format/js/test-declarations/
// test_declarations.js` and `prettier/tests/format/js/preserve-line/parameter-list.js`,
// both of which held a `fill_101_boundary` hunk because tsv broke a test call's callback
// parameters to chase print width. Keeping them flat (conformance_prettier.md §Print Width
// Philosophy, "A test call's name") removes that hunk from both — 26/33 → 12/24 and 18/16 →
// 13/15 diff lines. Neither reaches `match` (the match floor is untouched): what remains
// is unexplained, so both files move partial → `unknown` — a real +2 on that pin. Its
// value still reads 114 because the measured count had drifted to 112: earlier landed
// work fixed two unknowns without a re-pin, unnoticed at this gate's release cadence, and
// the +2 lands exactly back on the pinned value. No other reproducible file moves bucket
// with this change, in any tier.
typescript: 36,
typescript: 37,
css: 9
};

Expand Down
2 changes: 1 addition & 1 deletion crates/tsv_lang/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ All methods take `&self` (interior mutability via `RefCell`):
- Context — `with_context()`
- Line suffix — `line_suffix()`, `line_suffix_boundary()`, `break_parent()`, `flush_break()` (flush-scoped: forces only the group the deferred run flushes in)
- Convenience — `wrap()`, `parens()`, `brackets()`, `braces()`
- Inspection — `will_break()`, `has_forced_break()`
- Inspection — `will_break()`, `can_break()`
- Transforms — `remove_lines()` / `atomize()` — rebuild a subtree with its lines statically flattened (old nodes stay in the arena, unused). **Two operations, not one function with a strength dial**, so pick by which prettier behavior you want: `remove_lines` is prettier's `removeLines` (breakable lines only; hard lines and `MultilineText` survive — it cannot promise one line), while `atomize` emulates a re-render at `printWidth: Infinity` (hard lines deleted, `conditional_group` collapsed to its least-expanded state). Atomizing is only sound where the caller has proved no newline is required — deleting a hard line fuses the content around it. The atomize contract is asserted directly by a width-invariance test: its result must render identically at every width
- Diagnostics — `line_comment_text_pooled()` (tags `//` text for the swallow check)

Expand Down
39 changes: 0 additions & 39 deletions crates/tsv_lang/src/doc/arena.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1981,45 +1981,6 @@ impl DocArena {
result
}

/// Check if a doc has forced breaks (hardlines only, no should_break groups).
pub fn has_forced_break(&self, id: DocId) -> bool {
let nodes = self.nodes.borrow();
self.has_forced_break_inner(id, &nodes)
}

fn has_forced_break_inner(&self, id: DocId, nodes: &[DocNode]) -> bool {
match &nodes[id.index()] {
// deliberate asymmetry with `will_break_fill`: this predicate stays blind to
// newline-bearing Text on purpose — it is the "less aggressive" (hardlines-only,
// ignores `should_break`) test for the last-argument hug state in the call/new printers
DocNode::Text(_) => false,
DocNode::MultilineText { .. } => true,
DocNode::Line(kind) => matches!(kind, LineKind::Hard | LineKind::Literal),
DocNode::Indent(inner) | DocNode::Dedent(inner) => {
self.has_forced_break_inner(*inner, nodes)
}
DocNode::AlignRoot { contents, .. } | DocNode::Align { contents, .. } => {
self.has_forced_break_inner(*contents, nodes)
}
DocNode::IndentIfBreak { contents, .. } => {
self.has_forced_break_inner(*contents, nodes)
}
DocNode::Group { contents, .. } => self.has_forced_break_inner(*contents, nodes),
DocNode::IfBreak { .. } => false,
DocNode::Concat(range) | DocNode::Fill(range) => {
let children = self.children.borrow();
let kids = range.resolve(&children);
kids.iter()
.any(|&kid| self.has_forced_break_inner(kid, nodes))
}
DocNode::WithContext { doc, .. } => self.has_forced_break_inner(*doc, nodes),
DocNode::LineSuffix(_) => false,
DocNode::LineSuffixBoundary => false,
DocNode::BreakParent => true,
DocNode::FlushBreak => false,
}
}

/// Check if a doc can break (contains any line elements) — Prettier's `canBreak`.
///
/// The dual of [`Self::will_break`]: `will_break` asks whether a doc *must* break,
Expand Down
31 changes: 21 additions & 10 deletions crates/tsv_ts/src/printer/calls/call_formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use super::arg_wrapping::{
build_inline_args, build_inline_or_expand_all, could_expand_arrow_chain,
last_two_args_same_type, prebuild_expand_last_break_body, prebuild_expand_last_obj_array_body,
prepend_arrow_body_comments, should_expand_first_arg, try_hug_multiline_template_arg,
wrap_call_with_hard_breaks, wrap_call_with_soft_breaks,
wrap_call_with_hard_breaks, wrap_call_with_soft_breaks, wrap_call_with_will_break_guard,
};
use super::call_paren_open;
use super::module_paths::{get_module_path_chain_break, is_boolean_call, is_module_path_no_break};
Expand Down Expand Up @@ -460,15 +460,26 @@ pub(super) fn build_call_doc_with_wrapping(
);

// Prettier: group(contents, { shouldBreak: printedArguments.some(willBreak) }).
// The explicit shouldBreak is redundant here: a forced break anywhere in
// `arg_parts` (a non-empty block body's hardlines, a source-multiline object's
// group_break) already breaks this group at render, so the plain group is the
// same layout. This still handles block functions before the last arg
// (`fn((x) => { body }, aaa)`) without the old has_block_function_before_last
// check, which was too aggressive — it forced hardlines for empty block bodies
// like `async () => {}`, preventing calls like `fn([], 3, async () => {}, aaa)`
// from staying on one line.
wrap_call_with_soft_breaks(d, callee, arg_parts)
//
// The explicit shouldBreak is NOT redundant, though it looks it: a forced break in
// `arg_parts` does break this group at *render* (the plain-group arm keys on
// `should_break || will_break(contents)`), but `arena_fits`' Group arm keys only on
// `should_break` — tsv has no propagateBreaks. So inside an outer FLAT fits walk a
// plain group is measured flat to its first hardline, where a `group_break` is
// entered in Break mode and ends the line at its first softline. Prettier's
// propagateBreaks makes its own fits see such a group broken, i.e. like the
// `group_break` side. The two shapes therefore differ wherever an outer flat fits
// measures this call — a `conditional_group` state, a fill part — and the angle-bracket
// cast ladder is a live observer: without the guard, `<A>fn(a, (y) => { … }, c)` is
// measured short enough to select the parenthesized state, printing `<A>(⏎fn(…)⏎)`
// where prettier keeps the cast attached. See
// `expressions/type_assertion_call_block_arg_long`.
//
// This still handles block functions before the last arg (`fn((x) => { body }, aaa)`)
// without the old has_block_function_before_last check, which was too aggressive — it
// forced hardlines for empty block bodies like `async () => {}`, preventing calls like
// `fn([], 3, async () => {}, aaa)` from staying on one line.
wrap_call_with_will_break_guard(d, callee, arg_parts)
}

/// Single-argument comment paths: leading line comments (multi-line expansion)
Expand Down
22 changes: 7 additions & 15 deletions crates/tsv_ts/src/printer/calls/new_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,21 +711,13 @@ impl<'a> Printer<'a> {
);
}

// Different types: if last arg has forced breaks, use inline-or-expand-all
// TODO: call_formatting.rs's twin dropped this screen — its 3-state
// conditional_group lands a forced-break last arg on the hug; converging
// needs a `new`-expression fixture pinning that layout first.
if d.has_forced_break(last_arg_doc) {
return build_inline_or_expand_all(
d,
callee_with_types,
&head_parts,
last_arg_doc,
all_args_broken,
);
}

// No forced breaks: 3-state (inline → hug → expand all)
// Different types: 3-state (inline → hug → expand all), exactly as
// call_formatting.rs's twin does. Prettier's printCallArguments is shared
// by `new` (its header comment lists NewExpression), and for a breaking
// last arg it keeps the hug: `[breakParent, conditionalGroup([hug,
// allArgsBrokenOut])]` — there is no forced-break → inline-or-expand-all
// form. A last arg carrying its own forced break falls out of state 0 and
// lands on the hug, the same layout for both break kinds.
let state_inline =
build_inline_args(d, callee_with_types, &head_parts, last_arg_doc);
let state_hug = d.concat(&[
Expand Down
Loading