1use crate::ast::{BinaryOp, Commented, Expr, RecordEntry, RecordKey, SpannedExpr};
2use crate::ast_to_source::{expr_to_source, format_record_key, needs_parens_in_binop};
3use crate::values::LambdaArg;
4
5const DEFAULT_MAX_COLUMNS: usize = 80;
6const INDENT_SIZE: usize = 2;
7
8pub fn format_expr(expr: &SpannedExpr, max_columns: Option<usize>) -> String {
10 let max_cols = max_columns.unwrap_or(DEFAULT_MAX_COLUMNS);
11 format_expr_impl(expr, max_cols, 0)
12}
13
14fn format_expr_impl(expr: &SpannedExpr, max_cols: usize, indent: usize) -> String {
16 if let Expr::Lambda { args, body } = &expr.node {
18 return format_lambda(args, body, max_cols, indent);
19 }
20
21 if let Expr::DoBlock { .. } = &expr.node {
24 return format_multiline(expr, max_cols, indent);
25 }
26
27 let single_line = format_single_line(expr);
29
30 let first_line = single_line.lines().next().unwrap_or(&single_line);
33 let current_line_length = indent + first_line.len();
34
35 if !single_line.contains('\n') && current_line_length <= max_cols {
37 return single_line;
38 }
39
40 format_multiline(expr, max_cols, indent)
42}
43
44fn format_single_line(expr: &SpannedExpr) -> String {
46 match &expr.node {
47 Expr::Assignment { ident, value } => {
48 format!("{} = {}", ident, format_single_line(value))
49 }
50 Expr::Output { expr: inner_expr } => {
51 format!("output {}", format_single_line(inner_expr))
52 }
53 Expr::Lambda { args, body } => {
54 let args_str: Vec<String> = args.iter().map(lambda_arg_to_str).collect();
55 let args_part = if args.len() == 1 && matches!(args[0], LambdaArg::Required(_)) {
56 args_str[0].clone()
57 } else {
58 format!("({})", args_str.join(", "))
59 };
60 format!("{} => {}", args_part, format_single_line(body))
61 }
62 Expr::Call { func, args } => {
63 let func_str = match &func.node {
64 Expr::Lambda { .. } => format!("({})", format_single_line(func)),
65 _ => format_single_line(func),
66 };
67 let args_str: Vec<String> = args.iter().map(format_single_line).collect();
68 format!("{}({})", func_str, args_str.join(", "))
69 }
70 Expr::List(items) => {
71 if items.iter().any(|c| c.has_comments()) {
73 return "[\n]".to_string(); }
75 let items_str: Vec<String> =
76 items.iter().map(|c| format_single_line(&c.node)).collect();
77 format!("[{}]", items_str.join(", "))
78 }
79 Expr::Record(entries) => {
80 if entries.iter().any(|c| c.has_comments()) {
82 return "{\n}".to_string(); }
84 let entries_str: Vec<String> = entries
85 .iter()
86 .map(|c| format_record_entry_single_line(&c.node))
87 .collect();
88 format!("{{{}}}", entries_str.join(", "))
89 }
90 _ => expr_to_source(expr),
92 }
93}
94
95fn format_record_entry_single_line(entry: &RecordEntry) -> String {
97 match &entry.key {
98 RecordKey::Static(key) => format!(
99 "{}: {}",
100 format_record_key(key),
101 format_single_line(&entry.value)
102 ),
103 RecordKey::Dynamic(key_expr) => {
104 format!(
105 "[{}]: {}",
106 format_single_line(key_expr),
107 format_single_line(&entry.value)
108 )
109 }
110 RecordKey::Shorthand(name) => name.clone(),
111 RecordKey::Spread(expr) => format_single_line(expr),
112 }
113}
114
115fn format_multiline(expr: &SpannedExpr, max_cols: usize, indent: usize) -> String {
117 match &expr.node {
118 Expr::Output { expr: inner_expr } => {
119 let formatted_inner = format_expr_impl(inner_expr, max_cols, indent);
121 format!("output {}", formatted_inner)
122 }
123 Expr::Assignment { ident, value } => {
124 format_assignment_multiline(ident, value, max_cols, indent)
125 }
126 Expr::List(items) => format_list_multiline(items, max_cols, indent),
127 Expr::Record(entries) => format_record_multiline(entries, max_cols, indent),
128 Expr::Conditional {
129 condition,
130 then_expr,
131 else_expr,
132 } => format_conditional_multiline(condition, then_expr, else_expr, max_cols, indent),
133 Expr::Call { func, args } => format_call_multiline(func, args, max_cols, indent),
134 Expr::BinaryOp { op, left, right } => {
135 format_binary_op_multiline(op, left, right, max_cols, indent)
136 }
137 Expr::DoBlock {
138 statements,
139 return_expr,
140 } => format_do_block_multiline(statements, return_expr, max_cols, indent),
141 _ => expr_to_source(expr),
143 }
144}
145
146fn format_assignment_multiline(
148 ident: &str,
149 value: &SpannedExpr,
150 max_cols: usize,
151 indent: usize,
152) -> String {
153 let prefix = format!("{} = ", ident);
158
159 let formatted_value = format_expr_impl(value, max_cols, indent);
162
163 format!("{}{}", prefix, formatted_value)
164}
165
166fn format_list_multiline(
168 items: &[Commented<SpannedExpr>],
169 max_cols: usize,
170 indent: usize,
171) -> String {
172 if items.is_empty() {
173 return "[]".to_string();
174 }
175
176 let inner_indent = indent + INDENT_SIZE;
177 let indent_str = make_indent(inner_indent);
178
179 let mut result = "[".to_string();
180
181 for item in items.iter() {
182 for comment in &item.leading {
184 result.push('\n');
185 result.push_str(&indent_str);
186 result.push_str(comment);
187 }
188 result.push('\n');
190 result.push_str(&indent_str);
191 result.push_str(&format_expr_impl(&item.node, max_cols, inner_indent));
192 result.push(',');
193 if let Some(trailing) = &item.trailing {
195 result.push_str(" ");
196 result.push_str(trailing);
197 }
198 }
199
200 result.push('\n');
201 result.push_str(&make_indent(indent));
202 result.push(']');
203
204 result
205}
206
207fn format_record_multiline(
209 entries: &[Commented<RecordEntry>],
210 max_cols: usize,
211 indent: usize,
212) -> String {
213 if entries.is_empty() {
214 return "{}".to_string();
215 }
216
217 let inner_indent = indent + INDENT_SIZE;
218 let indent_str = make_indent(inner_indent);
219
220 let mut result = "{".to_string();
221
222 for entry in entries {
223 for comment in &entry.leading {
225 result.push('\n');
226 result.push_str(&indent_str);
227 result.push_str(comment);
228 }
229 result.push('\n');
231 result.push_str(&indent_str);
232 result.push_str(&format_record_entry(&entry.node, max_cols, inner_indent));
233 result.push(',');
234 if let Some(trailing) = &entry.trailing {
236 result.push_str(" ");
237 result.push_str(trailing);
238 }
239 }
240
241 result.push('\n');
242 result.push_str(&make_indent(indent));
243 result.push('}');
244
245 result
246}
247
248fn format_record_entry(entry: &RecordEntry, max_cols: usize, indent: usize) -> String {
250 match &entry.key {
251 RecordKey::Static(key) => {
252 format!(
253 "{}: {}",
254 format_record_key(key),
255 format_expr_impl(&entry.value, max_cols, indent)
256 )
257 }
258 RecordKey::Dynamic(key_expr) => {
259 format!(
260 "[{}]: {}",
261 format_expr_impl(key_expr, max_cols, indent),
262 format_expr_impl(&entry.value, max_cols, indent)
263 )
264 }
265 RecordKey::Shorthand(name) => name.clone(),
266 RecordKey::Spread(expr) => format_expr_impl(expr, max_cols, indent),
267 }
268}
269
270fn format_lambda(args: &[LambdaArg], body: &SpannedExpr, max_cols: usize, indent: usize) -> String {
272 let args_str: Vec<String> = args.iter().map(lambda_arg_to_str).collect();
273
274 let args_part = if args.len() == 1 && matches!(args[0], LambdaArg::Required(_)) {
276 format!("{} =>", args_str[0])
277 } else {
278 format!("({}) =>", args_str.join(", "))
279 };
280
281 if let Expr::DoBlock { .. } = &body.node {
283 let body_formatted = format_expr_impl(body, max_cols, indent);
284 return format!("{} {}", args_part, body_formatted);
285 }
286
287 let single_line_body = format_expr_impl(body, max_cols, indent);
289 let single_line = format!("{} {}", args_part, single_line_body);
290
291 if !single_line.contains('\n') && indent + single_line.len() <= max_cols {
293 return single_line;
294 }
295
296 let body_indent = indent + INDENT_SIZE;
298 format!(
299 "{}\n{}{}",
300 args_part,
301 make_indent(body_indent),
302 format_expr_impl(body, max_cols, body_indent)
303 )
304}
305
306fn format_conditional_multiline(
308 condition: &SpannedExpr,
309 then_expr: &SpannedExpr,
310 else_expr: &SpannedExpr,
311 max_cols: usize,
312 indent: usize,
313) -> String {
314 let cond_str = format_expr_impl(condition, max_cols, indent);
315
316 let if_then_prefix = format!("if {} then", cond_str);
318
319 let inner_indent = indent + INDENT_SIZE;
320
321 if indent + if_then_prefix.len() <= max_cols {
322 if let Expr::Conditional {
325 condition: else_cond,
326 then_expr: else_then,
327 else_expr: else_else,
328 } = &else_expr.node
329 {
330 let else_if_part =
332 format_conditional_multiline(else_cond, else_then, else_else, max_cols, indent);
333 format!(
334 "{}\n{}{}\n{}else {}",
335 if_then_prefix,
336 make_indent(inner_indent),
337 format_expr_impl(then_expr, max_cols, inner_indent),
338 make_indent(indent),
339 else_if_part
340 )
341 } else {
342 format!(
343 "{}\n{}{}\n{}else\n{}{}",
344 if_then_prefix,
345 make_indent(inner_indent),
346 format_expr_impl(then_expr, max_cols, inner_indent),
347 make_indent(indent),
348 make_indent(inner_indent),
349 format_expr_impl(else_expr, max_cols, inner_indent)
350 )
351 }
352 } else {
353 if let Expr::Conditional {
356 condition: else_cond,
357 then_expr: else_then,
358 else_expr: else_else,
359 } = &else_expr.node
360 {
361 let else_if_part =
362 format_conditional_multiline(else_cond, else_then, else_else, max_cols, indent);
363 format!(
364 "if\n{}{}\n{}then\n{}{}\n{}else {}",
365 make_indent(inner_indent),
366 format_expr_impl(condition, max_cols, inner_indent),
367 make_indent(indent),
368 make_indent(inner_indent),
369 format_expr_impl(then_expr, max_cols, inner_indent),
370 make_indent(indent),
371 else_if_part
372 )
373 } else {
374 format!(
375 "if\n{}{}\n{}then\n{}{}\n{}else\n{}{}",
376 make_indent(inner_indent),
377 format_expr_impl(condition, max_cols, inner_indent),
378 make_indent(indent),
379 make_indent(inner_indent),
380 format_expr_impl(then_expr, max_cols, inner_indent),
381 make_indent(indent),
382 make_indent(inner_indent),
383 format_expr_impl(else_expr, max_cols, inner_indent)
384 )
385 }
386 }
387}
388
389fn format_call_multiline(
391 func: &SpannedExpr,
392 args: &[SpannedExpr],
393 max_cols: usize,
394 indent: usize,
395) -> String {
396 let func_str = match &func.node {
397 Expr::Lambda { .. } => format!("({})", format_expr_impl(func, max_cols, indent)),
398 _ => format_expr_impl(func, max_cols, indent),
399 };
400
401 if args.is_empty() {
402 return format!("{}()", func_str);
403 }
404
405 let inner_indent = indent + INDENT_SIZE;
407 let indent_str = make_indent(inner_indent);
408
409 let mut result = format!("{}(", func_str);
410
411 for (i, arg) in args.iter().enumerate() {
412 result.push('\n');
413 result.push_str(&indent_str);
414 result.push_str(&format_expr_impl(arg, max_cols, inner_indent));
415
416 if i < args.len() - 1 {
417 result.push(',');
418 } else {
419 result.push(',');
421 }
422 }
423
424 result.push('\n');
425 result.push_str(&make_indent(indent));
426 result.push(')');
427
428 result
429}
430
431fn format_binary_op_multiline(
433 op: &BinaryOp,
434 left: &SpannedExpr,
435 right: &SpannedExpr,
436 max_cols: usize,
437 indent: usize,
438) -> String {
439 let op_str = binary_op_str(op);
440
441 let left_needs_parens = needs_parens_in_binop(op, left, true);
443 let right_needs_parens = needs_parens_in_binop(op, right, false);
444
445 let left_str = format_expr_impl(left, max_cols, indent);
446 let left_str = if left_needs_parens {
447 format!("({})", left_str)
448 } else {
449 left_str
450 };
451
452 if matches!(op, BinaryOp::Via | BinaryOp::Into | BinaryOp::Where)
455 && let Expr::Lambda { .. } = &right.node
456 {
457 let right_str = format_expr_impl(right, max_cols, indent);
459 let right_str = if right_needs_parens {
460 format!("({})", right_str)
461 } else {
462 right_str
463 };
464
465 let first_line_of_right = right_str.lines().next().unwrap_or(&right_str);
468 let first_line_combined = format!("{} {} {}", left_str, op_str, first_line_of_right);
469
470 if indent + first_line_combined.len() <= max_cols {
471 if right_str.contains('\n') {
474 let remaining_lines = right_str.lines().skip(1).collect::<Vec<_>>().join("\n");
476 return format!(
477 "{} {} {}\n{}",
478 left_str, op_str, first_line_of_right, remaining_lines
479 );
480 } else {
481 return format!("{} {} {}", left_str, op_str, right_str);
483 }
484 }
485
486 let continued_indent = indent;
488 let right_formatted = format_expr_impl(right, max_cols, continued_indent);
489 let right_formatted = if right_needs_parens {
490 format!("({})", right_formatted)
491 } else {
492 right_formatted
493 };
494 return format!(
495 "{}\n{}{} {}",
496 left_str,
497 make_indent(continued_indent),
498 op_str,
499 right_formatted
500 );
501 }
502
503 let right_indent = indent + INDENT_SIZE;
505 let right_str = format_expr_impl(right, max_cols, right_indent);
506 let right_str = if right_needs_parens {
507 format!("({})", right_str)
508 } else {
509 right_str
510 };
511 format!(
512 "{}\n{}{} {}",
513 left_str,
514 make_indent(right_indent),
515 op_str,
516 right_str
517 )
518}
519
520fn format_do_block_multiline(
522 statements: &[Commented<SpannedExpr>],
523 return_expr: &Commented<SpannedExpr>,
524 max_cols: usize,
525 indent: usize,
526) -> String {
527 let inner_indent = indent + INDENT_SIZE;
528 let indent_str = make_indent(inner_indent);
529
530 let mut result = "do {".to_string();
531
532 for stmt in statements {
533 for comment in &stmt.leading {
535 result.push('\n');
536 result.push_str(&indent_str);
537 result.push_str(comment);
538 }
539 result.push('\n');
541 result.push_str(&indent_str);
542 result.push_str(&format_expr_impl(&stmt.node, max_cols, inner_indent));
543 if let Some(trailing) = &stmt.trailing {
545 result.push_str(" ");
546 result.push_str(trailing);
547 }
548 }
549
550 for comment in &return_expr.leading {
552 result.push('\n');
553 result.push_str(&indent_str);
554 result.push_str(comment);
555 }
556 result.push('\n');
557 result.push_str(&indent_str);
558 result.push_str("return ");
559 result.push_str(&format_expr_impl(&return_expr.node, max_cols, inner_indent));
560 result.push('\n');
561 result.push_str(&make_indent(indent));
562 result.push('}');
563
564 result
565}
566
567fn lambda_arg_to_str(arg: &LambdaArg) -> String {
569 match arg {
570 LambdaArg::Required(name) => name.clone(),
571 LambdaArg::Optional(name) => format!("{}?", name),
572 LambdaArg::Rest(name) => format!("...{}", name),
573 }
574}
575
576fn binary_op_str(op: &BinaryOp) -> &'static str {
578 match op {
579 BinaryOp::Add => "+",
580 BinaryOp::Subtract => "-",
581 BinaryOp::Multiply => "*",
582 BinaryOp::Divide => "/",
583 BinaryOp::Modulo => "%",
584 BinaryOp::Power => "^",
585 BinaryOp::Equal => "==",
586 BinaryOp::NotEqual => "!=",
587 BinaryOp::Less => "<",
588 BinaryOp::LessEq => "<=",
589 BinaryOp::Greater => ">",
590 BinaryOp::GreaterEq => ">=",
591 BinaryOp::DotEqual => ".==",
592 BinaryOp::DotNotEqual => ".!=",
593 BinaryOp::DotLess => ".<",
594 BinaryOp::DotLessEq => ".<=",
595 BinaryOp::DotGreater => ".>",
596 BinaryOp::DotGreaterEq => ".>=",
597 BinaryOp::And => "&&",
598 BinaryOp::NaturalAnd => "and",
599 BinaryOp::Or => "||",
600 BinaryOp::NaturalOr => "or",
601 BinaryOp::Via => "via",
602 BinaryOp::Into => "into",
603 BinaryOp::Where => "where",
604 BinaryOp::Coalesce => "??",
605 }
606}
607
608fn make_indent(indent: usize) -> String {
610 " ".repeat(indent)
611}
612
613pub fn join_statements_with_spacing(
616 statements: &[(String, usize, usize)], ) -> String {
618 if statements.is_empty() {
619 return String::new();
620 }
621
622 let mut result = String::new();
623
624 for (i, (stmt, _start_line, end_line)) in statements.iter().enumerate() {
625 result.push_str(stmt);
626
627 if i < statements.len() - 1 {
629 let next_start_line = statements[i + 1].1;
630
631 let line_gap = next_start_line.saturating_sub(*end_line).saturating_sub(1);
636
637 let newlines = std::cmp::min(line_gap + 1, 3);
643
644 for _ in 0..newlines {
645 result.push('\n');
646 }
647 }
648 }
649
650 result
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656 use crate::expressions::pairs_to_expr;
657 use crate::parser::get_pairs;
658
659 fn parse_test_expr(source: &str) -> SpannedExpr {
660 use crate::parser::Rule;
661
662 let pairs = get_pairs(source).unwrap();
663
664 for pair in pairs {
666 if pair.as_rule() == Rule::statement
667 && let Some(inner_pair) = pair.into_inner().next()
668 {
669 return pairs_to_expr(inner_pair.into_inner()).unwrap();
670 }
671 }
672
673 panic!("No statement found in parsed input");
674 }
675
676 #[test]
677 fn test_format_short_list() {
678 let expr = parse_test_expr("[1, 2, 3]");
679 let formatted = format_expr(&expr, Some(80));
680 assert_eq!(formatted, "[1, 2, 3]");
681 }
682
683 #[test]
684 fn test_format_long_list() {
685 let expr = parse_test_expr("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]");
686 let formatted = format_expr(&expr, Some(40));
687 assert!(formatted.contains("\n"));
688 assert!(formatted.contains("[\n"));
689 }
690
691 #[test]
692 fn test_format_short_record() {
693 let expr = parse_test_expr("{x: 1, y: 2}");
694 let formatted = format_expr(&expr, Some(80));
695 assert_eq!(formatted, "{x: 1, y: 2}");
696 }
697
698 #[test]
699 fn test_format_long_record() {
700 let expr = parse_test_expr(
701 "{name: \"Alice\", age: 30, email: \"alice@example.com\", address: \"123 Main St\"}",
702 );
703 let formatted = format_expr(&expr, Some(40));
704 assert!(formatted.contains("\n"));
705 assert!(formatted.contains("{\n"));
706 }
707
708 #[test]
709 fn test_format_conditional() {
710 let expr =
711 parse_test_expr("if very_long_condition_variable > 100 then \"yes\" else \"no\"");
712 let formatted = format_expr(&expr, Some(30));
713 assert!(formatted.contains("\n"));
714 }
715
716 #[test]
717 fn test_format_binary_op() {
718 let expr = parse_test_expr("very_long_variable_name + another_very_long_variable_name");
719 let formatted = format_expr(&expr, Some(30));
720 assert!(formatted.contains("\n"));
721 }
722
723 #[test]
724 fn test_format_lambda() {
725 let expr = parse_test_expr("(x, y) => x + y");
726 let formatted = format_expr(&expr, Some(80));
727 assert_eq!(formatted, "(x, y) => x + y");
728 }
729
730 #[test]
731 fn test_format_nested_list() {
732 let expr = parse_test_expr("[[1, 2, 3], [4, 5, 6], [7, 8, 9]]");
733 let formatted = format_expr(&expr, Some(20));
734 assert!(formatted.contains("\n"));
735 }
736
737 #[test]
738 fn test_format_function_call() {
739 let expr = parse_test_expr("map([1, 2, 3], x => x * 2)");
740 let formatted = format_expr(&expr, Some(80));
741 assert_eq!(formatted, "map([1, 2, 3], x => x * 2)");
742 }
743
744 #[test]
745 fn test_format_do_block() {
746 let expr = parse_test_expr("do { x = 1\n return x }");
747 let formatted = format_expr(&expr, Some(80));
748 assert!(formatted.contains("do {"));
749 assert!(formatted.contains("return"));
750 }
751
752 #[test]
753 fn test_multiple_statements() {
754 use crate::parser::Rule;
755
756 let source = "x = [1, 2, 3, 4, 5]\ny = {name: \"Alice\", age: 30}\nz = x + y";
757 let pairs = get_pairs(source).unwrap();
758
759 let mut formatted_statements = Vec::new();
760
761 for pair in pairs {
762 if pair.as_rule() == Rule::statement
763 && let Some(inner_pair) = pair.into_inner().next()
764 && let Ok(expr) = pairs_to_expr(inner_pair.into_inner())
765 {
766 let formatted = format_expr(&expr, Some(80));
767 formatted_statements.push(formatted);
768 }
769 }
770
771 assert_eq!(formatted_statements.len(), 3);
773 assert_eq!(formatted_statements[0], "x = [1, 2, 3, 4, 5]");
774 assert_eq!(formatted_statements[1], "y = {name: \"Alice\", age: 30}");
775 assert_eq!(formatted_statements[2], "z = x + y");
776 }
777
778 #[test]
779 fn test_comments_are_preserved() {
780 use crate::parser::Rule;
781
782 let source = "// Comment 1\nx = [1, 2, 3]\n// Comment 2\ny = x + 1";
783 let pairs = get_pairs(source).unwrap();
784
785 let mut formatted_statements = Vec::new();
786
787 for pair in pairs {
788 if pair.as_rule() == Rule::statement
789 && let Some(inner_pair) = pair.into_inner().next()
790 {
791 match inner_pair.as_rule() {
792 Rule::comment => {
793 formatted_statements.push(inner_pair.as_str().to_string());
795 }
796 _ => {
797 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
799 let formatted = format_expr(&expr, Some(80));
800 formatted_statements.push(formatted);
801 }
802 }
803 }
804 }
805 }
806
807 assert_eq!(formatted_statements.len(), 4);
809 assert_eq!(formatted_statements[0], "// Comment 1");
810 assert_eq!(formatted_statements[1], "x = [1, 2, 3]");
811 assert_eq!(formatted_statements[2], "// Comment 2");
812 assert_eq!(formatted_statements[3], "y = x + 1");
813 }
814
815 #[test]
816 fn test_comments_with_formatted_code() {
817 use crate::parser::Rule;
818
819 let source = "// Configuration\nconfig = {name: \"test\", debug: true}\n// Process data\nresult = [1, 2, 3]";
820 let pairs = get_pairs(source).unwrap();
821
822 let mut formatted_statements = Vec::new();
823
824 for pair in pairs {
825 if pair.as_rule() == Rule::statement
826 && let Some(inner_pair) = pair.into_inner().next()
827 {
828 match inner_pair.as_rule() {
829 Rule::comment => {
830 formatted_statements.push(inner_pair.as_str().to_string());
831 }
832 _ => {
833 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
834 let formatted = format_expr(&expr, Some(80));
835 formatted_statements.push(formatted);
836 }
837 }
838 }
839 }
840 }
841
842 let result = formatted_statements.join("\n");
843
844 assert_eq!(formatted_statements.len(), 4);
846 assert_eq!(formatted_statements[0], "// Configuration");
847 assert!(formatted_statements[1].starts_with("config = "));
848 assert_eq!(formatted_statements[2], "// Process data");
849 assert_eq!(formatted_statements[3], "result = [1, 2, 3]");
850
851 assert!(result.contains("// Configuration"));
853 assert!(result.contains("// Process data"));
854 }
855
856 #[test]
857 fn test_output_declaration_with_assignment() {
858 use crate::parser::Rule;
859
860 let source = "output result = 42";
861 let pairs = get_pairs(source).unwrap();
862
863 let mut formatted_statements = Vec::new();
864
865 for pair in pairs {
866 if pair.as_rule() == Rule::statement
867 && let Some(inner_pair) = pair.into_inner().next()
868 {
869 match inner_pair.as_rule() {
870 Rule::output_declaration => {
871 let mut inner = inner_pair.into_inner();
873 let assignment_or_ident = inner.next().unwrap();
874
875 let formatted = if assignment_or_ident.as_rule() == Rule::assignment {
876 let mut assignment_inner = assignment_or_ident.into_inner();
878 let ident = assignment_inner.next().unwrap().as_str();
879 let value_expr =
880 pairs_to_expr(assignment_inner.next().unwrap().into_inner())
881 .unwrap();
882 let value_formatted = format_expr(&value_expr, Some(80));
883 format!("{} = {}", ident, value_formatted)
884 } else {
885 assignment_or_ident.as_str().to_string()
886 };
887
888 formatted_statements.push(format!("output {}", formatted));
889 }
890 _ => {
891 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
892 let formatted = format_expr(&expr, Some(80));
893 formatted_statements.push(formatted);
894 }
895 }
896 }
897 }
898 }
899
900 assert_eq!(formatted_statements.len(), 1);
901 assert_eq!(formatted_statements[0], "output result = 42");
902 }
903
904 #[test]
905 fn test_output_statement_separate() {
906 use crate::parser::Rule;
907
908 let source = "result = 42\noutput result";
909 let pairs = get_pairs(source).unwrap();
910
911 let mut formatted_statements = Vec::new();
912
913 for pair in pairs {
914 if pair.as_rule() == Rule::statement
915 && let Some(inner_pair) = pair.into_inner().next()
916 {
917 match inner_pair.as_rule() {
918 Rule::output_declaration => {
919 let mut inner = inner_pair.into_inner();
921 let assignment_or_ident = inner.next().unwrap();
922
923 let formatted = if assignment_or_ident.as_rule() == Rule::assignment {
924 let mut assignment_inner = assignment_or_ident.into_inner();
926 let ident = assignment_inner.next().unwrap().as_str();
927 let value_expr =
928 pairs_to_expr(assignment_inner.next().unwrap().into_inner())
929 .unwrap();
930 let value_formatted = format_expr(&value_expr, Some(80));
931 format!("{} = {}", ident, value_formatted)
932 } else {
933 assignment_or_ident.as_str().to_string()
934 };
935
936 formatted_statements.push(format!("output {}", formatted));
937 }
938 _ => {
939 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
940 let formatted = format_expr(&expr, Some(80));
941 formatted_statements.push(formatted);
942 }
943 }
944 }
945 }
946 }
947
948 assert_eq!(formatted_statements.len(), 2);
949 assert_eq!(formatted_statements[0], "result = 42");
950 assert_eq!(formatted_statements[1], "output result");
951 }
952
953 #[test]
954 fn test_output_with_complex_expression() {
955 use crate::parser::Rule;
956
957 let source = "output total = [1, 2, 3] into sum";
958 let pairs = get_pairs(source).unwrap();
959
960 let mut formatted_statements = Vec::new();
961
962 for pair in pairs {
963 if pair.as_rule() == Rule::statement
964 && let Some(inner_pair) = pair.into_inner().next()
965 {
966 match inner_pair.as_rule() {
967 Rule::output_declaration => {
968 let mut inner = inner_pair.into_inner();
970 let assignment_or_ident = inner.next().unwrap();
971
972 let formatted = if assignment_or_ident.as_rule() == Rule::assignment {
973 let mut assignment_inner = assignment_or_ident.into_inner();
975 let ident = assignment_inner.next().unwrap().as_str();
976 let value_expr =
977 pairs_to_expr(assignment_inner.next().unwrap().into_inner())
978 .unwrap();
979 let value_formatted = format_expr(&value_expr, Some(80));
980 format!("{} = {}", ident, value_formatted)
981 } else {
982 assignment_or_ident.as_str().to_string()
983 };
984
985 formatted_statements.push(format!("output {}", formatted));
986 }
987 _ => {
988 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
989 let formatted = format_expr(&expr, Some(80));
990 formatted_statements.push(formatted);
991 }
992 }
993 }
994 }
995 }
996
997 assert_eq!(formatted_statements.len(), 1);
998 assert_eq!(formatted_statements[0], "output total = [1, 2, 3] into sum");
999 }
1000
1001 #[test]
1002 fn test_end_of_line_comments() {
1003 use crate::parser::Rule;
1004
1005 let source = "x = 5 // this is an end-of-line comment\ny = 10";
1006 let pairs = get_pairs(source).unwrap();
1007
1008 let mut formatted_statements = Vec::new();
1009
1010 for pair in pairs {
1011 if pair.as_rule() == Rule::statement {
1012 let mut inner_pairs = pair.into_inner();
1013
1014 if let Some(first_pair) = inner_pairs.next() {
1015 let formatted = match first_pair.as_rule() {
1016 Rule::comment => first_pair.as_str().to_string(),
1017 _ => {
1018 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
1019 format_expr(&expr, Some(80))
1020 } else {
1021 continue;
1022 }
1023 }
1024 };
1025
1026 if let Some(eol_comment) = inner_pairs.next() {
1028 if eol_comment.as_rule() == Rule::comment {
1029 formatted_statements.push(format!(
1030 "{} {}",
1031 formatted,
1032 eol_comment.as_str()
1033 ));
1034 } else {
1035 formatted_statements.push(formatted);
1036 }
1037 } else {
1038 formatted_statements.push(formatted);
1039 }
1040 }
1041 }
1042 }
1043
1044 assert_eq!(formatted_statements.len(), 2);
1045 assert_eq!(
1046 formatted_statements[0],
1047 "x = 5 // this is an end-of-line comment"
1048 );
1049 assert_eq!(formatted_statements[1], "y = 10");
1050 }
1051
1052 #[test]
1053 fn test_multiple_end_of_line_comments() {
1054 use crate::parser::Rule;
1055
1056 let source = "a = 1 // comment 1\nb = 2 // comment 2\nc = 3";
1057 let pairs = get_pairs(source).unwrap();
1058
1059 let mut formatted_statements = Vec::new();
1060
1061 for pair in pairs {
1062 if pair.as_rule() == Rule::statement {
1063 let mut inner_pairs = pair.into_inner();
1064
1065 if let Some(first_pair) = inner_pairs.next() {
1066 let formatted = match first_pair.as_rule() {
1067 Rule::comment => first_pair.as_str().to_string(),
1068 _ => {
1069 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
1070 format_expr(&expr, Some(80))
1071 } else {
1072 continue;
1073 }
1074 }
1075 };
1076
1077 if let Some(eol_comment) = inner_pairs.next() {
1079 if eol_comment.as_rule() == Rule::comment {
1080 formatted_statements.push(format!(
1081 "{} {}",
1082 formatted,
1083 eol_comment.as_str()
1084 ));
1085 } else {
1086 formatted_statements.push(formatted);
1087 }
1088 } else {
1089 formatted_statements.push(formatted);
1090 }
1091 }
1092 }
1093 }
1094
1095 assert_eq!(formatted_statements.len(), 3);
1096 assert_eq!(formatted_statements[0], "a = 1 // comment 1");
1097 assert_eq!(formatted_statements[1], "b = 2 // comment 2");
1098 assert_eq!(formatted_statements[2], "c = 3");
1099 }
1100
1101 #[test]
1102 fn test_eol_comments_not_joined_with_next_line() {
1103 use crate::parser::Rule;
1104
1105 let source = "x = 1 // first value\ny = 2 // second value\nz = x + y";
1107 let pairs = get_pairs(source).unwrap();
1108
1109 let mut formatted_statements = Vec::new();
1110
1111 for pair in pairs {
1112 if pair.as_rule() == Rule::statement {
1113 let mut inner_pairs = pair.into_inner();
1114
1115 if let Some(first_pair) = inner_pairs.next() {
1116 let formatted = match first_pair.as_rule() {
1117 Rule::comment => first_pair.as_str().to_string(),
1118 _ => {
1119 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
1120 format_expr(&expr, Some(80))
1121 } else {
1122 continue;
1123 }
1124 }
1125 };
1126
1127 if let Some(eol_comment) = inner_pairs.next() {
1129 if eol_comment.as_rule() == Rule::comment {
1130 formatted_statements.push(format!(
1131 "{} {}",
1132 formatted,
1133 eol_comment.as_str()
1134 ));
1135 } else {
1136 formatted_statements.push(formatted);
1137 }
1138 } else {
1139 formatted_statements.push(formatted);
1140 }
1141 }
1142 }
1143 }
1144
1145 let result = formatted_statements.join("\n");
1147
1148 assert_eq!(formatted_statements.len(), 3);
1149 assert_eq!(result.lines().count(), 3);
1151 assert_eq!(formatted_statements[0], "x = 1 // first value");
1152 assert_eq!(formatted_statements[1], "y = 2 // second value");
1153 assert_eq!(formatted_statements[2], "z = x + y");
1154
1155 for line in result.lines() {
1157 assert_eq!(
1159 line.matches('=').count(),
1160 1,
1161 "Line should not contain multiple statements: {}",
1162 line
1163 );
1164 }
1165 }
1166
1167 #[test]
1168 fn test_eol_comments_with_line_breaking() {
1169 use crate::parser::Rule;
1170
1171 let source = "longRecord = {name: \"Alice\", age: 30, email: \"alice@example.com\", address: \"123 Main St\"} // user data";
1173 let pairs = get_pairs(source).unwrap();
1174
1175 let mut formatted_statements = Vec::new();
1176
1177 for pair in pairs {
1178 if pair.as_rule() == Rule::statement {
1179 let mut inner_pairs = pair.into_inner();
1180
1181 if let Some(first_pair) = inner_pairs.next() {
1182 let formatted = match first_pair.as_rule() {
1183 Rule::comment => first_pair.as_str().to_string(),
1184 _ => {
1185 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
1186 format_expr(&expr, Some(40))
1188 } else {
1189 continue;
1190 }
1191 }
1192 };
1193
1194 if let Some(eol_comment) = inner_pairs.next() {
1196 if eol_comment.as_rule() == Rule::comment {
1197 formatted_statements.push(format!(
1198 "{} {}",
1199 formatted,
1200 eol_comment.as_str()
1201 ));
1202 } else {
1203 formatted_statements.push(formatted);
1204 }
1205 } else {
1206 formatted_statements.push(formatted);
1207 }
1208 }
1209 }
1210 }
1211
1212 assert_eq!(formatted_statements.len(), 1);
1213 let result = &formatted_statements[0];
1214
1215 assert!(result.ends_with("// user data"));
1217 assert!(result.contains("longRecord = "));
1219 }
1220
1221 #[test]
1222 fn test_actual_line_breaking_behavior() {
1223 use crate::parser::Rule;
1224
1225 let source = "x = {name: \"Alice\", age: 30, email: \"alice@example.com\", address: \"123 Main St\", city: \"Springfield\"}";
1227 let pairs = get_pairs(source).unwrap();
1228
1229 for pair in pairs {
1230 if pair.as_rule() == Rule::statement
1231 && let Some(inner_pair) = pair.into_inner().next()
1232 && let Ok(expr) = pairs_to_expr(inner_pair.into_inner())
1233 {
1234 let formatted = format_expr(&expr, Some(40));
1235 println!("Formatted output:\n{}", formatted);
1236 println!("Line count: {}", formatted.lines().count());
1237
1238 if formatted.lines().count() > 1 {
1240 println!("✓ Lines were broken");
1241 } else {
1242 println!(
1243 "✗ No line breaking occurred - output is {} chars",
1244 formatted.len()
1245 );
1246 }
1247 }
1248 }
1249 }
1250
1251 #[test]
1252 fn test_multiline_input_gets_collapsed() {
1253 use crate::parser::Rule;
1254
1255 let source = "x = [\n 1,\n 2,\n 3\n]";
1257 let pairs = get_pairs(source).unwrap();
1258
1259 for pair in pairs {
1260 if pair.as_rule() == Rule::statement
1261 && let Some(inner_pair) = pair.into_inner().next()
1262 && let Ok(expr) = pairs_to_expr(inner_pair.into_inner())
1263 {
1264 let formatted = format_expr(&expr, Some(80));
1265 println!("Input:\n{}", source);
1266 println!("Output:\n{}", formatted);
1267
1268 assert_eq!(formatted, "x = [1, 2, 3]");
1270 }
1271 }
1272 }
1273
1274 #[test]
1275 fn test_empty_lines_between_statements() {
1276 use crate::parser::Rule;
1277
1278 let source = "x = 1\n\ny = 2\n\n\n\nz = 3";
1280 let pairs = get_pairs(source).unwrap();
1281
1282 let mut statements_with_positions = Vec::new();
1283
1284 for pair in pairs {
1285 if pair.as_rule() == Rule::statement {
1286 let start_line = pair.as_span().start_pos().line_col().0;
1287 let end_line = pair.as_span().end_pos().line_col().0;
1288
1289 if let Some(inner_pair) = pair.into_inner().next()
1290 && let Ok(expr) = pairs_to_expr(inner_pair.into_inner())
1291 {
1292 let formatted = format_expr(&expr, Some(80));
1293 statements_with_positions.push((formatted, start_line, end_line));
1294 }
1295 }
1296 }
1297
1298 println!("Statement positions: {:?}", statements_with_positions);
1299
1300 let result = join_statements_with_spacing(&statements_with_positions);
1302 println!("Output with preserved spacing:\n{}", result);
1303
1304 assert_eq!(result.lines().count(), 6); assert_eq!(result, "x = 1\n\ny = 2\n\n\nz = 3");
1315 }
1316
1317 #[test]
1318 fn test_assignment_with_long_list_indentation() {
1319 use crate::parser::Rule;
1320
1321 let source = "ingredients = [{name: \"sugar\", amount: 1}, {name: \"flour\", amount: 2}]";
1323 let pairs = get_pairs(source).unwrap();
1324
1325 for pair in pairs {
1326 if pair.as_rule() == Rule::statement
1327 && let Some(inner_pair) = pair.into_inner().next()
1328 && let Ok(expr) = pairs_to_expr(inner_pair.into_inner())
1329 {
1330 let formatted = format_expr(&expr, Some(40));
1331 println!("Formatted:\n{}", formatted);
1332
1333 assert!(formatted.contains("[\n {"));
1335 assert!(!formatted.contains(" ")); }
1337 }
1338 }
1339
1340 #[test]
1341 fn test_else_if_chain_stays_flat() {
1342 let source = "x = if a then 1 else if b then 2 else if c then 3 else 4";
1344 let expr = parse_test_expr(source);
1345 let formatted = format_expr(&expr, Some(40));
1346 println!("Formatted:\n{}", formatted);
1347
1348 let else_if_count = formatted.matches("else if").count();
1351 assert_eq!(else_if_count, 2, "Should have 2 'else if' clauses");
1352
1353 for line in formatted.lines() {
1356 let leading_spaces = line.len() - line.trim_start().len();
1357 assert!(
1359 leading_spaces <= 2,
1360 "Line has too much indentation: '{}'",
1361 line
1362 );
1363 }
1364 }
1365
1366 #[test]
1367 fn test_long_else_if_chain() {
1368 let source = "tax = if income <= 10000 then income * 0.1 else if income <= 50000 then 1000 + (income - 10000) * 0.2 else if income <= 100000 then 9000 + (income - 50000) * 0.3 else 24000 + (income - 100000) * 0.4";
1370 let expr = parse_test_expr(source);
1371 let formatted = format_expr(&expr, Some(60));
1372 println!("Formatted:\n{}", formatted);
1373
1374 let else_count = formatted.matches("\nelse").count();
1376 assert!(
1377 else_count >= 3,
1378 "Should have at least 3 else/else-if clauses, found {}",
1379 else_count
1380 );
1381
1382 for line in formatted.lines() {
1384 if line.starts_with("else") {
1385 assert!(
1387 line.starts_with("else"),
1388 "else clause should start at column 0: '{}'",
1389 line
1390 );
1391 }
1392 }
1393 }
1394
1395 #[test]
1396 fn test_multiline_preserves_precedence_parentheses() {
1397 let source = "result = amount * (rate * (1 + rate) ^ n) / ((1 + rate) ^ n - 1)";
1401 let expr = parse_test_expr(source);
1402 let formatted = format_expr(&expr, Some(40));
1403 println!("Formatted:\n{}", formatted);
1404
1405 assert!(
1408 formatted.contains("/ ((1 + rate) ^ n - 1)"),
1409 "Denominator should be wrapped in parentheses to preserve precedence. Got:\n{}",
1410 formatted
1411 );
1412 }
1413
1414 #[test]
1415 fn test_multiline_division_with_subtraction() {
1416 let source = "x = a / (b - c)";
1418 let expr = parse_test_expr(source);
1419 let formatted = format_expr(&expr, Some(80));
1420
1421 assert_eq!(formatted, "x = a / (b - c)");
1422 }
1423
1424 #[test]
1425 fn test_multiline_multiplication_with_addition() {
1426 let source = "x = a * (b + c)";
1428 let expr = parse_test_expr(source);
1429 let formatted = format_expr(&expr, Some(80));
1430
1431 assert_eq!(formatted, "x = a * (b + c)");
1432 }
1433
1434 #[test]
1435 fn test_else_indented_in_lambda_body() {
1436 let source = "is_positive = n => if n > 0 then true else false";
1439 let expr = parse_test_expr(source);
1440 let formatted = format_expr(&expr, Some(30)); for line in formatted.lines() {
1444 if line.trim().starts_with("else") {
1445 let leading_spaces = line.len() - line.trim_start().len();
1446 assert!(
1447 leading_spaces > 0,
1448 "else should be indented in lambda body, got: '{}'",
1449 line
1450 );
1451 }
1452 }
1453
1454 let reparsed = parse_test_expr(&formatted);
1456 assert!(matches!(reparsed.node, Expr::Assignment { .. }));
1457 }
1458
1459 #[test]
1460 fn test_via_breaks_before_operator_not_after() {
1461 let source = "result = [1, 2, 3, 4, 5] via (x) => x * 2";
1464 let expr = parse_test_expr(source);
1465 let formatted = format_expr(&expr, Some(30)); for line in formatted.lines() {
1469 assert!(
1470 !line.trim_end().ends_with("via"),
1471 "via should not be at end of line (would be parse error), got: '{}'",
1472 line
1473 );
1474 }
1475
1476 let reparsed = parse_test_expr(&formatted);
1478 assert!(matches!(reparsed.node, Expr::Assignment { .. }));
1479 }
1480
1481 #[test]
1482 fn test_record_keys_with_spaces_preserve_quotes() {
1483 let source = r#"x = {"my key": 1, "another-key": 2}"#;
1485 let expr = parse_test_expr(source);
1486 let formatted = format_expr(&expr, Some(80));
1487
1488 assert!(
1490 formatted.contains(r#""my key""#),
1491 "Key with spaces should remain quoted, got: {}",
1492 formatted
1493 );
1494 assert!(
1495 formatted.contains(r#""another-key""#),
1496 "Key with dashes should remain quoted, got: {}",
1497 formatted
1498 );
1499
1500 let reparsed = parse_test_expr(&formatted);
1502 assert!(matches!(reparsed.node, Expr::Assignment { .. }));
1503 }
1504
1505 #[test]
1506 fn test_record_keys_valid_identifiers_no_quotes() {
1507 let source = r#"x = {name: "Alice", age: 30}"#;
1509 let expr = parse_test_expr(source);
1510 let formatted = format_expr(&expr, Some(80));
1511
1512 assert_eq!(formatted, r#"x = {name: "Alice", age: 30}"#);
1514 }
1515
1516 #[test]
1517 fn test_record_keys_starting_with_number_need_quotes() {
1518 let source = r#"x = {"123abc": 1}"#;
1520 let expr = parse_test_expr(source);
1521 let formatted = format_expr(&expr, Some(80));
1522
1523 assert!(
1524 formatted.contains(r#""123abc""#),
1525 "Key starting with number should remain quoted, got: {}",
1526 formatted
1527 );
1528
1529 let reparsed = parse_test_expr(&formatted);
1531 assert!(matches!(reparsed.node, Expr::Assignment { .. }));
1532 }
1533
1534 #[test]
1535 fn test_list_comments_preserved() {
1536 use crate::expressions::pairs_to_expr_with_comments;
1537 use crate::parser::Rule;
1538
1539 let source = "x = [\n // first item\n 1,\n 2, // inline\n // third\n 3,\n]";
1540 let pairs = get_pairs(source).unwrap();
1541
1542 for pair in pairs {
1543 if pair.as_rule() == Rule::statement
1544 && let Some(inner_pair) = pair.into_inner().next()
1545 && let Ok(expr) = pairs_to_expr_with_comments(inner_pair.into_inner())
1546 {
1547 let formatted = format_expr(&expr, Some(80));
1548
1549 assert!(
1550 formatted.contains("// first item"),
1551 "Leading comment should be preserved, got:\n{}",
1552 formatted
1553 );
1554 assert!(
1555 formatted.contains("// inline"),
1556 "Inline comment should be preserved, got:\n{}",
1557 formatted
1558 );
1559 assert!(
1560 formatted.contains("// third"),
1561 "Leading comment on third item should be preserved, got:\n{}",
1562 formatted
1563 );
1564 }
1565 }
1566 }
1567
1568 #[test]
1569 fn test_record_comments_preserved() {
1570 use crate::expressions::pairs_to_expr_with_comments;
1571 use crate::parser::Rule;
1572
1573 let source = "x = {\n // name field\n name: \"Alice\",\n age: 30, // years old\n}";
1574 let pairs = get_pairs(source).unwrap();
1575
1576 for pair in pairs {
1577 if pair.as_rule() == Rule::statement
1578 && let Some(inner_pair) = pair.into_inner().next()
1579 && let Ok(expr) = pairs_to_expr_with_comments(inner_pair.into_inner())
1580 {
1581 let formatted = format_expr(&expr, Some(80));
1582
1583 assert!(
1584 formatted.contains("// name field"),
1585 "Leading comment should be preserved, got:\n{}",
1586 formatted
1587 );
1588 assert!(
1589 formatted.contains("// years old"),
1590 "Inline comment should be preserved, got:\n{}",
1591 formatted
1592 );
1593 }
1594 }
1595 }
1596
1597 #[test]
1598 fn test_do_block_comments_preserved() {
1599 use crate::expressions::pairs_to_expr_with_comments;
1600 use crate::parser::Rule;
1601
1602 let source =
1603 "f = n => do {\n // compute\n doubled = n * 2\n // result\n return doubled\n}";
1604 let pairs = get_pairs(source).unwrap();
1605
1606 for pair in pairs {
1607 if pair.as_rule() == Rule::statement
1608 && let Some(inner_pair) = pair.into_inner().next()
1609 && let Ok(expr) = pairs_to_expr_with_comments(inner_pair.into_inner())
1610 {
1611 let formatted = format_expr(&expr, Some(80));
1612
1613 assert!(
1614 formatted.contains("// compute"),
1615 "Statement leading comment should be preserved, got:\n{}",
1616 formatted
1617 );
1618 assert!(
1619 formatted.contains("// result"),
1620 "Return leading comment should be preserved, got:\n{}",
1621 formatted
1622 );
1623 }
1624 }
1625 }
1626
1627 #[test]
1628 fn test_comments_force_multiline() {
1629 use crate::expressions::pairs_to_expr_with_comments;
1630 use crate::parser::Rule;
1631
1632 let source = "x = [1, // one\n2]";
1634 let pairs = get_pairs(source).unwrap();
1635
1636 for pair in pairs {
1637 if pair.as_rule() == Rule::statement
1638 && let Some(inner_pair) = pair.into_inner().next()
1639 && let Ok(expr) = pairs_to_expr_with_comments(inner_pair.into_inner())
1640 {
1641 let formatted = format_expr(&expr, Some(80));
1642
1643 assert!(
1645 formatted.contains('\n'),
1646 "List with comments should be multiline, got:\n{}",
1647 formatted
1648 );
1649 assert!(
1650 formatted.contains("// one"),
1651 "Comment should be preserved, got:\n{}",
1652 formatted
1653 );
1654 }
1655 }
1656 }
1657}