fix(oxfmt): make one state conditionalGroup fit up to first hardline (#27275)

Prepare for yaml(FM)-in-mdx-in-md case.
This commit is contained in:
leaysgur committed 2026-10-02 04:28:59 +00:00
1 parent fd4ddce313
commit 16860185da
1 file changed
+71 -41
@@ -208,49 +208,73 @@ fn convert_group<'a>(
let final_state = expanded_states.last().unwrap_or(contents);
return convert_group_contents(Some(final_state), gid, true, out, ctx);
}
// A single state is just a regular group. BestFitting requires at least two variants
let mut variants = ArenaVec::with_capacity_in(expanded_states.len().max(2), &ctx.allocator);
if expanded_states.len() <= 1 {
return convert_group_contents(Some(contents), gid, false, out, ctx);
// A single state still goes through `BestFitting`, as `[flat, expanded]`:
// Prettier never propagates a hardline's break into a conditional group,
// it prints the state flat when it fits up to its first hardline (YAML's `key: >-`), expanded otherwise.
// Without an id the two variants are the same elements, so convert once:
// single-state groups nest per YAML mapping level, converting twice would double per level.
let flat = convert_variant(contents, GroupMode::Flat, gid, ctx)?;
let expanded = if gid.is_some() {
convert_variant(contents, GroupMode::Expand, gid, ctx)?
} else {
flat
};
variants.push(flat);
variants.push(expanded);
} else {
for (index, state) in
std::iter::once(contents).chain(expanded_states.iter().skip(1)).enumerate()
{
let mode = if index + 1 == expanded_states.len() {
GroupMode::Expand
} else {
GroupMode::Flat
};
variants.push(convert_variant(state, mode, gid, ctx)?);
}
}
let mut variants = ArenaVec::with_capacity_in(expanded_states.len(), &ctx.allocator);
for (index, state) in
std::iter::once(contents).chain(expanded_states.iter().skip(1)).enumerate()
{
let mode =
if index + 1 == expanded_states.len() { GroupMode::Expand } else { GroupMode::Flat };
let mut variant = ArenaVec::new_in(&ctx.allocator);
variant.push(FormatElement::Tag(Tag::StartEntry));
// `BestFitting` itself supplies the selected variant's print mode.
// A wrapper group is only needed to publish that mode under Prettier's group ID for `if-break(groupId)` consumers.
// Wrapping an ID-less variant would remeasure it after selection
// and can incorrectly expand an intermediate state that Prettier prints flat.
// (No core Prettier printer passes an id to `conditionalGroup`; this branch is defensive.
// Note the same remeasure hazard reappears here via `propagate_expand` if a variant contains a top-level hard line,
// so revisit before relying on it for real inputs.)
if gid.is_some() {
variant.push(FormatElement::Tag(Tag::StartGroup(
Group::new().with_id(gid).with_mode(mode),
)));
}
convert_doc(state, &mut variant, ctx)?;
if gid.is_some() {
variant.push(FormatElement::Tag(Tag::EndGroup));
}
variant.push(FormatElement::Tag(Tag::EndEntry));
// The trailing `EndEntry` tag keeps postprocess's trailing-hardline strip from firing:
// a variant retains its trailing hardline (content may follow the `BestFitting`).
postprocess(&mut variant, ctx.allocator);
variants.push(variant.into_arena_slice());
}
// SAFETY: `expanded_states.len() > 1`, and the loop emits exactly that many variants.
// SAFETY: Both branches emit at least two variants.
out.push(FormatElement::BestFitting(unsafe {
BestFittingElement::from_vec_unchecked(variants)
}));
Ok(())
}
/// One `BestFitting` variant of a conditional group's `state`, printed in `mode`.
fn convert_variant<'a>(
state: &Value,
mode: GroupMode,
gid: Option<GroupId>,
ctx: &mut FmtCtx<'a, '_>,
) -> Result<&'a [FormatElement<'a>], String> {
let mut variant = ArenaVec::new_in(&ctx.allocator);
variant.push(FormatElement::Tag(Tag::StartEntry));
// `BestFitting` itself supplies the selected variant's print mode.
// A wrapper group is only needed to publish that mode under Prettier's group ID for `if-break(groupId)` consumers.
// Wrapping an ID-less variant would remeasure it after selection
// and can incorrectly expand an intermediate state that Prettier prints flat.
//
// (No core Prettier printer passes an id to `conditionalGroup`; this branch is defensive.
// Note the same remeasure hazard reappears here via `propagate_expand` if a variant contains a top-level hard line,
// so revisit before relying on it for real inputs.)
if gid.is_some() {
variant
.push(FormatElement::Tag(Tag::StartGroup(Group::new().with_id(gid).with_mode(mode))));
}
convert_doc(state, &mut variant, ctx)?;
if gid.is_some() {
variant.push(FormatElement::Tag(Tag::EndGroup));
}
variant.push(FormatElement::Tag(Tag::EndEntry));
// The trailing `EndEntry` tag keeps postprocess's trailing-hardline strip from firing:
// a variant retains its trailing hardline (content may follow the `BestFitting`).
postprocess(&mut variant, ctx.allocator);
Ok(variant.into_arena_slice())
}
fn convert_group_contents<'a>(
contents: Option<&Value>,
gid: Option<GroupId>,
@@ -618,14 +642,20 @@ mod tests {
}
#[test]
fn conditional_group_with_one_state_is_a_regular_group() {
let group = json!({
"type": "group",
"contents": ["a", { "type": "line" }, "b"],
"expandedStates": [["a", { "type": "line" }, "b"]]
});
fn conditional_group_with_one_state_fits_up_to_its_first_hardline() {
let state = json!([
"a:",
{ "type": "line" },
">-",
{ "type": "line", "hard": true },
{ "type": "break-parent" },
"b"
]);
let group = json!({ "type": "group", "contents": state, "expandedStates": [state] });
assert_eq!(print_doc(&group, 80), "a b");
// A hardline does not break a conditional group (Prettier's YAML block scalar key)
assert_eq!(print_doc(&group, 80), "a: >-\nb");
assert_eq!(print_doc(&group, 3), "a:\n>-\nb");
}
#[test]