diff --git a/compiler-frontend/formatting/src/printer.rs b/compiler-frontend/formatting/src/printer.rs index 6b98a14db..15515f8c2 100644 --- a/compiler-frontend/formatting/src/printer.rs +++ b/compiler-frontend/formatting/src/printer.rs @@ -65,6 +65,17 @@ impl Tree { .collect() } + fn expression(&self) -> Option<&Tree> { + if self.kind == ExpressionTermArgument { self.children.first() } else { None } + } + + fn is_delimited_block(&self) -> bool { + matches!( + self.kind, + ExpressionParenthesized | ExpressionArray | ExpressionRecord | RecordUpdates + ) && self.has_inline_block() + } + fn ends_offside(&self) -> bool { if matches!( self.kind, @@ -79,12 +90,12 @@ impl Tree { self.elements().last().is_some_and(|child| child.ends_offside()) } - fn has_inline_do(&self) -> bool { + fn has_inline_block(&self) -> bool { match self.kind { - ExpressionDo | ExpressionAdo => true, - ExpressionLetIn | ExpressionIfThenElse | ExpressionCaseOf => false, - WhereExpression => self.children.first().is_some_and(Tree::has_inline_do), - _ => self.children.iter().any(Tree::has_inline_do), + ExpressionDo | ExpressionAdo | ExpressionCaseOf => true, + ExpressionLetIn | ExpressionIfThenElse => false, + WhereExpression => self.children.first().is_some_and(Tree::has_inline_block), + _ => self.children.iter().any(Tree::has_inline_block), } } } @@ -327,10 +338,17 @@ impl<'arena> Printer<'arena, '_> { }; let margin = context.margin + self.config.indent_width; let head = self.fixed(head, Context { following: Some(margin), ..context })?.group(); - if tail.iter().any(|tree| tree.has_inline_do()) { + if tail.iter().any(|tree| tree.has_inline_block()) { let inline = self.fixed(tail, context)?; let broken = self.fixed(tail, Context { margin, ..context })?; - return Ok(head.append(self.attach(first.start, Gap::Soft, inline, broken)?)); + let gap = if first.is_delimited_block() { Gap::Hard(1) } else { Gap::Soft }; + return Ok(head.append(self.attach( + first.start, + gap, + inline, + broken, + self.case_header(first, context)?, + )?)); } let tail = self.fixed(tail, Context { margin, ..context })?; Ok(head @@ -349,6 +367,7 @@ impl<'arena> Printer<'arena, '_> { gap: Gap, inline: Doc<'arena>, broken: Doc<'arena>, + header: Option>, ) -> Result, FormatError> { let arena = self.arena; let indent = self.config.indent_width; @@ -360,6 +379,22 @@ impl<'arena> Printer<'arena, '_> { // comments aligned with the continuation they precede. return Ok(self.boundary(index, gap)?.append(broken).nest(indent as isize)); } + if let Some(header) = header { + // Probe the complete flat header, not the first line of its fallback. + // Branch lines and oversized branch atoms cannot affect attachment. + let probe = arena.fail().flat_alt(self.boundary(index, gap)?.append(header)).group(); + let inline = self.boundary(index, Gap::Space)?.append(inline); + let broken = self.boundary(index, Gap::Hard(1))?.append(broken).nest(indent as isize); + let width = self.config.line_width; + return Ok(arena.column(move |column| { + let mut rendered = String::new(); + if probe.render_fmt(width.saturating_sub(column), &mut rendered).is_ok() { + inline.clone().into_doc() + } else { + broken.clone().into_doc() + } + })); + } if matches!(gap, Gap::Tight | Gap::Space | Gap::BrokenSpace) { return Ok(self.boundary(index, gap)?.append(inline)); } @@ -397,7 +432,13 @@ impl<'arena> Printer<'arena, '_> { head_length, context, context.margin + self.config.indent_width, - |_, _, _| Gap::Soft, + |_, _, current| { + if current.expression().unwrap_or(current).is_delimited_block() { + Gap::Hard(1) + } else { + Gap::Soft + } + }, ) } @@ -422,10 +463,19 @@ impl<'arena> Printer<'arena, '_> { return Ok(self.arena.nil()); } let (head, tail) = elements.split_at(head_length.min(elements.len())); - if tail.is_empty() { + let [first, ..] = tail else { return self.fixed(head, context); - } - let head_document = self.fixed(head, Context { following: Some(margin), ..context })?; + }; + let head_context = Context { following: Some(margin), ..context }; + let head_document = if let [keyword, constraints] = head + && keyword.kind == CLASS + && constraints.kind == ClassConstraints + && constraints.children.first().is_some_and(|tree| tree.kind == LEFT_PARENTHESIS) + { + self.delimited(constraints, head_context, Some((keyword, first)))? + } else { + self.fixed(head, head_context)? + }; let gap = |position: usize| { let previous = if position == 0 { head.last().unwrap() } else { tail[position - 1] }; let element = tail[position]; @@ -437,7 +487,10 @@ impl<'arena> Printer<'arena, '_> { }; let fixed_gaps = (0..tail.len()).all(|position| !matches!(gap(position), Gap::Soft | Gap::SoftEmpty)); - if fixed_gaps || elements.iter().any(|tree| tree.has_inline_do()) { + if fixed_gaps + || elements.iter().any(|tree| tree.has_inline_block()) + || head.last().is_some_and(|tree| tree.kind == ClassConstraints) + { let mut inline_suffix = self.arena.nil(); let mut broken_suffix = self.arena.nil(); for position in (0..tail.len()).rev() { @@ -453,7 +506,13 @@ impl<'arena> Printer<'arena, '_> { inline_suffix = inline; broken_suffix = broken; } else { - inline_suffix = self.attach(element.start, gap, inline, broken.clone())?; + inline_suffix = self.attach( + element.start, + gap, + inline, + broken.clone(), + self.case_header(element, context)?, + )?; broken_suffix = self.boundary(element.start, gap)?.group().append(broken); } } @@ -464,7 +523,7 @@ impl<'arena> Printer<'arena, '_> { let body = if matches!(gap(0), Gap::Soft) { body } else { body.group() }; Ok(head_document .append( - self.boundary(tail[0].start, gap(0))? + self.boundary(first.start, gap(0))? .append(body) .nest((margin - context.margin) as isize), ) @@ -473,10 +532,11 @@ impl<'arena> Printer<'arena, '_> { fn delimited( &self, - elements: &[&Tree], + tree: &Tree, context: Context, - aligned: bool, + prefix: Option<(&Tree, &Tree)>, ) -> Result, FormatError> { + let elements = tree.elements(); let opening = elements .iter() .position(|tree| matches!(tree.kind, LEFT_PARENTHESIS | LEFT_SQUARE | LEFT_CURLY)); @@ -484,102 +544,103 @@ impl<'arena> Printer<'arena, '_> { .iter() .rposition(|tree| matches!(tree.kind, RIGHT_PARENTHESIS | RIGHT_SQUARE | RIGHT_CURLY)); let Some((opening, closing)) = opening.zip(closing) else { - return self.fixed(elements, context); + return self.fixed(&elements, context); }; - if closing == opening + 1 && !aligned { - return self.fixed(elements, context); - } - if aligned { - // Coordinates are relative to the opener, including inline openers. - // Items start after the two-column `{ ` or `, ` prefix; only their - // continuations use the configured indentation width. - let item_context = Context { margin: 2, following: None }; - let punctuation_context = Context { margin: 0, following: None }; - let mut document = self.tree(elements[opening], punctuation_context)?; - let items = elements[opening + 1..closing].iter().flat_map(|element| { - if element.kind == TypeRowTail { element.elements() } else { vec![*element] } - }); - let items = items.collect::>(); - for (position, element) in items.iter().enumerate() { - let punctuation = matches!(element.kind, COMMA | PIPE); - let gap = if position == 0 { - if elements[opening].kind == LEFT_PARENTHESIS { - Gap::BrokenSpace - } else { - Gap::Space - } - } else if element.kind == COMMA { - Gap::SoftEmpty - } else if element.kind == PIPE { - Gap::Soft + // Coordinates are relative to the opener, including inline openers. + // Items start after the two-column `{ ` or `, ` prefix; only their + // continuations use the configured indentation width. + let item_context = Context { margin: 2, following: None }; + let punctuation_context = Context { margin: 0, following: None }; + let mut document = self.tree(elements[opening], punctuation_context)?; + let items = elements[opening + 1..closing].iter().flat_map(|element| { + if element.kind == TypeRowTail { element.elements() } else { vec![*element] } + }); + let items = items.collect::>(); + for (position, element) in items.iter().enumerate() { + let punctuation = matches!(element.kind, COMMA | PIPE); + let gap = if position == 0 { + if elements[opening].kind == LEFT_PARENTHESIS { + Gap::BrokenSpace } else { - self.fixed_gap(element.start) - }; - let boundary = self.boundary(element.start, gap)?; - let body = if punctuation { - boundary.append(self.tree(element, punctuation_context)?) - } else if position > 0 && items[position - 1].kind == PIPE { - // A row tail can start beside the opener or after a comment, - // so its continuation must follow the type's actual column. - boundary.append(self.tree(element, punctuation_context)?.align()).nest(2) - } else { - boundary.append(self.tree(element, item_context)?).nest(2) - }; - document = document.append(body); - } - let edge = if items.is_empty() { - Gap::Tight - } else if matches!(elements[opening].kind, LEFT_CURLY | LEFT_SQUARE) { + Gap::Space + } + } else if element.kind == COMMA { + Gap::SoftEmpty + } else if element.kind == PIPE { Gap::Soft } else { - Gap::SoftEmpty + self.fixed_gap(element.start) }; - document = document - .append(self.boundary(elements[closing].start, edge)?) - .append(self.tree(elements[closing], punctuation_context)?) - .group() - .align(); - return Ok(document); - } - let edge = if elements[opening].kind == LEFT_CURLY { Gap::Soft } else { Gap::SoftEmpty }; - let inner_context = - Context { margin: context.margin + self.config.indent_width, following: None }; - let body = - self.sequence(&elements[opening + 1..closing], inner_context, |_, _, current| { - if current.kind == COMMA { - Gap::SoftEmpty - } else if current.kind == TypeRowTail { - Gap::Soft - } else { - self.fixed_gap(current.start) - } - })?; - let opening_document = - self.fixed(&elements[..=opening], Context { following: None, ..context })?; - let boundary = self.boundary(elements[opening + 1].start, edge)?; - let boundary = if elements[opening + 1..closing].iter().any(|tree| tree.has_inline_do()) { - boundary.group() + let boundary = self.boundary(element.start, gap)?; + let body = if punctuation { + boundary.append(self.tree(element, punctuation_context)?) + } else if position > 0 && items[position - 1].kind == PIPE { + // A row tail can start beside the opener or after a comment, + // so its continuation must follow the type's actual column. + boundary.append(self.tree(element, punctuation_context)?.align()).nest(2) + } else { + boundary.append(self.tree(element, item_context)?).nest(2) + }; + document = document.append(body); + } + let edge = if items.is_empty() { + Gap::Tight + } else if matches!(elements[opening].kind, LEFT_CURLY | LEFT_SQUARE) { + Gap::Soft } else { - boundary + Gap::SoftEmpty }; - let document = opening_document - .append(boundary.append(body).nest(self.config.indent_width as isize)) + document = document .append(self.boundary(elements[closing].start, edge)?) - .append(self.tree(elements[closing], context)?) - .group(); + .append(self.tree(elements[closing], punctuation_context)?); + if prefix.is_none() && !matches!(tree.kind, ExportList | TypeItemsList) { + document = document.group(); + } + let mut document = document.align(); + if let Some((prefix, head)) = prefix { + let keyword = self.tree(prefix, context)?; + let broken = keyword.clone().append( + self.boundary(elements[opening].start, Gap::Hard(1))? + .append(document.clone()) + .nest(self.config.indent_width as isize), + ); + document = keyword.append( + self.boundary(elements[opening].start, Gap::Soft)? + .append(document) + .nest(self.config.indent_width as isize), + ); + // Fit the complete class head before allowing its parameters to wrap. + let probe = + self.arena.fail().flat_alt(self.fixed(&[prefix, tree, head], context)?).group(); + let width = self.config.line_width; + let inline = document.group(); + document = self.arena.column(move |column| { + let mut rendered = String::new(); + if probe.render_fmt(width.saturating_sub(column), &mut rendered).is_ok() { + inline.clone().into_doc() + } else { + broken.clone().into_doc() + } + }); + } + if opening > 0 { + document = self + .fixed(&elements[..opening], context)? + .append(self.boundary(elements[opening].start, Gap::Space)?) + .append(document); + } if closing + 1 < elements.len() { let margin = context.margin + self.config.indent_width; - Ok(document.append( + document = document.append( self.boundary( elements[closing + 1].start, self.fixed_gap(elements[closing + 1].start), )? .append(self.fixed(&elements[closing + 1..], Context { margin, ..context })?) .nest(self.config.indent_width as isize), - )) - } else { - Ok(document) + ); } + Ok(document) } fn arrows(&self, tree: &Tree, context: Context) -> Result, FormatError> { @@ -603,7 +664,7 @@ impl<'arena> Printer<'arena, '_> { .nest(self.config.indent_width as isize); let operand_document = |operand: &Tree, context: Context| { let document = self.tree(operand, context)?; - let document = if operand.has_inline_do() { document } else { document.align() }; + let document = if operand.has_inline_block() { document } else { document.align() }; Ok::<_, FormatError>(document) }; let pair = |operator: &Tree, operand: &Tree, context: Context| { @@ -616,27 +677,70 @@ impl<'arena> Printer<'arena, '_> { let gap = |position: usize| { if elements[position - 1].ends_offside() { Gap::Hard(1) } else { Gap::Soft } }; - if elements.iter().any(|tree| tree.has_inline_do()) { + if elements.iter().any(|tree| tree.has_inline_block()) { let mut inline_suffix = self.arena.nil(); let mut broken_suffix = self.arena.nil(); for position in (1..elements.len()).step_by(2).rev() { let operator = elements[position]; let operand = elements[position + 1]; + let operand_gap = + if operand.is_delimited_block() { Gap::Hard(1) } else { Gap::Space }; let following = if position + 2 < elements.len() { Some(margin) } else { context.following }; + let operand_header = self.case_header(operand, context)?; + let operator_header = if let Some(header) = &operand_header { + Some( + self.tree(operator, context)? + .append(self.boundary(operand.start, Gap::Space)?) + .append(header.clone()), + ) + } else { + None + }; let inline = operand_document(operand, Context { following, ..context })? .append(inline_suffix); + let moved = if operand.kind == ExpressionCaseOf { + Some( + operand_document( + operand, + Context { margin: margin + self.config.indent_width, following }, + )? + .append(broken_suffix.clone()), + ) + } else { + None + }; let broken = operand_document(operand, Context { margin, following })?.append(broken_suffix); let inline = self .tree(operator, Context { following: Some(margin), ..context })? - .append(self.attach(operand.start, Gap::Space, inline, broken.clone())?); + .append(self.attach( + operand.start, + operand_gap, + inline, + broken.clone(), + operand_header.clone(), + )?); + let broken = if let Some(moved) = moved { + self.attach(operand.start, operand_gap, broken, moved, operand_header)? + } else { + self.boundary(operand.start, operand_gap)?.group().append(broken) + }; + let broken = if operand.is_delimited_block() { + broken.nest(self.config.indent_width as isize) + } else { + broken + }; let broken = self .tree(operator, Context { margin, following: Some(margin) })? - .append(self.boundary(operand.start, Gap::Space)?.group()) .append(broken); - inline_suffix = - self.attach(operator.start, gap(position), inline, broken.clone())?; + inline_suffix = self.attach( + operator.start, + gap(position), + inline, + broken.clone(), + operator_header, + )?; broken_suffix = self.boundary(operator.start, gap(position))?.group().append(broken); } @@ -663,17 +767,115 @@ impl<'arena> Printer<'arena, '_> { context: Context, ) -> Result, FormatError> { let mut document = self.arena.nil(); - for clause in elements.chunks(2) { - if clause[0].kind != IF { - document = document.append(self.boundary(clause[0].start, Gap::Soft)?); + let mut clauses = elements.to_vec(); + let mut leading_else: Option<&Tree> = None; + loop { + let [if_keyword, condition, then_keyword, consequent, else_keyword, alternative] = + clauses[..] + else { + unreachable!("invariant violated: validated conditional has six elements"); + }; + let head = if let Some(keyword) = leading_else.take() { + document = document.append(self.boundary(keyword.start, Gap::Soft)?); + vec![keyword, if_keyword] + } else { + vec![if_keyword] + }; + document = document.append(self.continuation( + &head, + &[condition], + Context { following: None, ..context }, + )?); + document = document.append(self.boundary(then_keyword.start, Gap::Soft)?).append( + self.continuation( + &[then_keyword], + &[consequent], + Context { following: None, ..context }, + )?, + ); + // Only direct alternatives share a layout; parentheses and + // line-forcing comments retain their continuation boundary. + if let [nested] = alternative.elements().as_slice() + && nested.kind == ExpressionIfThenElse + { + let (comments, _) = + trivia(self.lexed.annotation(nested.start).unwrap_or_default())?; + if !comments.iter().any(|comment| comment.forces_line()) { + leading_else = Some(else_keyword); + clauses = nested.elements(); + continue; + } } + document = document.append(self.boundary(else_keyword.start, Gap::Soft)?); document = document.append(self.continuation( - &clause[..1], - &clause[1..], + &[else_keyword], + &[alternative], Context { following: None, ..context }, )?); + return Ok(document.group()); + } + } + + fn case_header( + &self, + tree: &Tree, + context: Context, + ) -> Result>, FormatError> { + let expression = tree.expression().unwrap_or(tree); + if expression.kind == ExpressionCaseOf { + let [case_keyword, trunk, of_keyword, _] = expression.elements()[..] else { + unreachable!("invariant violated: validated case expression has four elements"); + }; + self.fixed(&[case_keyword, trunk, of_keyword], context).map(Some) + } else { + Ok(None) } - Ok(document.group()) + } + + fn case_expression( + &self, + elements: &[&Tree], + context: Context, + ) -> Result, FormatError> { + let [case_keyword, scrutinees, of_keyword, branches] = elements[..] else { + unreachable!("invariant violated: validated case expression has four elements"); + }; + let header = self.fixed(&[case_keyword, scrutinees, of_keyword], context)?; + let margin = context.margin + self.config.indent_width; + let items = scrutinees.elements(); + let mut trunk = self.arena.nil(); + for (position, item) in items.iter().enumerate() { + let gap = if item.kind == COMMA { Gap::SoftEmpty } else { self.fixed_gap(item.start) }; + let boundary = + if position == 0 { self.arena.nil() } else { self.boundary(item.start, gap)? }; + let indentation = if position > 0 && items[position - 1].kind == COMMA { 2 } else { 0 }; + let item_context = Context { margin: margin + indentation, following: None }; + trunk = trunk + .append(boundary.append(self.tree(item, item_context)?).nest(indentation as isize)); + } + let broken = self + .tree(case_keyword, context)? + .append( + self.boundary(scrutinees.start, Gap::Hard(1))? + .append(trunk.group()) + .nest(self.config.indent_width as isize), + ) + .append(self.boundary(of_keyword.start, Gap::Hard(1))?) + .append(self.tree(of_keyword, context)?); + let arena = self.arena; + let probe = arena.fail().flat_alt(header).group(); + let width = self.config.line_width; + // A comment after `of` belongs to the branch boundary, so it must not + // participate in the decision to split the header. + let header = arena.column(move |column| { + let mut rendered = String::new(); + if probe.render_fmt(width.saturating_sub(column), &mut rendered).is_ok() { + arena.text(rendered).into_doc() + } else { + broken.clone().into_doc() + } + }); + Ok(header.append(self.tree(branches, context)?)) } fn where_expression( @@ -682,26 +884,32 @@ impl<'arena> Printer<'arena, '_> { elements: &[&Tree], context: Context, ) -> Result, FormatError> { - let Some(position) = elements.iter().position(|tree| tree.kind == WHERE) else { - return if head.is_empty() { - self.fixed(elements, context) - } else { - self.continuation(head, elements, context) - }; + let (expression, where_keyword, bindings) = match elements { + [expression] => { + return if head.is_empty() { + self.fixed(&[*expression], context) + } else { + self.continuation(head, &[*expression], context) + }; + } + [expression, where_keyword, bindings] => (*expression, *where_keyword, *bindings), + _ => unreachable!( + "invariant violated: validated where expression has one or three elements" + ), }; let margin = context.margin.max(context.following.unwrap_or(0) + self.config.indent_width); let expression_context = Context { following: Some(margin - self.config.indent_width), ..context }; let expression = if head.is_empty() { - self.fixed(&elements[..position], expression_context)? + self.fixed(&[expression], expression_context)? } else { - self.continuation(head, &elements[..position], expression_context)? + self.continuation(head, &[expression], expression_context)? }; let binding_context = Context { margin, following: Some(margin) }; - let bindings = self.block(&elements[position + 1].elements(), binding_context, margin)?; + let bindings = self.block(&bindings.elements(), binding_context, margin)?; Ok(expression.append( - self.boundary(elements[position].start, Gap::Hard(1))? - .append(self.tree(elements[position], binding_context)?) + self.boundary(where_keyword.start, Gap::Hard(1))? + .append(self.tree(where_keyword, binding_context)?) .append(bindings) .nest((margin - context.margin) as isize), )) @@ -733,6 +941,16 @@ impl<'arena> Printer<'arena, '_> { match tree.kind { Module => self.sequence(&elements, context, |_, _, _| Gap::Hard(2)), ModuleImports | ModuleStatements => self.statements(&elements, context), + ImportType | ExportType => { + let document = self.sequence(&elements, context, |_, _, current| { + if current.kind == TypeItemsList { + Gap::BrokenSpace + } else { + self.fixed_gap(current.start) + } + })?; + Ok(document.group()) + } DoStatements | CaseBranches | LetBindingStatements | ClassStatements | InstanceStatements | Conditionals => { // A layout body must exceed the planned following continuation, @@ -746,10 +964,10 @@ impl<'arena> Printer<'arena, '_> { | ExpressionRecordUpdate | RecordUpdateBranch | BinderConstructor - | DataConstructor | InstanceHead | ClassHead | FunctionBinders => self.application(&elements, 1, context), + DataConstructor => Ok(self.application(&elements, 1, context)?.align()), ExpressionOperatorChain | ExpressionInfixChain | TypeOperatorChain @@ -784,11 +1002,11 @@ impl<'arena> Printer<'arena, '_> { if let Some(separator) = elements.iter().position(|tree| tree.kind == EQUAL) { self.chain( &elements, - separator + 1, + separator, context, context.margin + self.config.indent_width, - |_, previous, current| { - if current.kind == PIPE || previous.kind == EQUAL { + |_, _, current| { + if matches!(current.kind, EQUAL | PIPE) { Gap::Soft } else { self.fixed_gap(current.start) @@ -800,24 +1018,32 @@ impl<'arena> Printer<'arena, '_> { } } TypeForall => { - let position = elements.iter().position(|tree| tree.kind == PERIOD).unwrap(); - let head = self.fixed(&elements[..=position], context)?.group(); + let [head @ .., body] = elements.as_slice() else { + unreachable!("invariant violated: validated forall has a body"); + }; + let head = self.fixed(head, context)?.group(); Ok(head - .append(self.boundary(elements[position + 1].start, Gap::Soft)?) - .append(self.fixed(&elements[position + 1..], context)?) + .append(self.boundary(body.start, Gap::Soft)?) + .append(self.fixed(&[*body], context)?) .group()) } - ExpressionArray | ExpressionRecord | BinderArray | BinderRecord | TypeRecord - | TypeRow | RecordUpdates => self.delimited(&elements, context, true), - ExpressionParenthesized + ExpressionArray + | ExpressionRecord + | BinderArray + | BinderRecord + | TypeRecord + | TypeRow + | RecordUpdates + | ExpressionParenthesized | BinderParenthesized | TypeParenthesized | ExportList | ImportList | TypeItemsList | InstanceConstraints - | ClassConstraints => self.delimited(&elements, context, false), + | ClassConstraints => self.delimited(tree, context, None), ExpressionIfThenElse => self.conditional(&elements, context), + ExpressionCaseOf => self.case_expression(&elements, context), ValueEquation | LetBindingEquation | LetBindingPattern @@ -857,7 +1083,7 @@ impl<'arena> Printer<'arena, '_> { | TypeSynonymEquation | NewtypeEquation => { if matches!(elements.first().map(|tree| tree.kind), Some(LEFT_PARENTHESIS)) { - return self.delimited(&elements, context, false); + return self.delimited(tree, context, None); } let separator = elements.iter().position(|tree| { matches!( @@ -873,10 +1099,12 @@ impl<'arena> Printer<'arena, '_> { } WhereExpression => self.where_expression(&[], &elements, context), ExpressionLetIn | ExpressionAdo => { - let position = elements.iter().position(|tree| tree.kind == IN).unwrap(); - let has_body = elements[1..position].iter().any(|tree| tree.start < tree.end); + let [keyword, statements, in_keyword, result] = elements[..] else { + unreachable!("invariant violated: validated let/ado has four elements"); + }; + let has_body = statements.start < statements.end; let head = - self.fixed(&elements[..position], Context { following: None, ..context })?; + self.fixed(&[keyword, statements], Context { following: None, ..context })?; let result_margin = if tree.kind == ExpressionAdo { context.margin + self.config.indent_width } else { @@ -886,22 +1114,18 @@ impl<'arena> Printer<'arena, '_> { let tail = if tree.kind == ExpressionLetIn { let body_context = Context { margin: context.margin + self.config.indent_width, ..context }; - self.tree(elements[position], context)?.append( - self.boundary(elements[position + 1].start, Gap::Hard(1))? - .append(self.fixed(&elements[position + 1..], body_context)?) + self.tree(in_keyword, context)?.append( + self.boundary(result.start, Gap::Hard(1))? + .append(self.fixed(&[result], body_context)?) .nest(self.config.indent_width as isize), ) } else { - self.continuation( - &elements[position..=position], - &elements[position + 1..], - result_context, - )? + self.continuation(&[in_keyword], &[result], result_context)? }; Ok(head .append( self.boundary( - elements[position].start, + in_keyword.start, if has_body { Gap::Hard(1) } else { Gap::Space }, )? .append(tail) @@ -911,13 +1135,15 @@ impl<'arena> Printer<'arena, '_> { } InstanceChain => self.sequence(&elements, context, |_, _, _| Gap::Hard(1)), InstanceDeclaration | ClassDeclaration | DeriveDeclaration => { - if elements.first().is_some_and(|tree| tree.kind == ELSE) + if let [else_keyword, rest @ ..] = elements.as_slice() + && let [first, ..] = rest + && else_keyword.kind == ELSE && tree.children.iter().any(|tree| tree.kind == LAYOUT_SEPARATOR) { let tail = self - .boundary(elements[1].start, Gap::Hard(1))? - .append(self.fixed(&elements[1..], context)?); - Ok(self.tree(elements[0], context)?.append(tail)) + .boundary(first.start, Gap::Hard(1))? + .append(self.fixed(rest, context)?); + Ok(self.tree(else_keyword, context)?.append(tail)) } else if let Some(head) = elements.iter().position(|tree| matches!(tree.kind, InstanceHead | ClassHead)) && elements[..head] diff --git a/tests-integration/fixtures/formatting/1791030060_layout_and_punctuation/Main.snap b/tests-integration/fixtures/formatting/1791030060_layout_and_punctuation/Main.snap index 906e1feb2..034e02d9e 100644 --- a/tests-integration/fixtures/formatting/1791030060_layout_and_punctuation/Main.snap +++ b/tests-integration/fixtures/formatting/1791030060_layout_and_punctuation/Main.snap @@ -1,6 +1,6 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 124 +assertion_line: 127 --- module Main (test, Box(..)) where @@ -37,9 +37,8 @@ local x = in y -branch x = - case x of - Box y -> if true then y else 0 +branch x = case x of + Box y -> if true then y else 0 record = { outer: { left: 1, right: 2 }, tail: 3 } diff --git a/tests-integration/fixtures/formatting/1791034641_width_aware/Main.snap b/tests-integration/fixtures/formatting/1791034641_width_aware/Main.snap index 91a92f3e5..5e1067108 100644 --- a/tests-integration/fixtures/formatting/1791034641_width_aware/Main.snap +++ b/tests-integration/fixtures/formatting/1791034641_width_aware/Main.snap @@ -1,27 +1,25 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 118 +assertion_line: 127 --- === width 40, indent 2 === module Main - ( - longApplication - , longRecord - , branches - , Box(..) + ( longApplication + , longRecord + , branches + , Box(..) ) where import Data.Collections - ( - Collection - , mapCollection - , filterCollection + ( Collection + , mapCollection + , filterCollection ) as Collections -data Box value = - Box value +data Box value + = Box value | Many value value value longApplication :: @@ -126,12 +124,11 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement - : secondElement - : remainingElements -> - transform remainingElements +operatorPattern value = case value of + firstElement + : secondElement + : remainingElements -> + transform remainingElements class Base value <= Derived value builtinBind = do @@ -266,10 +263,9 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement : secondElement : remainingElements -> - transform remainingElements +operatorPattern value = case value of + firstElement : secondElement : remainingElements -> + transform remainingElements class Base value <= Derived value builtinBind = do @@ -375,9 +371,8 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement : secondElement : remainingElements -> transform remainingElements +operatorPattern value = case value of + firstElement : secondElement : remainingElements -> transform remainingElements class Base value <= Derived value builtinBind = do @@ -425,24 +420,22 @@ last === width 40, indent 1 === module Main - ( - longApplication - , longRecord - , branches - , Box(..) + ( longApplication + , longRecord + , branches + , Box(..) ) where import Data.Collections - ( - Collection - , mapCollection - , filterCollection + ( Collection + , mapCollection + , filterCollection ) as Collections -data Box value = - Box value +data Box value + = Box value | Many value value value longApplication :: @@ -546,12 +539,11 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement - : secondElement - : remainingElements -> - transform remainingElements +operatorPattern value = case value of + firstElement + : secondElement + : remainingElements -> + transform remainingElements class Base value <= Derived value builtinBind = do @@ -604,24 +596,22 @@ last === width 40, indent 4 === module Main - ( - longApplication - , longRecord - , branches - , Box(..) + ( longApplication + , longRecord + , branches + , Box(..) ) where import Data.Collections - ( - Collection - , mapCollection - , filterCollection + ( Collection + , mapCollection + , filterCollection ) as Collections -data Box value = - Box value +data Box value + = Box value | Many value value value longApplication :: @@ -729,12 +719,11 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement - : secondElement - : remainingElements -> - transform remainingElements +operatorPattern value = case value of + firstElement + : secondElement + : remainingElements -> + transform remainingElements class Base value <= Derived value builtinBind = do @@ -789,24 +778,22 @@ last === width 40, indent 2, unicode === module Main - ( - longApplication - , longRecord - , branches - , Box(..) + ( longApplication + , longRecord + , branches + , Box(..) ) where import Data.Collections - ( - Collection - , mapCollection - , filterCollection + ( Collection + , mapCollection + , filterCollection ) as Collections -data Box value = - Box value +data Box value + = Box value | Many value value value longApplication ∷ @@ -911,12 +898,11 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement - : secondElement - : remainingElements → - transform remainingElements +operatorPattern value = case value of + firstElement + : secondElement + : remainingElements → + transform remainingElements class Base value ⇐ Derived value builtinBind = do @@ -1049,10 +1035,8 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement : secondElement : remainingElements → - transform remainingElements +operatorPattern value = case value of + firstElement : secondElement : remainingElements → transform remainingElements class Base value ⇐ Derived value builtinBind = do diff --git a/tests-integration/fixtures/formatting/1791042120_alternate_source_wrapping/Main.snap b/tests-integration/fixtures/formatting/1791042120_alternate_source_wrapping/Main.snap index 7ddebde15..434d74983 100644 --- a/tests-integration/fixtures/formatting/1791042120_alternate_source_wrapping/Main.snap +++ b/tests-integration/fixtures/formatting/1791042120_alternate_source_wrapping/Main.snap @@ -1,27 +1,25 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 118 +assertion_line: 127 --- === width 40, indent 2 === module Main - ( - longApplication - , longRecord - , branches - , Box(..) + ( longApplication + , longRecord + , branches + , Box(..) ) where import Data.Collections - ( - Collection - , mapCollection - , filterCollection + ( Collection + , mapCollection + , filterCollection ) as Collections -data Box value = - Box value +data Box value + = Box value | Many value value value longApplication :: @@ -122,12 +120,11 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement - : secondElement - : remainingElements -> - transform remainingElements +operatorPattern value = case value of + firstElement + : secondElement + : remainingElements -> + transform remainingElements class Base value <= Derived value builtinBind = do @@ -259,10 +256,9 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement : secondElement : remainingElements -> - transform remainingElements +operatorPattern value = case value of + firstElement : secondElement : remainingElements -> + transform remainingElements class Base value <= Derived value builtinBind = do @@ -365,9 +361,8 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement : secondElement : remainingElements -> transform remainingElements +operatorPattern value = case value of + firstElement : secondElement : remainingElements -> transform remainingElements class Base value <= Derived value builtinBind = do @@ -415,24 +410,22 @@ last === width 40, indent 1 === module Main - ( - longApplication - , longRecord - , branches - , Box(..) + ( longApplication + , longRecord + , branches + , Box(..) ) where import Data.Collections - ( - Collection - , mapCollection - , filterCollection + ( Collection + , mapCollection + , filterCollection ) as Collections -data Box value = - Box value +data Box value + = Box value | Many value value value longApplication :: @@ -532,12 +525,11 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement - : secondElement - : remainingElements -> - transform remainingElements +operatorPattern value = case value of + firstElement + : secondElement + : remainingElements -> + transform remainingElements class Base value <= Derived value builtinBind = do @@ -590,24 +582,22 @@ last === width 40, indent 4 === module Main - ( - longApplication - , longRecord - , branches - , Box(..) + ( longApplication + , longRecord + , branches + , Box(..) ) where import Data.Collections - ( - Collection - , mapCollection - , filterCollection + ( Collection + , mapCollection + , filterCollection ) as Collections -data Box value = - Box value +data Box value + = Box value | Many value value value longApplication :: @@ -711,12 +701,11 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement - : secondElement - : remainingElements -> - transform remainingElements +operatorPattern value = case value of + firstElement + : secondElement + : remainingElements -> + transform remainingElements class Base value <= Derived value builtinBind = do @@ -771,24 +760,22 @@ last === width 40, indent 2, unicode === module Main - ( - longApplication - , longRecord - , branches - , Box(..) + ( longApplication + , longRecord + , branches + , Box(..) ) where import Data.Collections - ( - Collection - , mapCollection - , filterCollection + ( Collection + , mapCollection + , filterCollection ) as Collections -data Box value = - Box value +data Box value + = Box value | Many value value value longApplication ∷ @@ -889,12 +876,11 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement - : secondElement - : remainingElements → - transform remainingElements +operatorPattern value = case value of + firstElement + : secondElement + : remainingElements → + transform remainingElements class Base value ⇐ Derived value builtinBind = do @@ -1024,10 +1010,8 @@ commentedOperatorDo = do -- before an inline block + do action -operatorPattern value = - case value of - firstElement : secondElement : remainingElements → - transform remainingElements +operatorPattern value = case value of + firstElement : secondElement : remainingElements → transform remainingElements class Base value ⇐ Derived value builtinBind = do diff --git a/tests-integration/fixtures/formatting/1791044520_delimited_do_headers/Main.purs b/tests-integration/fixtures/formatting/1791044520_delimited_do_headers/Main.purs index 66f764179..0568b3cd5 100644 --- a/tests-integration/fixtures/formatting/1791044520_delimited_do_headers/Main.purs +++ b/tests-integration/fixtures/formatting/1791044520_delimited_do_headers/Main.purs @@ -20,3 +20,9 @@ applicative = use (ado first <- firstAction second <- secondAction in combine first second) + +wrapped = (combine firstArgument secondArgument) +nested = ((combine firstArgument secondArgument)) + +operator = identity $ { onClick: do + action, other: 1 } diff --git a/tests-integration/fixtures/formatting/1791044520_delimited_do_headers/Main.snap b/tests-integration/fixtures/formatting/1791044520_delimited_do_headers/Main.snap index b805991d3..4749b9200 100644 --- a/tests-integration/fixtures/formatting/1791044520_delimited_do_headers/Main.snap +++ b/tests-integration/fixtures/formatting/1791044520_delimited_do_headers/Main.snap @@ -1,88 +1,147 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 114 +assertion_line: 127 --- === width 30, indent 2 === module Main where -lambda = for_ values ( - \value -> do - log value -) - -parenthesized = use (do - action -) next - -record = { onClick: do - action - , other: 1 - } - -array = [ do - firstAction - , do - secondAction - ] - -applicative = use (ado - first <- firstAction - second <- secondAction - in combine first second -) +lambda = for_ values + ( \value -> do + log value + ) + +parenthesized = use + ( do + action + ) next + +record = + { onClick: do + action + , other: 1 + } + +array = + [ do + firstAction + , do + secondAction + ] + +applicative = use + ( ado + first <- firstAction + second <- secondAction + in combine first second + ) + +wrapped = + ( combine + firstArgument + secondArgument + ) +nested = + ( ( combine + firstArgument + secondArgument + ) + ) + +operator = identity $ + { onClick: do + action + , other: 1 + } === width 80, indent 2 === module Main where -lambda = for_ values (\value -> do - log value -) - -parenthesized = use (do - action -) next - -record = { onClick: do - action - , other: 1 - } - -array = [ do - firstAction - , do - secondAction - ] - -applicative = use (ado - first <- firstAction - second <- secondAction - in combine first second -) +lambda = for_ values + ( \value -> do + log value + ) + +parenthesized = use + ( do + action + ) next + +record = + { onClick: do + action + , other: 1 + } + +array = + [ do + firstAction + , do + secondAction + ] + +applicative = use + ( ado + first <- firstAction + second <- secondAction + in combine first second + ) + +wrapped = (combine firstArgument secondArgument) +nested = ((combine firstArgument secondArgument)) + +operator = identity $ + { onClick: do + action + , other: 1 + } === width 40, indent 4, unicode === module Main where -lambda = for_ values (\value → do - log value -) - -parenthesized = use (do - action -) next - -record = { onClick: do - action - , other: 1 - } - -array = [ do - firstAction - , do - secondAction - ] - -applicative = use (ado - first ← firstAction - second ← secondAction - in combine first second -) +lambda = for_ values + ( \value → do + log value + ) + +parenthesized = use + ( do + action + ) next + +record = + { onClick: do + action + , other: 1 + } + +array = + [ do + firstAction + , do + secondAction + ] + +applicative = use + ( ado + first ← firstAction + second ← secondAction + in combine first second + ) + +wrapped = + ( combine + firstArgument + secondArgument + ) +nested = + ( ( combine + firstArgument + secondArgument + ) + ) + +operator = identity $ + { onClick: do + action + , other: 1 + } diff --git a/tests-integration/fixtures/formatting/1791044520_nested_do_continuations/Main.purs b/tests-integration/fixtures/formatting/1791044520_nested_do_continuations/Main.purs index 59b830dd9..194f176bf 100644 --- a/tests-integration/fixtures/formatting/1791044520_nested_do_continuations/Main.purs +++ b/tests-integration/fixtures/formatting/1791044520_nested_do_continuations/Main.purs @@ -33,3 +33,20 @@ empty = ado emptyLine = ado -- before the result in 2 + +letCase = let y = 1 in case y of + value -> value + +conditionalCase = + if condition + then case value of + A -> first + else do + fallback + +lambdaCase = \value -> case value of + A -> do + firstAction + B -> ado + result <- secondAction + in result diff --git a/tests-integration/fixtures/formatting/1791044520_nested_do_continuations/Main.snap b/tests-integration/fixtures/formatting/1791044520_nested_do_continuations/Main.snap index dda2f0afd..b57773fab 100644 --- a/tests-integration/fixtures/formatting/1791044520_nested_do_continuations/Main.snap +++ b/tests-integration/fixtures/formatting/1791044520_nested_do_continuations/Main.snap @@ -1,6 +1,6 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 92 +assertion_line: 127 --- === width 30, indent 2 === module Main where @@ -21,20 +21,19 @@ statement = do action y pure x -branches value = - case value of - A -> - let - y = 1 - in - do - action y - B -> - if condition - then do - firstAction - secondAction - else fallback +branches value = case value of + A -> + let + y = 1 + in + do + action y + B -> + if condition + then do + firstAction + secondAction + else fallback conditional = if condition @@ -57,6 +56,28 @@ emptyLine = ado -- before the result in 2 +letCase = + let + y = 1 + in + case y of + value -> value + +conditionalCase = + if condition + then case value of + A -> first + else do + fallback + +lambdaCase = \value -> + case value of + A -> do + firstAction + B -> ado + result <- secondAction + in result + === width 80, indent 2 === module Main where @@ -76,20 +97,19 @@ statement = do action y pure x -branches value = - case value of - A -> - let - y = 1 - in - do - action y - B -> - if condition - then do - firstAction - secondAction - else fallback +branches value = case value of + A -> + let + y = 1 + in + do + action y + B -> + if condition + then do + firstAction + secondAction + else fallback conditional = if condition @@ -112,6 +132,27 @@ emptyLine = ado -- before the result in 2 +letCase = + let + y = 1 + in + case y of + value -> value + +conditionalCase = + if condition + then case value of + A -> first + else do + fallback + +lambdaCase = \value -> case value of + A -> do + firstAction + B -> ado + result <- secondAction + in result + === width 40, indent 4, unicode === module Main where @@ -131,20 +172,19 @@ statement = do action y pure x -branches value = - case value of - A → - let - y = 1 - in - do - action y - B → - if condition - then do - firstAction - secondAction - else fallback +branches value = case value of + A → + let + y = 1 + in + do + action y + B → + if condition + then do + firstAction + secondAction + else fallback conditional = if condition @@ -166,3 +206,24 @@ empty = ado emptyLine = ado -- before the result in 2 + +letCase = + let + y = 1 + in + case y of + value → value + +conditionalCase = + if condition + then case value of + A → first + else do + fallback + +lambdaCase = \value → case value of + A → do + firstAction + B → ado + result ← secondAction + in result diff --git a/tests-integration/fixtures/formatting/1791046620_constraint_continuation_comments/Main.purs b/tests-integration/fixtures/formatting/1791046620_constraint_continuation_comments/Main.purs index e868dc57c..812aa91e6 100644 --- a/tests-integration/fixtures/formatting/1791046620_constraint_continuation_comments/Main.purs +++ b/tests-integration/fixtures/formatting/1791046620_constraint_continuation_comments/Main.purs @@ -1,6 +1,9 @@ -- @format width=20 indent=1 +-- @format width=30 -- @format width=80 -- @format width=40 indent=4 unicode=true +-- @format width=41 indent=4 unicode=true +-- @format width=42 indent=4 unicode=true module Main where instance (Show value) -- instance explanation @@ -10,6 +13,8 @@ derive instance (Eq value) -- derive explanation => Eq (Box value) -class (Eq value) +class (Eq value, Show value) -- superclass explanation <= Ord value + +class (Eq value, Show value) <= Thing value diff --git a/tests-integration/fixtures/formatting/1791046620_constraint_continuation_comments/Main.snap b/tests-integration/fixtures/formatting/1791046620_constraint_continuation_comments/Main.snap index 2c824ff8f..4571f4e78 100644 --- a/tests-integration/fixtures/formatting/1791046620_constraint_continuation_comments/Main.snap +++ b/tests-integration/fixtures/formatting/1791046620_constraint_continuation_comments/Main.snap @@ -1,25 +1,56 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 106 +assertion_line: 127 --- === width 20, indent 1 === module Main where -instance ( - Show value -) -- instance explanation +instance ( Show + value + ) -- instance explanation => Show (Box value) -derive instance ( - Eq value -) +derive instance ( Eq + value + ) -- derive explanation => Eq (Box value) -class (Eq value) +class + ( Eq value + , Show value + ) -- superclass explanation <= Ord value +class + ( Eq value + , Show value + ) <= Thing value + +=== width 30, indent 2 === +module Main where + +instance ( Show value + ) -- instance explanation + => Show (Box value) + +derive instance (Eq value) + -- derive explanation + => Eq (Box value) + +class + ( Eq value + , Show value + ) + -- superclass explanation + <= Ord value + +class + ( Eq value + , Show value + ) <= Thing value + === width 80, indent 2 === module Main where @@ -30,22 +61,77 @@ derive instance (Eq value) -- derive explanation => Eq (Box value) -class (Eq value) +class + ( Eq value + , Show value + ) -- superclass explanation <= Ord value +class (Eq value, Show value) <= Thing value + === width 40, indent 4, unicode === module Main where -instance ( - Show value -) -- instance explanation +instance ( Show value + ) -- instance explanation ⇒ Show (Box value) derive instance (Eq value) -- derive explanation ⇒ Eq (Box value) -class (Eq value) +class + ( Eq value + , Show value + ) -- superclass explanation ⇐ Ord value + +class + ( Eq value + , Show value + ) ⇐ Thing value + +=== width 41, indent 4, unicode === +module Main where + +instance ( Show value + ) -- instance explanation + ⇒ Show (Box value) + +derive instance (Eq value) + -- derive explanation + ⇒ Eq (Box value) + +class + ( Eq value + , Show value + ) + -- superclass explanation + ⇐ Ord value + +class + ( Eq value + , Show value + ) ⇐ Thing value + +=== width 42, indent 4, unicode === +module Main where + +instance ( Show value + ) -- instance explanation + ⇒ Show (Box value) + +derive instance (Eq value) + -- derive explanation + ⇒ Eq (Box value) + +class + ( Eq value + , Show value + ) + -- superclass explanation + ⇐ Ord value + +class (Eq value, Show value) ⇐ Thing value diff --git a/tests-integration/fixtures/formatting/1791046620_declaration_separators/Main.snap b/tests-integration/fixtures/formatting/1791046620_declaration_separators/Main.snap index 4762b1375..07c3bb5d2 100644 --- a/tests-integration/fixtures/formatting/1791046620_declaration_separators/Main.snap +++ b/tests-integration/fixtures/formatting/1791046620_declaration_separators/Main.snap @@ -1,12 +1,12 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 118 +assertion_line: 127 --- === width 40, indent 2 === module Main where -data Command = - Initialize Configuration +data Command + = Initialize Configuration | Execute Request Environment | Shutdown Reason | Restart @@ -36,21 +36,19 @@ class Multiple | firstLongVariable secondLongVariable -> thirdLongVariable fourthLongVariable -instance command :: ( - Show configuration - , Eq environment -) => +instance command :: ( Show configuration + , Eq environment + ) => Show - ( - Command configuration environment + ( Command configuration environment ) where show value = "command" === width 80, indent 2 === module Main where -data Command = - Initialize Configuration +data Command + = Initialize Configuration | Execute Request Environment | Shutdown Reason | Restart @@ -80,8 +78,8 @@ instance command :: (Show configuration, Eq environment) => === width 40, indent 4, unicode === module Main where -data Command = - Initialize Configuration +data Command + = Initialize Configuration | Execute Request Environment | Shutdown Reason | Restart @@ -112,14 +110,12 @@ class Multiple | firstLongVariable secondLongVariable → thirdLongVariable fourthLongVariable -instance command ∷ ( - Show configuration - , Eq environment -) ⇒ +instance command ∷ ( Show configuration + , Eq environment + ) ⇒ Show - ( - Command - configuration - environment + ( Command + configuration + environment ) where show value = "command" diff --git a/tests-integration/fixtures/formatting/1791046620_tight_syntax_at_line_starts/Main.snap b/tests-integration/fixtures/formatting/1791046620_tight_syntax_at_line_starts/Main.snap index d51679b9c..b8f603cc7 100644 --- a/tests-integration/fixtures/formatting/1791046620_tight_syntax_at_line_starts/Main.snap +++ b/tests-integration/fixtures/formatting/1791046620_tight_syntax_at_line_starts/Main.snap @@ -1,25 +1,22 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 118 +assertion_line: 127 --- === width 30, indent 4 === module Main where main = traverse_ - ( - \item -> - log - ( - "processing " - <> show - item - ) + ( \item -> + log + ( "processing " + <> show + item + ) ) - ( - Array.range - 1 - someUpperBoundValue + ( Array.range + 1 + someUpperBoundValue ) terms = do @@ -28,15 +25,14 @@ terms = do (-1) (h @b) ({ a: 1 }) - ((a do - b - ) + ( ( a do + b + ) ) -patterns value = - case value of - [ -1 ] -> 0 - _ -> 1 +patterns value = case value of + [ -1 ] -> 0 + _ -> 1 rows :: forall r. @@ -58,15 +54,14 @@ terms = do (-1) (h @b) ({ a: 1 }) - ((a do + ( ( a do b ) ) -patterns value = - case value of - [ -1 ] -> 0 - _ -> 1 +patterns value = case value of + [ -1 ] -> 0 + _ -> 1 rows :: forall r. { | r } -> { | r } -> Int quantified :: (forall b. b -> b) -> Int @@ -76,19 +71,16 @@ module Main where main = traverse_ - ( - \item → - log - ( - "processing " - <> show - item - ) + ( \item → + log + ( "processing " + <> show + item + ) ) - ( - Array.range - 1 - someUpperBoundValue + ( Array.range + 1 + someUpperBoundValue ) terms = do @@ -97,15 +89,14 @@ terms = do (-1) (h @b) ({ a: 1 }) - ((a do - b - ) + ( ( a do + b + ) ) -patterns value = - case value of - [ -1 ] → 0 - _ → 1 +patterns value = case value of + [ -1 ] → 0 + _ → 1 rows ∷ ∀ r. diff --git a/tests-integration/fixtures/formatting/1791047940_syntax_boundaries/Main.purs b/tests-integration/fixtures/formatting/1791047940_syntax_boundaries/Main.purs index 47d57f9e5..686e0d153 100644 --- a/tests-integration/fixtures/formatting/1791047940_syntax_boundaries/Main.purs +++ b/tests-integration/fixtures/formatting/1791047940_syntax_boundaries/Main.purs @@ -67,7 +67,7 @@ emptyAdo = ado in 1 hole = ?hole binders {} [] named@value (item :: Int) { field: field } = value -binderOperator (head : tail) = head +binderOperator (firstElement : remainingElements) = firstElement whereValue = result where result = 1 diff --git a/tests-integration/fixtures/formatting/1791047940_syntax_boundaries/Main.snap b/tests-integration/fixtures/formatting/1791047940_syntax_boundaries/Main.snap index b8d827b77..e3f061020 100644 --- a/tests-integration/fixtures/formatting/1791047940_syntax_boundaries/Main.snap +++ b/tests-integration/fixtures/formatting/1791047940_syntax_boundaries/Main.snap @@ -1,6 +1,6 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 118 +assertion_line: 127 --- === width 40, indent 2 === module Main where @@ -60,12 +60,10 @@ guarded value | Just result <- lookup value, result > 0 = result conditional = if condition then 1 else 2 -caseResult = - case value of - Just result -> result -multipleCase = - case first, second of - Just result, Nothing -> result +caseResult = case value of + Just result -> result +multipleCase = case first, second of + Just result, Nothing -> result lambda = \value -> value parenthesized = (1) negative = -1 @@ -89,7 +87,9 @@ binders {} named@value (item :: Int) { field: field } = value -binderOperator (head : tail) = head +binderOperator ( firstElement + : remainingElements + ) = firstElement whereValue = result where result = 1 @@ -158,12 +158,10 @@ patternLocal = guarded value | Just result <- lookup value, result > 0 = result conditional = if condition then 1 else 2 -caseResult = - case value of - Just result -> result -multipleCase = - case first, second of - Just result, Nothing -> result +caseResult = case value of + Just result -> result +multipleCase = case first, second of + Just result, Nothing -> result lambda = \value -> value parenthesized = (1) negative = -1 @@ -182,7 +180,7 @@ emptyAdo = ado in 1 hole = ?hole binders {} [] named@value (item :: Int) { field: field } = value -binderOperator (head : tail) = head +binderOperator (firstElement : remainingElements) = firstElement whereValue = result where result = 1 @@ -254,12 +252,10 @@ guarded value | Just result ← lookup value, result > 0 = result conditional = if condition then 1 else 2 -caseResult = - case value of - Just result → result -multipleCase = - case first, second of - Just result, Nothing → result +caseResult = case value of + Just result → result +multipleCase = case first, second of + Just result, Nothing → result lambda = \value → value parenthesized = (1) negative = -1 @@ -284,7 +280,9 @@ binders {} named@value (item ∷ Int) { field: field } = value -binderOperator (head : tail) = head +binderOperator ( firstElement + : remainingElements + ) = firstElement whereValue = result where result = 1 diff --git a/tests-integration/fixtures/formatting/1791048960_commented_separators/Main.snap b/tests-integration/fixtures/formatting/1791048960_commented_separators/Main.snap index 4c2d09f24..02b2ebd35 100644 --- a/tests-integration/fixtures/formatting/1791048960_commented_separators/Main.snap +++ b/tests-integration/fixtures/formatting/1791048960_commented_separators/Main.snap @@ -1,12 +1,12 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 118 +assertion_line: 127 --- === width 80, indent 2 === module Main where -data Choice = - First -- first constructor +data Choice + = First -- first constructor | Second data Another @@ -31,8 +31,8 @@ arrow :: === width 20, indent 2 === module Main where -data Choice = - First -- first constructor +data Choice + = First -- first constructor | Second data Another @@ -61,8 +61,8 @@ arrow :: === width 40, indent 4, unicode === module Main where -data Choice = - First -- first constructor +data Choice + = First -- first constructor | Second data Another diff --git a/tests-integration/fixtures/formatting/1791048960_nested_blank_lines/Main.snap b/tests-integration/fixtures/formatting/1791048960_nested_blank_lines/Main.snap index 8f95b8d1d..2f534b2c0 100644 --- a/tests-integration/fixtures/formatting/1791048960_nested_blank_lines/Main.snap +++ b/tests-integration/fixtures/formatting/1791048960_nested_blank_lines/Main.snap @@ -1,6 +1,6 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 114 +assertion_line: 127 --- === width 40, indent 2 === module Main where @@ -10,11 +10,10 @@ doValue = do second -caseValue = - case value of - First -> 1 +caseValue = case value of + First -> 1 - Second -> 2 + Second -> 2 whereValue = first where @@ -58,11 +57,10 @@ doValue = do second -caseValue = - case value of - First -> 1 +caseValue = case value of + First -> 1 - Second -> 2 + Second -> 2 whereValue = first where diff --git a/tests-integration/fixtures/formatting/1791093000_aligned_where_bindings/Main.snap b/tests-integration/fixtures/formatting/1791093000_aligned_where_bindings/Main.snap index b56383464..18ef61503 100644 --- a/tests-integration/fixtures/formatting/1791093000_aligned_where_bindings/Main.snap +++ b/tests-integration/fixtures/formatting/1791093000_aligned_where_bindings/Main.snap @@ -1,6 +1,6 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 118 +assertion_line: 127 --- === width 24, indent 1 === module Main where @@ -85,12 +85,11 @@ effect = do pure 1 sibling = 2 -branch input = - case input of - Just value -> result - where - result = value - Nothing -> fallback +branch input = case input of + Just value -> result + where + result = value + Nothing -> fallback where fallback = 0 @@ -146,12 +145,11 @@ effect = do pure 1 sibling = 2 -branch input = - case input of - Just value → result - where - result = value - Nothing → fallback +branch input = case input of + Just value → result + where + result = value + Nothing → fallback where fallback = 0 @@ -207,12 +205,11 @@ effect = do pure 1 sibling = 2 -branch input = - case input of - Just value -> result - where - result = value - Nothing -> fallback +branch input = case input of + Just value -> result + where + result = value + Nothing -> fallback where fallback = 0 diff --git a/tests-integration/fixtures/formatting/1791093360_leading_comma_layout_boundaries/Main.snap b/tests-integration/fixtures/formatting/1791093360_leading_comma_layout_boundaries/Main.snap index 7c90bac13..871af97ca 100644 --- a/tests-integration/fixtures/formatting/1791093360_leading_comma_layout_boundaries/Main.snap +++ b/tests-integration/fixtures/formatting/1791093360_leading_comma_layout_boundaries/Main.snap @@ -1,6 +1,6 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 118 +assertion_line: 127 --- === width 20, indent 1 === module Main where @@ -34,15 +34,17 @@ effect = do } finalAction -argument = use [ do - action - , nextArgument - ] - finalArgument +argument = use + [ do + action + , nextArgument + ] finalArgument patterns first second = - case first, second of + case + first, second + of { firstField: value , secondField: @@ -87,13 +89,13 @@ type Tail row = { | row } type RowTail row = (| row) -data Wrapped = - Wrapped - { firstField :: - FirstType - , secondField :: - SecondType - } +data Wrapped + = Wrapped + { firstField :: + FirstType + , secondField :: + SecondType + } literal = [ """first @@ -110,11 +112,12 @@ second""" ] updateEffect = - record { field = do - action - , otherField = - secondArgument - } + record + { field = do + action + , otherField = + secondArgument + } qualifiedUpdate = M.record { field = @@ -130,9 +133,8 @@ accessUpdate = secondArgument } parenthesizedUpdate = - ( - chooseRecord - firstArgument + ( chooseRecord + firstArgument ) { field = firstArgument @@ -147,12 +149,12 @@ sectionUpdate = secondArgument } widePrefix = use - "界界界界" { field: - do - action - , otherField: - secondArgument - } + "界界界界" + { field: do + action + , otherField: + secondArgument + } === width 40, indent 4, unicode === module Main where @@ -164,10 +166,9 @@ effect = do value ← secondAction in value ] - { result: - case input of - Just value → value - Nothing → fallback + { result: case input of + Just value → value + Nothing → fallback , local: let value = firstArgument @@ -180,10 +181,11 @@ effect = do } finalAction -argument = use [ do - action - , nextArgument - ] finalArgument +argument = use + [ do + action + , nextArgument + ] finalArgument patterns first second = case first, second of @@ -215,11 +217,11 @@ doLocal = do type Tail row = { | row } type RowTail row = (| row) -data Wrapped = - Wrapped - { firstField ∷ FirstType - , secondField ∷ SecondType - } +data Wrapped + = Wrapped + { firstField ∷ FirstType + , secondField ∷ SecondType + } literal = [ """first @@ -231,11 +233,11 @@ second""" } ] -updateEffect = record { field = do - action - , otherField = - secondArgument - } +updateEffect = record + { field = do + action + , otherField = secondArgument + } qualifiedUpdate = M.record { field = firstArgument @@ -256,11 +258,11 @@ sectionUpdate = { field = firstArgument , otherField = secondArgument } -widePrefix = use "界界界界" { field: do - action - , otherField: - secondArgument - } +widePrefix = use "界界界界" + { field: do + action + , otherField: secondArgument + } === width 80, indent 2 === module Main where @@ -272,10 +274,9 @@ effect = do value <- secondAction in value ] - { result: - case input of - Just value -> value - Nothing -> fallback + { result: case input of + Just value -> value + Nothing -> fallback , local: let value = firstArgument @@ -285,15 +286,15 @@ effect = do } finalAction -argument = use [ do - action - , nextArgument - ] finalArgument +argument = use + [ do + action + , nextArgument + ] finalArgument -patterns first second = - case first, second of - { firstField: value, secondField: other }, [ item, rest ] -> value - _, _ -> fallback +patterns first second = case first, second of + { firstField: value, secondField: other }, [ item, rest ] -> value + _, _ -> fallback named whole@{ firstField: value, secondField: other } = whole lambda = \{ firstField: value, secondField: other } -> value @@ -317,10 +318,11 @@ second""" , { nested: [ { value: firstArgument }, { value: secondArgument } ] } ] -updateEffect = record { field = do - action - , otherField = secondArgument - } +updateEffect = record + { field = do + action + , otherField = secondArgument + } qualifiedUpdate = M.record { field = firstArgument, otherField = secondArgument } accessUpdate = @@ -329,7 +331,8 @@ parenthesizedUpdate = (chooseRecord firstArgument) { field = firstArgument, otherField = secondArgument } sectionUpdate = _ { field = firstArgument, otherField = secondArgument } -widePrefix = use "界界界界" { field: do - action - , otherField: secondArgument - } +widePrefix = use "界界界界" + { field: do + action + , otherField: secondArgument + } diff --git a/tests-integration/fixtures/formatting/1791093720_leading_comma_guards/Main.snap b/tests-integration/fixtures/formatting/1791093720_leading_comma_guards/Main.snap index fddb9a08c..d2a88b285 100644 --- a/tests-integration/fixtures/formatting/1791093720_leading_comma_guards/Main.snap +++ b/tests-integration/fixtures/formatting/1791093720_leading_comma_guards/Main.snap @@ -1,6 +1,6 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 118 +assertion_line: 127 --- === width 20, indent 2 === module Main where @@ -54,14 +54,13 @@ recordGuard value predicate value = 1 | otherwise = 2 -branch value = - case value of - result - | [ firstElementName - , secondElementName - ] <- result, - predicate result -> 1 - _ -> 2 +branch value = case value of + result + | [ firstElementName + , secondElementName + ] <- result, + predicate result -> 1 + _ -> 2 === width 40, indent 4, unicode === module Main where @@ -82,14 +81,13 @@ recordGuard value predicate value = 1 | otherwise = 2 -branch value = - case value of - result - | [ firstElementName - , secondElementName - ] ← result, - predicate result → 1 - _ → 2 +branch value = case value of + result + | [ firstElementName + , secondElementName + ] ← result, + predicate result → 1 + _ → 2 === width 120, indent 2 === module Main where @@ -102,8 +100,7 @@ recordGuard value | { firstFieldName: firstArgument, secondFieldName: secondArgument } == value, predicate value = 1 | otherwise = 2 -branch value = - case value of - result - | [ firstElementName, secondElementName ] <- result, predicate result -> 1 - _ -> 2 +branch value = case value of + result + | [ firstElementName, secondElementName ] <- result, predicate result -> 1 + _ -> 2 diff --git a/tests-integration/fixtures/formatting/1791134340_leading_operator_operands/Main.snap b/tests-integration/fixtures/formatting/1791134340_leading_operator_operands/Main.snap index 8ac403410..35bfd4ae8 100644 --- a/tests-integration/fixtures/formatting/1791134340_leading_operator_operands/Main.snap +++ b/tests-integration/fixtures/formatting/1791134340_leading_operator_operands/Main.snap @@ -1,6 +1,6 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 118 +assertion_line: 127 --- === width 20, indent 2 === module Main where @@ -22,11 +22,12 @@ localBinding = + anotherLongOperand branching = firstLongOperand - <=> case choice of - First -> - firstBranchValue - Second -> - secondBranchValue + <=> + case choice of + First -> + firstBranchValue + Second -> + secondBranchValue guarded value | firstLongCondition @@ -63,8 +64,7 @@ commented = secondArgument negated = firstLongOperand - - ( - -secondLongOperand + - ( -secondLongOperand ) qualified = @@ -100,21 +100,19 @@ localBinding = bound = longBindingValue in bound + anotherLongOperand -branching = - firstLongOperand - <=> case choice of - First -> firstBranchValue - Second -> secondBranchValue +branching = firstLongOperand + <=> case choice of + First -> firstBranchValue + Second -> secondBranchValue guarded value | firstLongCondition value && secondLongCondition value = 1 | otherwise = 2 -pattern value = - case value of - Cons firstElement secondElement - : remainingElements -> result +pattern value = case value of + Cons firstElement secondElement + : remainingElements -> result typed :: forall value. @@ -174,10 +172,10 @@ localBinding = branching = firstLongOperand <=> case choice of - First -> - firstBranchValue - Second -> - secondBranchValue + First -> + firstBranchValue + Second -> + secondBranchValue guarded value | firstLongCondition @@ -214,8 +212,7 @@ commented = secondArgument negated = firstLongOperand - - ( - -secondLongOperand + - ( -secondLongOperand ) qualified = @@ -252,22 +249,19 @@ localBinding = in bound + anotherLongOperand -branching = - firstLongOperand - <=> case choice of - First → firstBranchValue - Second → - secondBranchValue +branching = firstLongOperand + <=> case choice of + First → firstBranchValue + Second → secondBranchValue guarded value | firstLongCondition value && secondLongCondition value = 1 | otherwise = 2 -pattern value = - case value of - Cons firstElement secondElement - : remainingElements → result +pattern value = case value of + Cons firstElement secondElement + : remainingElements → result typed ∷ ∀ value. diff --git a/tests-integration/fixtures/formatting/1791134340_nested_backtick_spacing/Main.snap b/tests-integration/fixtures/formatting/1791134340_nested_backtick_spacing/Main.snap index 18c8425ed..d4f28d58c 100644 --- a/tests-integration/fixtures/formatting/1791134340_nested_backtick_spacing/Main.snap +++ b/tests-integration/fixtures/formatting/1791134340_nested_backtick_spacing/Main.snap @@ -1,6 +1,6 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 118 +assertion_line: 127 --- === width 20, indent 2 === module Main where @@ -8,18 +8,15 @@ module Main where value = firstLongOperand `combine - ( - inner + ( inner `f` argument )` secondLongOperand nested = firstOperand `combine - ( - inner + ( inner `f - ( - deeper + ( deeper `g` argument )` another )` secondOperand @@ -37,17 +34,14 @@ module Main where value = firstLongOperand `combine - ( - inner `f` argument + ( inner `f` argument )` secondLongOperand nested = firstOperand `combine - ( - inner - `f - ( - deeper - `g` argument - )` another + ( inner + `f + ( deeper + `g` argument + )` another )` secondOperand diff --git a/tests-integration/fixtures/formatting/1791199200_sorted_imports/Main.snap b/tests-integration/fixtures/formatting/1791199200_sorted_imports/Main.snap index 04f899eac..10bf70ee1 100644 --- a/tests-integration/fixtures/formatting/1791199200_sorted_imports/Main.snap +++ b/tests-integration/fixtures/formatting/1791199200_sorted_imports/Main.snap @@ -1,6 +1,6 @@ --- source: tests-integration/tests/formatting.rs -assertion_line: 124 +assertion_line: 127 --- === width 80, indent 2 === module Main where -- Module explanation. @@ -37,8 +37,7 @@ import Data.Maybe (Maybe(..)) as Maybe import Data.Tuple - ( - Tuple(..) + ( Tuple(..) ) {- Tuple explanation. Still attached to Tuple. -} import Prelude @@ -51,12 +50,10 @@ import Prelude -- The declaration explanation. test = Tuple - ( - Maybe.Just - ( - Functions.identity - 1 - ) + ( Maybe.Just + ( Functions.identity + 1 + ) ) (length [ 1, 2 ]) @@ -80,8 +77,7 @@ import Prelude as Functions -- The declaration explanation. test = Tuple - ( - Maybe.Just - (Functions.identity 1) + ( Maybe.Just + (Functions.identity 1) ) (length [ 1, 2 ]) diff --git a/tests-integration/fixtures/formatting/1791361260_case_header_attachment/Main.purs b/tests-integration/fixtures/formatting/1791361260_case_header_attachment/Main.purs new file mode 100644 index 000000000..d7bb3dfc3 --- /dev/null +++ b/tests-integration/fixtures/formatting/1791361260_case_header_attachment/Main.purs @@ -0,0 +1,50 @@ +-- @format width=14 +-- @format width=15 +-- @format width=40 +-- @format width=50 +-- @format width=80 +-- @format width=40 indent=4 +module Main where + +abc = case _ of + 0 -> 1 + value -> value + +longHeader firstArgument secondArgument = case firstArgument of + 0 -> secondArgument + value -> case secondArgument of + 0 -> value + other -> other + +commented = -- keep the comment before the case + case _ of + value -> value + +handler = \value -> case value of + other -> other + +applied = consume case input of + value -> value + nextArgument + +scrutinee = case combine firstArgument secondArgument of + value -> "this indivisible body literal must not force the case header onto a new line" + +multiple = case combine firstArgument secondArgument, choose thirdArgument fourthArgument of + first, second -> first + +nestedScrutinee = case do + firstAction + secondAction + of + value -> value + +headerComment = case input -- scrutinee explanation + of + value -> value + +branchComment = case input of -- branch explanation + value -> value + +parenthesized = consume (case combine firstArgument secondArgument of + value -> value) diff --git a/tests-integration/fixtures/formatting/1791361260_case_header_attachment/Main.snap b/tests-integration/fixtures/formatting/1791361260_case_header_attachment/Main.snap new file mode 100644 index 000000000..5d4ca8f50 --- /dev/null +++ b/tests-integration/fixtures/formatting/1791361260_case_header_attachment/Main.snap @@ -0,0 +1,460 @@ +--- +source: tests-integration/tests/formatting.rs +assertion_line: 127 +--- +=== width 14, indent 2 === +module Main + where + +abc = + case _ of + 0 -> 1 + value -> + value + +longHeader firstArgument + secondArgument = + case + firstArgument + of + 0 -> + secondArgument + value -> + case + secondArgument + of + 0 -> + value + other -> + other + +commented = -- keep the comment before the case + case _ of + value -> + value + +handler = + \value -> + case + value + of + other -> + other + +applied = + consume + case + input + of + value -> + value + nextArgument + +scrutinee = + case + combine + firstArgument + secondArgument + of + value -> + "this indivisible body literal must not force the case header onto a new line" + +multiple = + case + combine + firstArgument + secondArgument + , choose + thirdArgument + fourthArgument + of + first, second -> + first + +nestedScrutinee = + case + do + firstAction + secondAction + of + value -> + value + +headerComment = + case + input -- scrutinee explanation + of + value -> + value + +branchComment = + case + input + of -- branch explanation + value -> + value + +parenthesized = + consume + ( case + combine + firstArgument + secondArgument + of + value -> + value + ) + +=== width 15, indent 2 === +module Main + where + +abc = case _ of + 0 -> 1 + value -> + value + +longHeader firstArgument + secondArgument = + case + firstArgument + of + 0 -> + secondArgument + value -> + case + secondArgument + of + 0 -> + value + other -> + other + +commented = -- keep the comment before the case + case _ of + value -> + value + +handler = + \value -> + case + value + of + other -> + other + +applied = + consume + case + input + of + value -> + value + nextArgument + +scrutinee = + case + combine + firstArgument + secondArgument + of + value -> + "this indivisible body literal must not force the case header onto a new line" + +multiple = + case + combine + firstArgument + secondArgument + , choose + thirdArgument + fourthArgument + of + first, second -> + first + +nestedScrutinee = + case + do + firstAction + secondAction + of + value -> + value + +headerComment = + case + input -- scrutinee explanation + of + value -> + value + +branchComment = + case input of -- branch explanation + value -> + value + +parenthesized = + consume + ( case + combine + firstArgument + secondArgument + of + value -> + value + ) + +=== width 40, indent 2 === +module Main where + +abc = case _ of + 0 -> 1 + value -> value + +longHeader firstArgument + secondArgument = case firstArgument of + 0 -> secondArgument + value -> case secondArgument of + 0 -> value + other -> other + +commented = -- keep the comment before the case + case _ of + value -> value + +handler = \value -> case value of + other -> other + +applied = consume case input of + value -> value + nextArgument + +scrutinee = + case + combine firstArgument secondArgument + of + value -> + "this indivisible body literal must not force the case header onto a new line" + +multiple = + case + combine firstArgument secondArgument + , choose + thirdArgument + fourthArgument + of + first, second -> first + +nestedScrutinee = + case + do + firstAction + secondAction + of + value -> value + +headerComment = + case + input -- scrutinee explanation + of + value -> value + +branchComment = case input of -- branch explanation + value -> value + +parenthesized = consume + ( case + combine + firstArgument + secondArgument + of + value -> value + ) + +=== width 50, indent 2 === +module Main where + +abc = case _ of + 0 -> 1 + value -> value + +longHeader firstArgument secondArgument = + case firstArgument of + 0 -> secondArgument + value -> case secondArgument of + 0 -> value + other -> other + +commented = -- keep the comment before the case + case _ of + value -> value + +handler = \value -> case value of + other -> other + +applied = consume case input of + value -> value + nextArgument + +scrutinee = + case combine firstArgument secondArgument of + value -> + "this indivisible body literal must not force the case header onto a new line" + +multiple = + case + combine firstArgument secondArgument + , choose thirdArgument fourthArgument + of + first, second -> first + +nestedScrutinee = + case + do + firstAction + secondAction + of + value -> value + +headerComment = + case + input -- scrutinee explanation + of + value -> value + +branchComment = case input of -- branch explanation + value -> value + +parenthesized = consume + ( case combine firstArgument secondArgument of + value -> value + ) + +=== width 80, indent 2 === +module Main where + +abc = case _ of + 0 -> 1 + value -> value + +longHeader firstArgument secondArgument = case firstArgument of + 0 -> secondArgument + value -> case secondArgument of + 0 -> value + other -> other + +commented = -- keep the comment before the case + case _ of + value -> value + +handler = \value -> case value of + other -> other + +applied = consume case input of + value -> value + nextArgument + +scrutinee = case combine firstArgument secondArgument of + value -> + "this indivisible body literal must not force the case header onto a new line" + +multiple = + case + combine firstArgument secondArgument, choose thirdArgument fourthArgument + of + first, second -> first + +nestedScrutinee = + case + do + firstAction + secondAction + of + value -> value + +headerComment = + case + input -- scrutinee explanation + of + value -> value + +branchComment = case input of -- branch explanation + value -> value + +parenthesized = consume + ( case combine firstArgument secondArgument of + value -> value + ) + +=== width 40, indent 4 === +module Main where + +abc = case _ of + 0 -> 1 + value -> value + +longHeader firstArgument + secondArgument = + case firstArgument of + 0 -> secondArgument + value -> case secondArgument of + 0 -> value + other -> other + +commented = -- keep the comment before the case + case _ of + value -> value + +handler = \value -> case value of + other -> other + +applied = consume case input of + value -> value + nextArgument + +scrutinee = + case + combine + firstArgument + secondArgument + of + value -> + "this indivisible body literal must not force the case header onto a new line" + +multiple = + case + combine + firstArgument + secondArgument + , choose + thirdArgument + fourthArgument + of + first, second -> first + +nestedScrutinee = + case + do + firstAction + secondAction + of + value -> value + +headerComment = + case + input -- scrutinee explanation + of + value -> value + +branchComment = case input of -- branch explanation + value -> value + +parenthesized = consume + ( case + combine + firstArgument + secondArgument + of + value -> value + ) diff --git a/tests-integration/fixtures/formatting/1791361260_import_export_list_alignment/Main.purs b/tests-integration/fixtures/formatting/1791361260_import_export_list_alignment/Main.purs new file mode 100644 index 000000000..c08250814 --- /dev/null +++ b/tests-integration/fixtures/formatting/1791361260_import_export_list_alignment/Main.purs @@ -0,0 +1,13 @@ +-- @format width=52 +-- @format width=53 +-- @format width=80 +-- @format width=30 indent=4 +-- @format width=20 indent=4 +module Main (first, second, Box(First, Second)) where + +import Library (first, second, Box(First, Second)) +import Library hiding (first, second, Box(First, Second)) as Hidden + +first = 1 +second = 2 +data Box = First | Second diff --git a/tests-integration/fixtures/formatting/1791361260_import_export_list_alignment/Main.snap b/tests-integration/fixtures/formatting/1791361260_import_export_list_alignment/Main.snap new file mode 100644 index 000000000..48e045d3e --- /dev/null +++ b/tests-integration/fixtures/formatting/1791361260_import_export_list_alignment/Main.snap @@ -0,0 +1,100 @@ +--- +source: tests-integration/tests/formatting.rs +assertion_line: 127 +--- +=== width 52, indent 2 === +module Main + ( first + , second + , Box(First, Second) + ) + where + +import Library (first, second, Box(First, Second)) +import Library + hiding (first, second, Box(First, Second)) + as Hidden + +first = 1 +second = 2 +data Box = First | Second + +=== width 53, indent 2 === +module Main (first, second, Box(First, Second)) where + +import Library (first, second, Box(First, Second)) +import Library + hiding (first, second, Box(First, Second)) + as Hidden + +first = 1 +second = 2 +data Box = First | Second + +=== width 80, indent 2 === +module Main (first, second, Box(First, Second)) where + +import Library (first, second, Box(First, Second)) +import Library hiding (first, second, Box(First, Second)) as Hidden + +first = 1 +second = 2 +data Box = First | Second + +=== width 30, indent 4 === +module Main + ( first + , second + , Box(First, Second) + ) + where + +import Library + ( first + , second + , Box(First, Second) + ) +import Library + hiding ( first + , second + , Box ( First + , Second + ) + ) + as Hidden + +first = 1 +second = 2 +data Box = First | Second + +=== width 20, indent 4 === +module Main + ( first + , second + , Box ( First + , Second + ) + ) + where + +import Library + ( first + , second + , Box ( First + , Second + ) + ) +import Library + hiding ( first + , second + , Box ( First + , Second + ) + ) + as Hidden + +first = 1 +second = 2 +data Box + = First + | Second diff --git a/tests-integration/fixtures/formatting/1791417240_else_if_chains/Main.purs b/tests-integration/fixtures/formatting/1791417240_else_if_chains/Main.purs new file mode 100644 index 000000000..f333daa99 --- /dev/null +++ b/tests-integration/fixtures/formatting/1791417240_else_if_chains/Main.purs @@ -0,0 +1,33 @@ +-- @format width=30 +-- @format width=80 +-- @format width=160 +-- @format width=40 indent=4 unicode=true +module Main where + +choose = if firstCondition then firstResult else if secondCondition then secondResult else if thirdCondition then thirdResult else fallback + +short = if a then x else if b then y else z + +blocks = if firstCondition then do + firstAction + secondAction +else if secondCondition then do + thirdAction +else fallback + +commented = if firstCondition then firstResult else -- next condition + if secondCondition then secondResult else fallback + +parenthesized = if firstCondition then firstResult else (if secondCondition then secondResult else fallback) + +nested = if firstCondition then if innerCondition then innerResult else innerFallback else if secondCondition then secondResult else fallback + +wrapped = if a then x else if predicate firstArgument secondArgument then y else z + +inlineComment = if a then x else {- next condition -} if b then y else z + +standaloneComment = if a then x else + {- next condition -} + if b then y else z + +operand = firstOperand + if firstCondition then firstResult else if secondCondition then secondResult else fallback diff --git a/tests-integration/fixtures/formatting/1791417240_else_if_chains/Main.snap b/tests-integration/fixtures/formatting/1791417240_else_if_chains/Main.snap new file mode 100644 index 000000000..574d8ac82 --- /dev/null +++ b/tests-integration/fixtures/formatting/1791417240_else_if_chains/Main.snap @@ -0,0 +1,278 @@ +--- +source: tests-integration/tests/formatting.rs +assertion_line: 127 +--- +=== width 30, indent 2 === +module Main where + +choose = + if firstCondition + then firstResult + else if secondCondition + then secondResult + else if thirdCondition + then thirdResult + else fallback + +short = + if a + then x + else if b + then y + else z + +blocks = + if firstCondition + then do + firstAction + secondAction + else if secondCondition + then do + thirdAction + else fallback + +commented = + if firstCondition + then firstResult + else -- next condition + if secondCondition + then secondResult + else fallback + +parenthesized = + if firstCondition + then firstResult + else + ( if secondCondition + then secondResult + else fallback + ) + +nested = + if firstCondition + then + if innerCondition + then innerResult + else innerFallback + else if secondCondition + then secondResult + else fallback + +wrapped = + if a + then x + else if + predicate + firstArgument + secondArgument + then y + else z + +inlineComment = + if a + then x + else {- next condition -} if + b + then y + else z + +standaloneComment = + if a + then x + else + {- next condition -} + if b then y else z + +operand = + firstOperand + + if firstCondition + then firstResult + else if secondCondition + then secondResult + else fallback + +=== width 80, indent 2 === +module Main where + +choose = + if firstCondition + then firstResult + else if secondCondition + then secondResult + else if thirdCondition + then thirdResult + else fallback + +short = if a then x else if b then y else z + +blocks = + if firstCondition + then do + firstAction + secondAction + else if secondCondition + then do + thirdAction + else fallback + +commented = + if firstCondition + then firstResult + else -- next condition + if secondCondition then secondResult else fallback + +parenthesized = + if firstCondition + then firstResult + else (if secondCondition then secondResult else fallback) + +nested = + if firstCondition + then if innerCondition then innerResult else innerFallback + else if secondCondition + then secondResult + else fallback + +wrapped = + if a then x else if predicate firstArgument secondArgument then y else z + +inlineComment = if a then x else {- next condition -} if b then y else z + +standaloneComment = + if a + then x + else + {- next condition -} + if b then y else z + +operand = + firstOperand + + if firstCondition + then firstResult + else if secondCondition + then secondResult + else fallback + +=== width 160, indent 2 === +module Main where + +choose = if firstCondition then firstResult else if secondCondition then secondResult else if thirdCondition then thirdResult else fallback + +short = if a then x else if b then y else z + +blocks = + if firstCondition + then do + firstAction + secondAction + else if secondCondition + then do + thirdAction + else fallback + +commented = + if firstCondition + then firstResult + else -- next condition + if secondCondition then secondResult else fallback + +parenthesized = if firstCondition then firstResult else (if secondCondition then secondResult else fallback) + +nested = if firstCondition then if innerCondition then innerResult else innerFallback else if secondCondition then secondResult else fallback + +wrapped = if a then x else if predicate firstArgument secondArgument then y else z + +inlineComment = if a then x else {- next condition -} if b then y else z + +standaloneComment = + if a + then x + else + {- next condition -} + if b then y else z + +operand = firstOperand + if firstCondition then firstResult else if secondCondition then secondResult else fallback + +=== width 40, indent 4, unicode === +module Main where + +choose = + if firstCondition + then firstResult + else if secondCondition + then secondResult + else if thirdCondition + then thirdResult + else fallback + +short = + if a then x else if b then y else z + +blocks = + if firstCondition + then do + firstAction + secondAction + else if secondCondition + then do + thirdAction + else fallback + +commented = + if firstCondition + then firstResult + else -- next condition + if secondCondition + then secondResult + else fallback + +parenthesized = + if firstCondition + then firstResult + else + ( if secondCondition + then secondResult + else fallback + ) + +nested = + if firstCondition + then + if innerCondition + then innerResult + else innerFallback + else if secondCondition + then secondResult + else fallback + +wrapped = + if a + then x + else if + predicate + firstArgument + secondArgument + then y + else z + +inlineComment = + if a + then x + else {- next condition -} if b + then y + else z + +standaloneComment = + if a + then x + else + {- next condition -} + if b then y else z + +operand = + firstOperand + + if firstCondition + then firstResult + else if secondCondition + then secondResult + else fallback