1use cove_diag::Span;
34
35use crate::ast::{
36 Arg, BinaryOp, Block, EnumCase, EnumDecl, Expr, ExprKind, Field, FnDecl, GenericParam,
37 ImplBlock, Item, ItemKind, MatchArm, Param, Pattern, PatternKind, Receiver, SourceUnit, Stmt,
38 StmtKind, StrPart, StructDecl, TraitDecl, TraitMethod, Type, TypeAlias, TypeKind, UnaryOp, Use,
39};
40
41pub const MAX_WIDTH: usize = 80;
43
44const INDENT: usize = 2;
46
47pub fn format_unit(unit: &SourceUnit) -> String {
54 format_source("", unit)
55}
56
57pub fn format_expr(expr: &Expr) -> String {
63 Formatter::new("").flat(expr, 0)
64}
65
66pub fn format_source(source: &str, unit: &SourceUnit) -> String {
72 let mut formatter = Formatter::new(source);
73 formatter.source_unit(unit);
74 formatter.finish()
75}
76
77fn width(text: &str) -> usize {
79 text.chars().count()
80}
81
82#[derive(Clone, Debug)]
91struct Comment {
92 start: usize,
93 end: usize,
94 own_line: bool,
98 text: String,
100 column: usize,
103}
104
105fn scan_comments(source: &str) -> Vec<Comment> {
107 let bytes = source.as_bytes();
108 let mut comments = Vec::new();
109 let mut i = 0;
110 while i < bytes.len() {
111 match bytes[i] {
112 b'"' => i = skip_string(bytes, i),
113 b'/' if bytes.get(i + 1) == Some(&b'/') => {
114 let is_doc = bytes.get(i + 2) == Some(&b'/') && bytes.get(i + 3) != Some(&b'/');
115 let end = line_end(bytes, i);
116 if !is_doc {
117 comments.push(comment_at(source, i, end));
118 }
119 i = end;
120 }
121 b'/' if bytes.get(i + 1) == Some(&b'*') => {
122 let end = skip_block_comment(bytes, i);
123 comments.push(comment_at(source, i, end));
124 i = end;
125 }
126 _ => i += 1,
127 }
128 }
129 comments
130}
131
132fn comment_at(source: &str, start: usize, end: usize) -> Comment {
133 let before = &source[..start];
134 let line = before.rsplit('\n').next().unwrap_or("");
135 let text = source[start..end]
136 .lines()
137 .map(str::trim_end)
138 .collect::<Vec<_>>()
139 .join("\n");
140 Comment {
141 start,
142 end,
143 own_line: line.trim().is_empty(),
144 text,
145 column: width(line),
146 }
147}
148
149fn line_end(bytes: &[u8], from: usize) -> usize {
150 let mut i = from;
151 while i < bytes.len() && bytes[i] != b'\n' {
152 i += 1;
153 }
154 i
155}
156
157fn skip_string(bytes: &[u8], start: usize) -> usize {
160 let mut i = start + 1;
161 while i < bytes.len() {
162 match bytes[i] {
163 b'\\' => i += 2,
164 b'"' => return i + 1,
165 b'{' => i = skip_interpolation(bytes, i + 1),
166 _ => i += 1,
167 }
168 }
169 i
170}
171
172fn skip_interpolation(bytes: &[u8], from: usize) -> usize {
173 let mut i = from;
174 while i < bytes.len() {
175 match bytes[i] {
176 b'}' => return i + 1,
177 b'{' => i = skip_interpolation(bytes, i + 1),
178 b'"' => i = skip_string(bytes, i),
179 _ => i += 1,
180 }
181 }
182 i
183}
184
185fn skip_block_comment(bytes: &[u8], start: usize) -> usize {
187 let mut i = start + 2;
188 let mut depth = 1u32;
189 while i < bytes.len() {
190 if bytes[i] == b'*' && bytes.get(i + 1) == Some(&b'/') {
191 i += 2;
192 depth -= 1;
193 if depth == 0 {
194 return i;
195 }
196 } else if bytes[i] == b'/' && bytes.get(i + 1) == Some(&b'*') {
197 i += 2;
198 depth += 1;
199 } else {
200 i += 1;
201 }
202 }
203 i
204}
205
206#[derive(Default)]
212struct Line {
213 text: String,
214 comment: Option<String>,
215}
216
217#[derive(Default)]
223struct Out {
224 lines: Vec<Line>,
225 current: String,
226 comment: Option<String>,
227 open: bool,
228 pending_blank: bool,
229}
230
231impl Out {
232 fn start_line(&mut self, indent: usize) {
238 let empty = self.current.trim().is_empty() && self.comment.is_none();
239 if self.open && !empty {
240 self.lines.push(Line {
241 text: std::mem::take(&mut self.current),
242 comment: self.comment.take(),
243 });
244 }
245 if self.pending_blank && !self.lines.is_empty() {
246 self.lines.push(Line::default());
247 }
248 self.pending_blank = false;
249 self.current = " ".repeat(indent);
250 self.open = true;
251 }
252
253 fn write(&mut self, text: &str) {
254 self.current.push_str(text);
255 }
256
257 fn col(&self) -> usize {
259 width(&self.current)
260 }
261
262 fn set_comment(&mut self, text: &str) {
267 if self.current.trim().is_empty() && self.comment.is_none() {
268 self.write(text);
269 return;
270 }
271 match &mut self.comment {
272 Some(existing) => {
273 existing.push(' ');
274 existing.push_str(text);
275 }
276 None => self.comment = Some(text.to_string()),
277 }
278 }
279
280 fn finish(mut self) -> String {
283 if self.open {
284 self.lines.push(Line {
285 text: self.current,
286 comment: self.comment,
287 });
288 }
289 while self
290 .lines
291 .last()
292 .is_some_and(|line| line.text.trim().is_empty() && line.comment.is_none())
293 {
294 self.lines.pop();
295 }
296
297 let mut out = String::new();
298 let mut i = 0;
299 while i < self.lines.len() {
300 if self.lines[i].comment.is_none() {
301 out.push_str(self.lines[i].text.trim_end());
302 out.push('\n');
303 i += 1;
304 continue;
305 }
306 let mut end = i;
307 let mut column = 0;
308 while end < self.lines.len() && self.lines[end].comment.is_some() {
309 column = column.max(width(self.lines[end].text.trim_end()));
310 end += 1;
311 }
312 for line in &self.lines[i..end] {
313 let text = line.text.trim_end();
314 out.push_str(text);
315 out.push_str(&" ".repeat(column - width(text) + 1));
316 out.push_str(line.comment.as_deref().unwrap_or(""));
317 out.push('\n');
318 }
319 i = end;
320 }
321 out
322 }
323}
324
325mod prec {
331 pub const RETURN: u8 = 0;
332 pub const ASSIGN: u8 = 1;
333 pub const OR: u8 = 2;
334 pub const AND: u8 = 3;
335 pub const COMPARISON: u8 = 4;
336 pub const RANGE: u8 = 5;
337 pub const ADDITIVE: u8 = 6;
338 pub const MULTIPLICATIVE: u8 = 7;
339 pub const UNARY: u8 = 8;
340 pub const POSTFIX: u8 = 9;
341 pub const PRIMARY: u8 = 10;
342}
343
344fn binary_prec(op: BinaryOp) -> u8 {
345 match op {
346 BinaryOp::Or => prec::OR,
347 BinaryOp::And => prec::AND,
348 BinaryOp::Eq
349 | BinaryOp::Ne
350 | BinaryOp::Lt
351 | BinaryOp::Le
352 | BinaryOp::Gt
353 | BinaryOp::Ge
354 | BinaryOp::Is => prec::COMPARISON,
355 BinaryOp::Add | BinaryOp::Sub => prec::ADDITIVE,
356 BinaryOp::Mul | BinaryOp::Div | BinaryOp::Rem => prec::MULTIPLICATIVE,
357 }
358}
359
360fn expr_prec(expr: &Expr) -> u8 {
361 match &expr.kind {
362 ExprKind::Return(_) | ExprKind::Break(_) => prec::RETURN,
363 ExprKind::Assign { .. } => prec::ASSIGN,
364 ExprKind::Binary { op, .. } => binary_prec(*op),
365 ExprKind::Range { .. } => prec::RANGE,
366 ExprKind::Unary { .. } | ExprKind::Await(_) => prec::UNARY,
367 ExprKind::Field { .. } | ExprKind::Call { .. } | ExprKind::Try(_) => prec::POSTFIX,
368 _ => prec::PRIMARY,
369 }
370}
371
372fn binary_symbol(op: BinaryOp) -> &'static str {
373 match op {
374 BinaryOp::Add => "+",
375 BinaryOp::Sub => "-",
376 BinaryOp::Mul => "*",
377 BinaryOp::Div => "/",
378 BinaryOp::Rem => "%",
379 BinaryOp::Eq => "==",
380 BinaryOp::Ne => "!=",
381 BinaryOp::Lt => "<",
382 BinaryOp::Le => "<=",
383 BinaryOp::Gt => ">",
384 BinaryOp::Ge => ">=",
385 BinaryOp::Is => "is",
386 BinaryOp::And => "&&",
387 BinaryOp::Or => "||",
388 }
389}
390
391fn unary_symbol(op: UnaryOp) -> &'static str {
392 match op {
393 UnaryOp::Not => "!",
394 UnaryOp::Neg => "-",
395 }
396}
397
398fn ends_with_brace(expr: &Expr) -> bool {
404 match &expr.kind {
405 ExprKind::Block(_)
406 | ExprKind::If { .. }
407 | ExprKind::Match { .. }
408 | ExprKind::For { .. }
409 | ExprKind::While { .. }
410 | ExprKind::Scope { .. }
411 | ExprKind::Lambda { .. } => true,
412 ExprKind::Call { trailing, .. } => trailing.is_some(),
413 ExprKind::Binary { rhs, .. } => ends_with_brace(rhs),
414 ExprKind::Assign { value, .. } => ends_with_brace(value),
415 ExprKind::Range { end, .. } => ends_with_brace(end),
416 ExprKind::Unary { operand, .. } => ends_with_brace(operand),
417 ExprKind::Await(operand) => ends_with_brace(operand),
418 ExprKind::Return(Some(value)) | ExprKind::Break(Some(value)) => ends_with_brace(value),
419 _ => false,
420 }
421}
422
423fn close_brace(end: u32) -> usize {
426 (end as usize).saturating_sub(1)
427}
428
429fn block_is_empty(block: &Block) -> bool {
430 block.statements.is_empty() && block.tail.is_none()
431}
432
433fn trailing_body(expr: &Expr) -> Option<&Block> {
436 match &expr.kind {
437 ExprKind::Lambda { body, .. } => Some(body),
438 _ => None,
439 }
440}
441
442struct Formatter<'a> {
447 source: &'a str,
448 comments: Vec<Comment>,
449 next: usize,
451 pos: usize,
454 out: Out,
455}
456
457impl<'a> Formatter<'a> {
458 fn new(source: &'a str) -> Self {
459 Formatter {
460 source,
461 comments: scan_comments(source),
462 next: 0,
463 pos: 0,
464 out: Out::default(),
465 }
466 }
467
468 fn finish(self) -> String {
469 self.out.finish()
470 }
471
472 fn text(&self, span: Span) -> Option<&'a str> {
475 self.source.get(span.start as usize..span.end as usize)
476 }
477
478 fn code_point_text(&self, span: Span) -> Option<&'a str> {
489 let text = self.text(span)?;
490 let mut characters = text.chars();
491 (characters.next() == Some('\'') && characters.next_back() == Some('\'')).then_some(text)
492 }
493
494 fn number_text(&self, span: Span) -> Option<&'a str> {
497 let text = self.text(span)?;
498 let first = text.chars().next()?;
499 if !first.is_ascii_digit() || text.chars().any(char::is_whitespace) {
500 return None;
501 }
502 Some(text)
503 }
504
505 fn string_text(&self, span: Span) -> Option<&'a str> {
509 let text = self.text(span)?;
510 if text.len() >= 2 && text.starts_with('"') && text.ends_with('"') {
511 Some(text)
512 } else {
513 None
514 }
515 }
516
517 fn blank_line_between(&self, from: usize, to: usize) -> bool {
519 if from >= to {
520 return false;
521 }
522 let Some(text) = self.source.get(from..to) else {
523 return false;
524 };
525 let lines: Vec<&str> = text.split('\n').collect();
526 lines.len() >= 3
527 && lines[1..lines.len() - 1]
528 .iter()
529 .any(|l| l.trim().is_empty())
530 }
531
532 fn comment_between(&self, from: u32, to: u32) -> bool {
534 self.comments[self.next..]
535 .iter()
536 .any(|c| c.start >= from as usize && c.start < to as usize)
537 }
538
539 fn holds_comment(&self, span: Span) -> bool {
545 self.comment_between(span.start, span.end)
546 }
547
548 fn advance(&mut self, to: u32) {
549 self.pos = self.pos.max(to as usize);
550 }
551
552 fn lead(&mut self, start: usize, indent: usize, allow_blank: bool) {
560 let mut allow = allow_blank;
561 while self.next < self.comments.len() && self.comments[self.next].start < start {
562 let comment = self.comments[self.next].clone();
563 if allow && self.blank_line_between(self.pos, comment.start) {
564 self.out.pending_blank = true;
565 }
566 allow = true;
567 self.out.start_line(indent);
568 self.write_comment(&comment, indent);
569 self.pos = self.pos.max(comment.end);
570 self.next += 1;
571 }
572 if allow && self.blank_line_between(self.pos, start) {
573 self.out.pending_blank = true;
574 }
575 }
576
577 fn lead_close(&mut self, start: usize, indent: usize) {
580 self.lead(start, indent, true);
581 self.out.pending_blank = false;
582 }
583
584 fn trail(&mut self, end: u32) {
587 let end = end as usize;
588 while self.next < self.comments.len() {
589 let comment = &self.comments[self.next];
590 if comment.own_line || comment.start < end || comment.text.contains('\n') {
591 return;
592 }
593 match self.source.get(end..comment.start) {
594 Some(between) if !between.contains('\n') => {}
595 _ => return,
596 }
597 let text = comment.text.clone();
598 let comment_end = comment.end;
599 self.out.set_comment(&text);
600 self.pos = self.pos.max(comment_end);
601 self.next += 1;
602 }
603 }
604
605 fn write_comment(&mut self, comment: &Comment, indent: usize) {
608 let mut lines = comment.text.split('\n');
609 if let Some(first) = lines.next() {
610 self.out.write(first);
611 }
612 let prefix = " ".repeat(comment.column);
613 for line in lines {
614 self.out.start_line(indent);
615 let rest = line
616 .strip_prefix(prefix.as_str())
617 .unwrap_or(line.trim_start());
618 self.out.write(rest);
619 }
620 }
621
622 fn source_unit(&mut self, unit: &SourceUnit) {
625 let first_item = unit
626 .items
627 .first()
628 .map(|item| item.span.start as usize)
629 .unwrap_or(usize::MAX);
630
631 for use_decl in &unit.uses {
632 let limit = (use_decl.span.start as usize).min(first_item);
633 self.lead(limit, 0, false);
634 self.out.start_line(0);
635 self.use_decl(use_decl);
636 self.advance(use_decl.span.end);
637 self.trail(use_decl.span.end);
638 }
639
640 for (i, item) in unit.items.iter().enumerate() {
641 if i > 0 || !unit.uses.is_empty() {
642 self.out.pending_blank = true;
643 }
644 self.lead(item.span.start as usize, 0, true);
645 self.item(item, 0);
646 self.advance(item.span.end);
647 self.trail(item.span.end);
648 }
649
650 self.lead(self.source.len(), 0, true);
651 }
652
653 fn use_decl(&mut self, use_decl: &Use) {
654 self.out.write("use ");
655 let path: Vec<&str> = use_decl.path.iter().map(|s| s.node.as_str()).collect();
656 self.out.write(&path.join("."));
657 }
658
659 fn item(&mut self, item: &Item, indent: usize) {
660 if let Some(doc) = &item.doc {
661 self.doc_comment(doc, indent);
662 }
663 self.out.start_line(indent);
664 if item.exported {
665 self.out.write("export ");
666 }
667 if item.is_opaque {
668 self.out.write("opaque ");
669 }
670 if item.is_test {
671 self.out.write("test ");
672 }
673 match &item.kind {
674 ItemKind::Fn(decl) => self.fn_decl(decl, indent),
675 ItemKind::Struct(decl) => self.struct_decl(decl, indent),
676 ItemKind::Enum(decl) => self.enum_decl(decl, indent),
677 ItemKind::Trait(decl) => self.trait_decl(decl, indent),
678 ItemKind::Impl(block) => self.impl_block(block, indent),
679 ItemKind::TypeAlias(alias) => self.type_alias(alias, indent),
680 }
681 }
682
683 fn doc_comment(&mut self, doc: &str, indent: usize) {
686 for line in doc.split('\n') {
687 self.out.start_line(indent);
688 if line.is_empty() {
689 self.out.write("///");
690 } else {
691 self.out.write("/// ");
692 self.out.write(line);
693 }
694 }
695 self.out.pending_blank = false;
696 }
697
698 fn generics(&mut self, generics: &[GenericParam]) {
700 if generics.is_empty() {
701 return;
702 }
703 let names: Vec<String> = generics.iter().map(GenericParam::to_string).collect();
704 self.out.write("<");
705 self.out.write(&names.join(", "));
706 self.out.write(">");
707 }
708
709 fn fn_decl(&mut self, decl: &FnDecl, indent: usize) {
712 if decl.is_async {
713 self.out.write("async ");
714 }
715 self.out.write("fn ");
716 self.out.write(&decl.name.node);
717 self.generics(&decl.generics);
718 self.param_list(
719 decl.receiver,
720 &decl.params,
721 indent,
722 decl.return_type.as_ref(),
723 );
724 if let Some(return_type) = &decl.return_type {
725 self.out.write(" -> ");
726 self.type_ref(return_type, indent);
727 }
728 self.out.write(" ");
729 self.block(&decl.body, indent);
730 }
731
732 fn param_list(
735 &mut self,
736 receiver: Option<Receiver>,
737 params: &[Param],
738 indent: usize,
739 return_type: Option<&Type>,
740 ) {
741 let mut entries: Vec<String> = Vec::new();
742 if let Some(receiver) = receiver {
743 entries.push(if receiver.is_var {
744 "var self".into()
745 } else {
746 "self".into()
747 });
748 }
749 entries.extend(params.iter().map(|p| self.param_flat(p)));
750
751 let flat = format!("({})", entries.join(", "));
752 let tail = match return_type {
753 Some(ty) => width(&ty.to_string()) + 4,
754 None => 0,
755 };
756 if self.out.col() + width(&flat) + tail + 2 <= MAX_WIDTH {
758 self.out.write(&flat);
759 return;
760 }
761 self.out.write("(");
762 for entry in &entries {
763 self.out.start_line(indent + INDENT);
764 self.out.write(entry);
765 self.out.write(",");
766 }
767 self.out.start_line(indent);
768 self.out.write(")");
769 }
770
771 fn param_flat(&self, param: &Param) -> String {
774 let mut out = String::new();
775 if param.is_var {
776 out.push_str("var ");
777 }
778 if !param.name.node.is_empty() {
779 out.push_str(¶m.name.node);
780 if param.ty.is_some() {
781 out.push_str(": ");
782 }
783 }
784 if let Some(ty) = ¶m.ty {
785 out.push_str(&ty.to_string());
786 }
787 if param.variadic {
788 out.push_str("...");
789 }
790 if let Some(default) = ¶m.default {
791 out.push_str(" = ");
792 out.push_str(&self.flat(default, prec::RETURN));
793 }
794 out
795 }
796
797 fn struct_decl(&mut self, decl: &StructDecl, indent: usize) {
798 self.out.write("struct ");
799 self.out.write(&decl.name.node);
800 self.generics(&decl.generics);
801 self.out.write(" ");
802 if decl.fields.is_empty() && !self.holds_comment(decl.span) {
803 self.out.write("{ }");
804 return;
805 }
806 self.out.write("{");
807 let inner = indent + INDENT;
808 for (i, field) in decl.fields.iter().enumerate() {
809 self.lead(field.span.start as usize, inner, i > 0);
810 self.field(field, inner);
811 self.advance(field.span.end);
812 self.trail(field.span.end);
813 }
814 self.lead_close(close_brace(decl.span.end), inner);
815 self.out.start_line(indent);
816 self.out.write("}");
817 }
818
819 fn field(&mut self, field: &Field, indent: usize) {
820 if let Some(doc) = &field.doc {
821 self.doc_comment(doc, indent);
822 }
823 self.out.start_line(indent);
824 self.out.write(&field.name.node);
825 self.out.write(": ");
826 self.type_ref(&field.ty, indent);
827 }
828
829 fn enum_decl(&mut self, decl: &EnumDecl, indent: usize) {
830 self.out.write("enum ");
831 self.out.write(&decl.name.node);
832 self.generics(&decl.generics);
833 self.out.write(" ");
834 if decl.cases.is_empty() && !self.holds_comment(decl.span) {
835 self.out.write("{ }");
836 return;
837 }
838 self.out.write("{");
839 let inner = indent + INDENT;
840 for (i, case) in decl.cases.iter().enumerate() {
841 self.lead(case.span.start as usize, inner, i > 0);
842 self.enum_case(case, inner);
843 self.advance(case.span.end);
844 self.trail(case.span.end);
845 }
846 self.lead_close(close_brace(decl.span.end), inner);
847 self.out.start_line(indent);
848 self.out.write("}");
849 }
850
851 fn enum_case(&mut self, case: &EnumCase, indent: usize) {
852 if let Some(doc) = &case.doc {
853 self.doc_comment(doc, indent);
854 }
855 self.out.start_line(indent);
856 self.out.write(&case.name.node);
857 if !case.payload.is_empty() {
858 let types: Vec<String> = case.payload.iter().map(Type::to_string).collect();
859 self.out.write("(");
860 self.out.write(&types.join(", "));
861 self.out.write(")");
862 }
863 }
864
865 fn trait_decl(&mut self, decl: &TraitDecl, indent: usize) {
867 self.out.write("trait ");
868 self.out.write(&decl.name.node);
869 self.out.write(" ");
870 if decl.methods.is_empty() && !self.holds_comment(decl.span) {
871 self.out.write("{ }");
872 return;
873 }
874 self.out.write("{");
875 let inner = indent + INDENT;
876 for (i, method) in decl.methods.iter().enumerate() {
877 if i > 0 {
878 self.out.pending_blank = true;
879 }
880 self.lead(method.span.start as usize, inner, i > 0);
881 self.trait_method(method, inner);
882 self.advance(method.span.end);
883 self.trail(method.span.end);
884 }
885 self.lead_close(close_brace(decl.span.end), inner);
886 self.out.start_line(indent);
887 self.out.write("}");
888 }
889
890 fn trait_method(&mut self, method: &TraitMethod, indent: usize) {
892 if let Some(doc) = &method.doc {
893 self.doc_comment(doc, indent);
894 }
895 self.out.start_line(indent);
896 if method.is_async {
897 self.out.write("async ");
898 }
899 self.out.write("fn ");
900 self.out.write(&method.name.node);
901 self.param_list(
902 method.receiver,
903 &method.params,
904 indent,
905 method.return_type.as_ref(),
906 );
907 if let Some(return_type) = &method.return_type {
908 self.out.write(" -> ");
909 self.type_ref(return_type, indent);
910 }
911 if let Some(default) = &method.default {
912 self.out.write(" ");
913 self.block(default, indent);
914 }
915 }
916
917 fn impl_block(&mut self, block: &ImplBlock, indent: usize) {
918 self.out.write("impl ");
919 if let Some(trait_name) = &block.trait_name {
920 self.out.write(&trait_name.node);
921 self.out.write(" for ");
922 }
923 self.out.write(&block.type_name.node);
924 self.generics(&block.generics);
925 self.out.write(" ");
926 if block.items.is_empty() && !self.holds_comment(block.span) {
927 self.out.write("{ }");
928 return;
929 }
930 self.out.write("{");
931 let inner = indent + INDENT;
932 for (i, item) in block.items.iter().enumerate() {
933 if i > 0 {
934 self.out.pending_blank = true;
935 }
936 self.lead(item.span.start as usize, inner, i > 0);
937 self.item(item, inner);
938 self.advance(item.span.end);
939 self.trail(item.span.end);
940 }
941 self.lead_close(close_brace(block.span.end), inner);
942 self.out.start_line(indent);
943 self.out.write("}");
944 }
945
946 fn type_alias(&mut self, alias: &TypeAlias, indent: usize) {
947 self.out.write("type ");
948 self.out.write(&alias.name.node);
949 self.generics(&alias.generics);
950 self.out.write(" = ");
951 self.type_ref(&alias.ty, indent);
952 }
953
954 fn type_ref(&mut self, ty: &Type, indent: usize) {
959 let flat = ty.to_string();
960 if self.out.col() + width(&flat) <= MAX_WIDTH {
961 self.out.write(&flat);
962 return;
963 }
964 let TypeKind::Fn {
965 is_async,
966 params,
967 return_type,
968 } = &ty.kind
969 else {
970 self.out.write(&flat);
971 return;
972 };
973 if *is_async {
974 self.out.write("async ");
975 }
976 self.out.write("fn(");
977 for param in params {
978 self.out.start_line(indent + INDENT);
979 self.out.write(¶m.to_string());
980 self.out.write(",");
981 }
982 self.out.start_line(indent);
983 self.out.write(")");
984 if let Some(return_type) = return_type {
985 self.out.write(" -> ");
986 self.type_ref(return_type, indent);
987 }
988 }
989
990 fn block(&mut self, block: &Block, indent: usize) {
994 if block_is_empty(block) && !self.holds_comment(block.span) {
995 self.out.write("{ }");
996 self.advance(block.span.end);
997 return;
998 }
999 self.out.write("{");
1000 self.advance(block.span.start + 1);
1001 let inner = indent + INDENT;
1002 let mut first = true;
1003 for stmt in &block.statements {
1004 self.lead(stmt.span.start as usize, inner, !first);
1005 self.stmt(stmt, inner);
1006 self.advance(stmt.span.end);
1007 self.trail(stmt.span.end);
1008 first = false;
1009 }
1010 if let Some(tail) = &block.tail {
1011 self.lead(tail.span.start as usize, inner, !first);
1012 self.out.start_line(inner);
1013 self.expr(tail, prec::RETURN, inner);
1014 self.advance(tail.span.end);
1015 self.trail(tail.span.end);
1016 }
1017 self.lead_close(close_brace(block.span.end), inner);
1018 self.out.start_line(indent);
1019 self.out.write("}");
1020 self.advance(block.span.end);
1021 }
1022
1023 fn stmt(&mut self, stmt: &Stmt, indent: usize) {
1024 match &stmt.kind {
1025 StmtKind::Item(item) => self.item(item, indent),
1026 StmtKind::Let {
1027 is_var,
1028 name,
1029 ty,
1030 value,
1031 } => {
1032 self.out.start_line(indent);
1033 self.out.write(if *is_var { "var " } else { "let " });
1034 self.out.write(&name.node);
1035 if let Some(ty) = ty {
1036 self.out.write(": ");
1037 self.type_ref(ty, indent);
1038 }
1039 self.out.write(" = ");
1040 self.expr(value, prec::RETURN, indent);
1041 }
1042 StmtKind::Expr(value) => {
1043 self.out.start_line(indent);
1044 self.expr(value, prec::RETURN, indent);
1045 }
1046 }
1047 }
1048}
1049
1050enum Post<'e> {
1056 Field(&'e str),
1057 Call {
1058 generics: &'e [Type],
1059 args: &'e [Arg],
1060 trailing: Option<&'e Expr>,
1061 },
1062 Try,
1063}
1064
1065fn flatten_postfix(expr: &Expr) -> (&Expr, Vec<Post<'_>>) {
1068 match &expr.kind {
1069 ExprKind::Field { base, name } => {
1070 let (base, mut ops) = flatten_postfix(base);
1071 ops.push(Post::Field(name.node.as_str()));
1072 (base, ops)
1073 }
1074 ExprKind::Call {
1075 callee,
1076 generics,
1077 args,
1078 trailing,
1079 } => {
1080 let (base, mut ops) = flatten_postfix(callee);
1081 ops.push(Post::Call {
1082 generics,
1083 args,
1084 trailing: trailing.as_deref(),
1085 });
1086 (base, ops)
1087 }
1088 ExprKind::Try(inner) => {
1089 let (base, mut ops) = flatten_postfix(inner);
1090 ops.push(Post::Try);
1091 (base, ops)
1092 }
1093 _ => (expr, Vec::new()),
1094 }
1095}
1096
1097fn chain_break_points(ops: &[Post<'_>]) -> Vec<usize> {
1100 let mut points = Vec::new();
1101 let mut seen_call = false;
1102 for (i, op) in ops.iter().enumerate() {
1103 match op {
1104 Post::Call { .. } => seen_call = true,
1105 Post::Field(_) if seen_call => points.push(i),
1106 _ => {}
1107 }
1108 }
1109 points
1110}
1111
1112fn hug_applies(closure: bool, last: &Arg, earlier: &[Arg]) -> bool {
1119 closure || (earlier.is_empty() && last.label.is_none() && !last.is_var && !last.spread)
1120}
1121
1122fn call_count(ops: &[Post<'_>]) -> usize {
1123 ops.iter()
1124 .filter(|op| matches!(op, Post::Call { .. }))
1125 .count()
1126}
1127
1128impl Formatter<'_> {
1133 fn breaks(&self, expr: &Expr) -> bool {
1140 if self.holds_comment(expr.span) {
1141 return true;
1142 }
1143 match &expr.kind {
1144 ExprKind::Int(_)
1145 | ExprKind::Float(_)
1146 | ExprKind::Bool(_)
1147 | ExprKind::Duration(_)
1148 | ExprKind::Str(_)
1149 | ExprKind::Unit
1150 | ExprKind::Ident(_) => false,
1151 ExprKind::ArrayLit(elements) => elements.iter().any(|e| self.breaks(e)),
1152 ExprKind::Field { base, .. } => self.breaks(base),
1153 ExprKind::Call {
1154 callee,
1155 args,
1156 trailing,
1157 ..
1158 } => {
1159 self.breaks(callee)
1160 || args.iter().any(|arg| self.breaks(&arg.value))
1161 || trailing.as_deref().is_some_and(|t| self.trailing_breaks(t))
1162 }
1163 ExprKind::Unary { operand, .. } => self.breaks(operand),
1164 ExprKind::Binary { lhs, rhs, .. } => self.breaks(lhs) || self.breaks(rhs),
1165 ExprKind::Assign { target, value, .. } => self.breaks(target) || self.breaks(value),
1166 ExprKind::Try(inner) | ExprKind::Await(inner) => self.breaks(inner),
1167 ExprKind::Block(block) => !block_is_empty(block),
1168 ExprKind::If { .. }
1169 | ExprKind::Match { .. }
1170 | ExprKind::For { .. }
1171 | ExprKind::While { .. }
1172 | ExprKind::Scope { .. } => true,
1173 ExprKind::Return(value) | ExprKind::Break(value) => {
1174 value.as_deref().is_some_and(|v| self.breaks(v))
1175 }
1176 ExprKind::Continue => false,
1177 ExprKind::Lambda { body, .. } => !block_is_empty(body),
1178 ExprKind::Range { start, end, .. } => self.breaks(start) || self.breaks(end),
1179 }
1180 }
1181
1182 fn trailing_breaks(&self, closure: &Expr) -> bool {
1186 let Some(body) = trailing_body(closure) else {
1187 return self.breaks(closure);
1188 };
1189 if self.holds_comment(body.span) || !body.statements.is_empty() {
1190 return true;
1191 }
1192 body.tail.as_deref().is_some_and(|tail| self.breaks(tail))
1193 }
1194
1195 fn expr(&mut self, expr: &Expr, min: u8, indent: usize) {
1198 if expr_prec(expr) < min {
1199 self.out.write("(");
1200 self.expr(expr, prec::RETURN, indent);
1201 self.out.write(")");
1202 return;
1203 }
1204 if !self.breaks(expr) {
1205 let flat = self.flat_inner(expr);
1206 if self.out.col() + width(&flat) <= MAX_WIDTH {
1207 self.out.write(&flat);
1208 return;
1209 }
1210 }
1211 self.expr_broken(expr, indent);
1212 }
1213
1214 fn header(&mut self, expr: &Expr, indent: usize) {
1218 if ends_with_brace(expr) {
1219 self.out.write("(");
1220 self.expr(expr, prec::RETURN, indent);
1221 self.out.write(")");
1222 } else {
1223 self.expr(expr, prec::RETURN, indent);
1224 }
1225 }
1226
1227 fn expr_broken(&mut self, expr: &Expr, indent: usize) {
1228 match &expr.kind {
1229 ExprKind::Call { .. } => self.call(expr, indent),
1230 ExprKind::ArrayLit(elements) => self.array_literal(elements, expr.span, indent),
1231 ExprKind::Field { base, name } => {
1232 self.expr(base, prec::POSTFIX, indent);
1233 self.out.write(".");
1234 self.out.write(&name.node);
1235 }
1236 ExprKind::Unary { op, operand } => {
1237 self.out.write(unary_symbol(*op));
1238 self.expr(operand, prec::UNARY, indent);
1239 }
1240 ExprKind::Binary { op, lhs, rhs } => self.binary(*op, lhs, rhs, indent),
1241 ExprKind::Assign { op, target, value } => {
1242 self.expr(target, prec::POSTFIX, indent);
1243 match op {
1244 Some(op) => {
1245 self.out.write(" ");
1246 self.out.write(binary_symbol(*op));
1247 self.out.write("= ");
1248 }
1249 None => self.out.write(" = "),
1250 }
1251 self.expr(value, prec::ASSIGN, indent);
1252 }
1253 ExprKind::Try(inner) => match &inner.kind {
1256 ExprKind::Await(awaited) => {
1257 self.out.write("await ");
1258 self.expr(awaited, prec::POSTFIX, indent);
1259 self.out.write("?");
1260 }
1261 _ => {
1262 self.expr(inner, prec::POSTFIX, indent);
1263 self.out.write("?");
1264 }
1265 },
1266 ExprKind::Await(inner) => {
1267 self.out.write("await ");
1268 self.expr(inner, prec::POSTFIX, indent);
1269 }
1270 ExprKind::Block(block) => self.block(block, indent),
1271 ExprKind::If { .. } => self.if_expr(expr, indent),
1272 ExprKind::Match { scrutinee, arms } => {
1273 self.match_expr(scrutinee, arms, expr.span, indent)
1274 }
1275 ExprKind::For {
1276 binding,
1277 iterable,
1278 body,
1279 } => {
1280 self.out.write("for ");
1281 self.out.write(&binding.node);
1282 self.out.write(" in ");
1283 self.header(iterable, indent);
1284 self.out.write(" ");
1285 self.block(body, indent);
1286 }
1287 ExprKind::While { condition, body } => {
1288 self.out.write("while ");
1289 self.header(condition, indent);
1290 self.out.write(" ");
1291 self.block(body, indent);
1292 }
1293 ExprKind::Scope { name, body } => {
1294 self.out.write("scope ");
1295 self.out.write(&name.node);
1296 self.out.write(" ");
1297 self.block(body, indent);
1298 }
1299 ExprKind::Return(value) => {
1300 self.out.write("return");
1301 if let Some(value) = value {
1302 self.out.write(" ");
1303 self.expr(value, prec::RETURN, indent);
1304 }
1305 }
1306 ExprKind::Break(value) => {
1307 self.out.write("break");
1308 if let Some(value) = value {
1309 self.out.write(" ");
1310 self.expr(value, prec::RETURN, indent);
1311 }
1312 }
1313 ExprKind::Continue => self.out.write("continue"),
1314 ExprKind::Lambda {
1315 is_async,
1316 params,
1317 body,
1318 } => self.lambda(*is_async, params, body, indent),
1319 ExprKind::Range {
1320 start,
1321 end,
1322 inclusive_end,
1323 } => {
1324 self.expr(start, prec::ADDITIVE, indent);
1325 self.out.write(if *inclusive_end { ".." } else { "..<" });
1326 self.expr(end, prec::ADDITIVE, indent);
1327 }
1328 _ => {
1329 let flat = self.flat_inner(expr);
1330 self.out.write(&flat);
1331 }
1332 }
1333 }
1334
1335 fn binary(&mut self, op: BinaryOp, lhs: &Expr, rhs: &Expr, indent: usize) {
1340 let level = binary_prec(op);
1341 self.expr(lhs, level, indent);
1342 self.out.write(" ");
1343 self.out.write(binary_symbol(op));
1344 if self.breaks(rhs) {
1345 self.out.write(" ");
1346 self.expr(rhs, level + 1, indent);
1347 return;
1348 }
1349 let flat = self.flat(rhs, level + 1);
1350 if self.out.col() + 1 + width(&flat) <= MAX_WIDTH {
1351 self.out.write(" ");
1352 self.out.write(&flat);
1353 } else {
1354 self.out.start_line(indent + INDENT);
1355 self.expr(rhs, level + 1, indent + INDENT);
1356 }
1357 }
1358
1359 fn if_expr(&mut self, expr: &Expr, indent: usize) {
1360 let ExprKind::If {
1361 condition,
1362 then_branch,
1363 else_branch,
1364 } = &expr.kind
1365 else {
1366 return;
1367 };
1368 self.out.write("if ");
1369 self.header(condition, indent);
1370 self.out.write(" ");
1371 self.block(then_branch, indent);
1372 let Some(else_branch) = else_branch else {
1373 return;
1374 };
1375 self.out.write(" else ");
1376 match &else_branch.kind {
1377 ExprKind::Block(block) => self.block(block, indent),
1378 ExprKind::If { .. } => self.if_expr(else_branch, indent),
1379 _ => self.expr(else_branch, prec::RETURN, indent),
1380 }
1381 }
1382
1383 fn match_expr(&mut self, scrutinee: &Expr, arms: &[MatchArm], span: Span, indent: usize) {
1384 self.out.write("match ");
1385 self.header(scrutinee, indent);
1386 self.out.write(" ");
1387 if arms.is_empty() && !self.holds_comment(span) {
1388 self.out.write("{ }");
1389 self.advance(span.end);
1390 return;
1391 }
1392 self.out.write("{");
1393 let inner = indent + INDENT;
1394 for (i, arm) in arms.iter().enumerate() {
1395 self.lead(arm.span.start as usize, inner, i > 0);
1396 self.out.start_line(inner);
1397 self.out.write(&self.pattern_flat(&arm.pattern));
1398 self.out.write(" => ");
1399 self.expr(&arm.body, prec::RETURN, inner);
1400 self.advance(arm.span.end);
1401 self.trail(arm.span.end);
1402 }
1403 self.lead_close(close_brace(span.end), inner);
1404 self.out.start_line(indent);
1405 self.out.write("}");
1406 self.advance(span.end);
1407 }
1408
1409 fn lambda(&mut self, is_async: bool, params: &[Param], body: &Block, indent: usize) {
1413 if is_async {
1414 self.out.write("async ");
1415 }
1416 self.out.write("fn");
1417 self.param_list(None, params, indent, None);
1418 self.out.write(" ");
1419 self.block(body, indent);
1420 }
1421
1422 fn lambda_header(&self, is_async: bool, params: &[Param]) -> String {
1425 let mut head = String::new();
1426 if is_async {
1427 head.push_str("async ");
1428 }
1429 head.push_str("fn(");
1430 let entries: Vec<String> = params.iter().map(|p| self.param_flat(p)).collect();
1431 head.push_str(&entries.join(", "));
1432 head.push_str(") {");
1433 head
1434 }
1435
1436 fn call(&mut self, expr: &Expr, indent: usize) {
1437 let ExprKind::Call {
1438 callee,
1439 generics,
1440 args,
1441 trailing,
1442 } = &expr.kind
1443 else {
1444 return;
1445 };
1446
1447 if !self.breaks(expr) {
1449 let (base, ops) = flatten_postfix(expr);
1450 let points = chain_break_points(&ops);
1451 if call_count(&ops) >= 2 && !points.is_empty() {
1452 self.chain(base, &ops, &points, expr.span, indent);
1453 return;
1454 }
1455 }
1456
1457 self.expr(callee, prec::POSTFIX, indent);
1458 self.generic_args(generics);
1459 self.call_tail(args, trailing.as_deref(), generics, expr.span, indent);
1460 }
1461
1462 fn call_tail(
1464 &mut self,
1465 args: &[Arg],
1466 trailing: Option<&Expr>,
1467 generics: &[Type],
1468 span: Span,
1469 indent: usize,
1470 ) {
1471 let parens = !(args.is_empty() && trailing.is_some() && generics.is_empty());
1475 if parens {
1476 self.arg_list(args, trailing.is_some(), span, indent);
1477 }
1478 if let Some(trailing) = trailing {
1479 self.out.write(" ");
1480 match trailing_body(trailing) {
1481 Some(body) => self.block(body, indent),
1482 None => self.expr(trailing, prec::RETURN, indent),
1483 }
1484 }
1485 }
1486
1487 fn arg_list(&mut self, args: &[Arg], has_trailing: bool, span: Span, indent: usize) {
1488 let region_end = if has_trailing {
1491 args.last().map(|arg| arg.span.end)
1492 } else {
1493 Some(span.end)
1494 };
1495 let commented = region_end.is_some_and(|end| self.comment_between(span.start, end));
1496
1497 let flat = format!("({})", self.args_flat(args));
1498 let reserved = if has_trailing { 2 } else { 0 };
1500 if !commented
1501 && !args.iter().any(|arg| self.breaks(&arg.value))
1502 && self.out.col() + width(&flat) + reserved <= MAX_WIDTH
1503 {
1504 self.out.write(&flat);
1505 return;
1506 }
1507
1508 if let [only] = args {
1511 let value = self.flat(&only.value, prec::RETURN);
1512 if !commented
1513 && !self.breaks(&only.value)
1514 && only.label.is_none()
1515 && !only.is_var
1516 && !only.spread
1517 && indent + INDENT + width(&value) + 1 > MAX_WIDTH
1518 {
1519 self.out.write(&flat);
1520 return;
1521 }
1522 }
1523
1524 if !has_trailing && self.hug_last(args, span, indent) {
1528 return;
1529 }
1530
1531 self.out.write("(");
1532 for (i, arg) in args.iter().enumerate() {
1533 self.lead(arg.span.start as usize, indent + INDENT, i > 0);
1534 self.out.start_line(indent + INDENT);
1535 self.arg_prefix(arg);
1536 self.expr(&arg.value, prec::RETURN, indent + INDENT);
1537 self.out.write(",");
1538 self.advance(arg.span.end);
1539 self.trail(arg.span.end);
1540 }
1541 if !has_trailing {
1542 self.lead_close(close_brace(span.end), indent + INDENT);
1543 }
1544 self.out.start_line(indent);
1545 self.out.write(")");
1546 }
1547
1548 fn hug_head(&self, expr: &Expr) -> Option<(String, bool)> {
1554 match &expr.kind {
1555 ExprKind::Lambda {
1556 is_async, params, ..
1557 } => Some((self.lambda_header(*is_async, params), true)),
1558 ExprKind::ArrayLit(elements) if !elements.is_empty() => Some(("[".to_string(), false)),
1559 ExprKind::Call {
1560 callee,
1561 generics,
1562 args,
1563 trailing,
1564 } if trailing.is_none() && !args.is_empty() => {
1565 let mut head = self.flat(callee, prec::POSTFIX);
1566 if !generics.is_empty() {
1567 let names: Vec<String> = generics.iter().map(Type::to_string).collect();
1568 head.push('<');
1569 head.push_str(&names.join(", "));
1570 head.push('>');
1571 }
1572 head.push('(');
1573 let mut closure = false;
1574 if let Some((last, earlier)) = args.split_last() {
1575 if let Some((inner, inner_closure)) = self.hug_head(&last.value) {
1576 if hug_applies(inner_closure, last, earlier)
1577 && !earlier.iter().any(|arg| self.breaks(&arg.value))
1578 {
1579 for arg in earlier {
1580 head.push_str(&self.arg_flat(arg));
1581 head.push_str(", ");
1582 }
1583 head.push_str(&self.arg_prefix_text(last));
1584 head.push_str(&inner);
1585 closure = inner_closure;
1586 }
1587 }
1588 }
1589 Some((head, closure))
1590 }
1591 _ => None,
1592 }
1593 }
1594
1595 fn hug_last(&mut self, args: &[Arg], span: Span, indent: usize) -> bool {
1598 let Some((last, earlier)) = args.split_last() else {
1599 return false;
1600 };
1601 if earlier.iter().any(|arg| self.breaks(&arg.value))
1602 || self.comment_between(span.start, last.span.start)
1603 {
1604 return false;
1605 }
1606 let Some((inner_head, closure)) = self.hug_head(&last.value) else {
1607 return false;
1608 };
1609 if !hug_applies(closure, last, earlier) {
1610 return false;
1611 }
1612
1613 let mut prefix = String::from("(");
1614 for arg in earlier {
1615 prefix.push_str(&self.arg_flat(arg));
1616 prefix.push_str(", ");
1617 }
1618 prefix.push_str(&self.arg_prefix_text(last));
1619 let column = self.out.col() + width(&prefix);
1620 if column + width(&inner_head) > MAX_WIDTH {
1621 return false;
1622 }
1623 if !self.breaks(&last.value)
1627 && column + width(&self.flat(&last.value, prec::RETURN)) <= MAX_WIDTH
1628 {
1629 return false;
1630 }
1631
1632 self.out.write(&prefix);
1633 self.expr(&last.value, prec::RETURN, indent);
1634 self.advance(last.span.end);
1635 self.out.write(")");
1636 true
1637 }
1638
1639 fn array_literal(&mut self, elements: &[Expr], span: Span, indent: usize) {
1640 self.out.write("[");
1641 for element in elements {
1642 self.lead(element.span.start as usize, indent + INDENT, false);
1643 self.out.start_line(indent + INDENT);
1644 self.expr(element, prec::RETURN, indent + INDENT);
1645 self.out.write(",");
1646 self.advance(element.span.end);
1647 self.trail(element.span.end);
1648 }
1649 self.lead_close(close_brace(span.end), indent + INDENT);
1650 self.out.start_line(indent);
1651 self.out.write("]");
1652 self.advance(span.end);
1653 }
1654
1655 fn chain(
1657 &mut self,
1658 base: &Expr,
1659 ops: &[Post<'_>],
1660 points: &[usize],
1661 span: Span,
1662 indent: usize,
1663 ) {
1664 self.expr(base, prec::POSTFIX, indent);
1665 let mut level = indent;
1666 for (i, op) in ops.iter().enumerate() {
1667 match op {
1668 Post::Field(name) => {
1669 if points.contains(&i) {
1670 level = indent + INDENT;
1671 self.out.start_line(level);
1672 }
1673 self.out.write(".");
1674 self.out.write(name);
1675 }
1676 Post::Call {
1677 generics,
1678 args,
1679 trailing,
1680 } => {
1681 self.generic_args(generics);
1682 self.call_tail(args, *trailing, generics, span, level);
1683 }
1684 Post::Try => self.out.write("?"),
1685 }
1686 }
1687 }
1688
1689 fn generic_args(&mut self, generics: &[Type]) {
1690 if generics.is_empty() {
1691 return;
1692 }
1693 let names: Vec<String> = generics.iter().map(Type::to_string).collect();
1694 self.out.write("<");
1695 self.out.write(&names.join(", "));
1696 self.out.write(">");
1697 }
1698
1699 fn arg_prefix(&mut self, arg: &Arg) {
1700 let prefix = self.arg_prefix_text(arg);
1701 self.out.write(&prefix);
1702 }
1703
1704 fn arg_prefix_text(&self, arg: &Arg) -> String {
1705 let mut out = String::new();
1706 if let Some(label) = &arg.label {
1707 out.push_str(&label.node);
1708 out.push_str(": ");
1709 }
1710 if arg.is_var {
1711 out.push_str("var ");
1712 }
1713 if arg.spread {
1714 out.push_str("...");
1715 }
1716 out
1717 }
1718}
1719
1720fn duration_text(ns: i64) -> String {
1729 if ns == 0 {
1730 return "0ns".to_string();
1731 }
1732 for (factor, unit) in [
1733 (3_600_000_000_000i64, "h"),
1734 (60_000_000_000, "m"),
1735 (1_000_000_000, "s"),
1736 (1_000_000, "ms"),
1737 (1_000, "us"),
1738 (1, "ns"),
1739 ] {
1740 if ns % factor == 0 {
1741 return format!("{}{unit}", ns / factor);
1742 }
1743 }
1744 format!("{ns}ns")
1745}
1746
1747impl Formatter<'_> {
1748 fn flat(&self, expr: &Expr, min: u8) -> String {
1751 let inner = self.flat_inner(expr);
1752 if expr_prec(expr) < min {
1753 format!("({inner})")
1754 } else {
1755 inner
1756 }
1757 }
1758
1759 fn flat_header(&self, expr: &Expr) -> String {
1760 if ends_with_brace(expr) {
1761 format!("({})", self.flat_inner(expr))
1762 } else {
1763 self.flat_inner(expr)
1764 }
1765 }
1766
1767 fn flat_inner(&self, expr: &Expr) -> String {
1768 match &expr.kind {
1769 ExprKind::Int(value) => self
1770 .number_text(expr.span)
1771 .or_else(|| self.code_point_text(expr.span))
1772 .map(str::to_string)
1773 .unwrap_or_else(|| value.to_string()),
1774 ExprKind::Float(value) => self
1775 .number_text(expr.span)
1776 .map(str::to_string)
1777 .unwrap_or_else(|| format!("{value:?}")),
1778 ExprKind::Duration(ns) => self
1779 .number_text(expr.span)
1780 .map(str::to_string)
1781 .unwrap_or_else(|| duration_text(*ns)),
1782 ExprKind::Bool(value) => value.to_string(),
1783 ExprKind::Str(parts) => self
1784 .string_text(expr.span)
1785 .map(str::to_string)
1786 .unwrap_or_else(|| self.string_from_parts(parts)),
1787 ExprKind::Unit => "()".to_string(),
1788 ExprKind::Ident(name) => name.clone(),
1789 ExprKind::ArrayLit(elements) => {
1790 let items: Vec<String> = elements
1791 .iter()
1792 .map(|e| self.flat(e, prec::RETURN))
1793 .collect();
1794 format!("[{}]", items.join(", "))
1795 }
1796 ExprKind::Field { base, name } => {
1797 format!("{}.{}", self.flat(base, prec::POSTFIX), name.node)
1798 }
1799 ExprKind::Call {
1800 callee,
1801 generics,
1802 args,
1803 trailing,
1804 } => {
1805 let mut out = self.flat(callee, prec::POSTFIX);
1806 if !generics.is_empty() {
1807 let names: Vec<String> = generics.iter().map(Type::to_string).collect();
1808 out.push('<');
1809 out.push_str(&names.join(", "));
1810 out.push('>');
1811 }
1812 if !(args.is_empty() && trailing.is_some() && generics.is_empty()) {
1813 out.push('(');
1814 out.push_str(&self.args_flat(args));
1815 out.push(')');
1816 }
1817 if let Some(trailing) = trailing {
1818 out.push(' ');
1819 match trailing_body(trailing) {
1820 Some(body) => out.push_str(&self.flat_block(body)),
1821 None => out.push_str(&self.flat(trailing, prec::RETURN)),
1822 }
1823 }
1824 out
1825 }
1826 ExprKind::Unary { op, operand } => {
1827 format!("{}{}", unary_symbol(*op), self.flat(operand, prec::UNARY))
1828 }
1829 ExprKind::Binary { op, lhs, rhs } => {
1830 let level = binary_prec(*op);
1831 format!(
1832 "{} {} {}",
1833 self.flat(lhs, level),
1834 binary_symbol(*op),
1835 self.flat(rhs, level + 1)
1836 )
1837 }
1838 ExprKind::Assign { op, target, value } => {
1839 let operator = match op {
1840 Some(op) => format!("{}=", binary_symbol(*op)),
1841 None => "=".to_string(),
1842 };
1843 format!(
1844 "{} {operator} {}",
1845 self.flat(target, prec::POSTFIX),
1846 self.flat(value, prec::ASSIGN)
1847 )
1848 }
1849 ExprKind::Try(inner) => match &inner.kind {
1850 ExprKind::Await(awaited) => {
1851 format!("await {}?", self.flat(awaited, prec::POSTFIX))
1852 }
1853 _ => format!("{}?", self.flat(inner, prec::POSTFIX)),
1854 },
1855 ExprKind::Await(inner) => format!("await {}", self.flat(inner, prec::POSTFIX)),
1856 ExprKind::Block(block) => self.flat_block(block),
1857 ExprKind::If {
1858 condition,
1859 then_branch,
1860 else_branch,
1861 } => {
1862 let mut out = format!(
1863 "if {} {}",
1864 self.flat_header(condition),
1865 self.flat_block(then_branch)
1866 );
1867 if let Some(else_branch) = else_branch {
1868 out.push_str(" else ");
1869 out.push_str(&self.flat(else_branch, prec::RETURN));
1870 }
1871 out
1872 }
1873 ExprKind::Match { scrutinee, arms } => {
1874 let arms: Vec<String> = arms
1877 .iter()
1878 .map(|arm| {
1879 format!(
1880 "{} => {}",
1881 self.pattern_flat(&arm.pattern),
1882 self.flat(&arm.body, prec::RETURN)
1883 )
1884 })
1885 .collect();
1886 if arms.is_empty() {
1887 format!("match {} {{ }}", self.flat_header(scrutinee))
1888 } else {
1889 format!(
1890 "match {} {{ {} }}",
1891 self.flat_header(scrutinee),
1892 arms.join(", ")
1893 )
1894 }
1895 }
1896 ExprKind::For {
1897 binding,
1898 iterable,
1899 body,
1900 } => format!(
1901 "for {} in {} {}",
1902 binding.node,
1903 self.flat_header(iterable),
1904 self.flat_block(body)
1905 ),
1906 ExprKind::While { condition, body } => format!(
1907 "while {} {}",
1908 self.flat_header(condition),
1909 self.flat_block(body)
1910 ),
1911 ExprKind::Scope { name, body } => {
1912 format!("scope {} {}", name.node, self.flat_block(body))
1913 }
1914 ExprKind::Return(value) => match value {
1915 Some(value) => format!("return {}", self.flat(value, prec::RETURN)),
1916 None => "return".to_string(),
1917 },
1918 ExprKind::Break(value) => match value {
1919 Some(value) => format!("break {}", self.flat(value, prec::RETURN)),
1920 None => "break".to_string(),
1921 },
1922 ExprKind::Continue => "continue".to_string(),
1923 ExprKind::Lambda {
1924 is_async,
1925 params,
1926 body,
1927 } => {
1928 let mut out = String::new();
1929 if *is_async {
1930 out.push_str("async ");
1931 }
1932 out.push_str("fn(");
1933 let entries: Vec<String> = params.iter().map(|p| self.param_flat(p)).collect();
1934 out.push_str(&entries.join(", "));
1935 out.push_str(") ");
1936 out.push_str(&self.flat_block(body));
1937 out
1938 }
1939 ExprKind::Range {
1940 start,
1941 end,
1942 inclusive_end,
1943 } => format!(
1944 "{}{}{}",
1945 self.flat(start, prec::ADDITIVE),
1946 if *inclusive_end { ".." } else { "..<" },
1947 self.flat(end, prec::ADDITIVE)
1948 ),
1949 }
1950 }
1951
1952 fn args_flat(&self, args: &[Arg]) -> String {
1953 let entries: Vec<String> = args.iter().map(|arg| self.arg_flat(arg)).collect();
1954 entries.join(", ")
1955 }
1956
1957 fn arg_flat(&self, arg: &Arg) -> String {
1958 format!(
1959 "{}{}",
1960 self.arg_prefix_text(arg),
1961 self.flat(&arg.value, prec::RETURN)
1962 )
1963 }
1964
1965 fn flat_block(&self, block: &Block) -> String {
1966 if block_is_empty(block) {
1967 return "{ }".to_string();
1968 }
1969 let mut parts: Vec<String> = block
1970 .statements
1971 .iter()
1972 .map(|stmt| self.stmt_flat(stmt))
1973 .collect();
1974 if let Some(tail) = &block.tail {
1975 parts.push(self.flat(tail, prec::RETURN));
1976 }
1977 format!("{{ {} }}", parts.join(" "))
1978 }
1979
1980 fn stmt_flat(&self, stmt: &Stmt) -> String {
1981 match &stmt.kind {
1982 StmtKind::Expr(value) => self.flat(value, prec::RETURN),
1983 StmtKind::Let {
1984 is_var,
1985 name,
1986 ty,
1987 value,
1988 } => {
1989 let mut out = String::from(if *is_var { "var " } else { "let " });
1990 out.push_str(&name.node);
1991 if let Some(ty) = ty {
1992 out.push_str(": ");
1993 out.push_str(&ty.to_string());
1994 }
1995 out.push_str(" = ");
1996 out.push_str(&self.flat(value, prec::RETURN));
1997 out
1998 }
1999 StmtKind::Item(item) => {
2000 let mut sub = Formatter::new("");
2001 sub.item(item, 0);
2002 sub.finish()
2003 .lines()
2004 .map(str::trim)
2005 .collect::<Vec<_>>()
2006 .join(" ")
2007 }
2008 }
2009 }
2010
2011 fn string_from_parts(&self, parts: &[StrPart]) -> String {
2014 let mut out = String::from("\"");
2015 for part in parts {
2016 match part {
2017 StrPart::Text(text) => {
2018 for c in text.chars() {
2019 match c {
2020 '\\' => out.push_str("\\\\"),
2021 '"' => out.push_str("\\\""),
2022 '\n' => out.push_str("\\n"),
2023 '\t' => out.push_str("\\t"),
2024 '\r' => out.push_str("\\r"),
2025 '\0' => out.push_str("\\0"),
2026 '{' => out.push_str("\\{"),
2027 '}' => out.push_str("\\}"),
2028 c => out.push(c),
2029 }
2030 }
2031 }
2032 StrPart::Interpolation(value) => {
2033 out.push('{');
2034 out.push_str(&self.flat(value, prec::RETURN));
2035 out.push('}');
2036 }
2037 }
2038 }
2039 out.push('"');
2040 out
2041 }
2042
2043 fn pattern_flat(&self, pattern: &Pattern) -> String {
2044 match &pattern.kind {
2045 PatternKind::Wildcard => "_".to_string(),
2046 PatternKind::Binding(name) => name.clone(),
2047 PatternKind::Literal(value) => self.flat(value, prec::RETURN),
2048 PatternKind::Variant { path, payload } => {
2049 let path: Vec<&str> = path.iter().map(|p| p.node.as_str()).collect();
2050 let mut out = path.join(".");
2051 if !payload.is_empty() {
2052 let items: Vec<String> = payload.iter().map(|p| self.pattern_flat(p)).collect();
2053 out.push('(');
2054 out.push_str(&items.join(", "));
2055 out.push(')');
2056 }
2057 out
2058 }
2059 }
2060 }
2061}
2062
2063#[cfg(test)]
2064mod tests {
2065 use super::*;
2066 use cove_diag::SourceMap;
2067 use std::path::{Path, PathBuf};
2068
2069 fn src(text: &str) -> String {
2072 text.strip_prefix('\n').unwrap_or(text).to_string()
2073 }
2074
2075 fn parse(source: &str) -> SourceUnit {
2076 let mut sources = SourceMap::new();
2077 let file = sources.add("test.cove", source.to_string());
2078 match crate::parse_file(&sources, file) {
2079 Ok(unit) => unit,
2080 Err(diagnostics) => {
2081 let rendered: Vec<String> = diagnostics
2082 .iter()
2083 .map(|d| cove_diag::render(&sources, d))
2084 .collect();
2085 panic!("source does not parse:\n{}", rendered.join(""));
2086 }
2087 }
2088 }
2089
2090 fn format(source: &str) -> String {
2091 format_source(source, &parse(source))
2092 }
2093
2094 fn formatted(source: &str) {
2097 let source = src(source);
2098 assert_eq!(format(&source), source, "\n--- source was:\n{source}");
2099 }
2100
2101 fn reformats(source: &str, expected: &str) {
2104 let source = src(source);
2105 let expected = src(expected);
2106 assert_eq!(format(&source), expected, "\n--- source was:\n{source}");
2107 assert_eq!(format(&expected), expected, "formatting is not idempotent");
2108 }
2109
2110 fn without_spans(unit: &SourceUnit) -> String {
2113 let text = format!("{unit:?}");
2114 let mut out = String::new();
2115 let mut rest = text.as_str();
2116 while let Some(start) = rest.find("Span {") {
2117 out.push_str(&rest[..start]);
2118 out.push_str("Span");
2119 let close = rest[start..]
2120 .find('}')
2121 .expect("a Span renders as one set of braces");
2122 rest = &rest[start + close + 1..];
2123 }
2124 out.push_str(rest);
2125 out
2126 }
2127
2128 #[test]
2131 fn keeps_uses_in_source_order_above_one_blank_line() {
2132 reformats(
2133 "
2134use console
2135
2136use console.println
2137use http
2138
2139
2140/// A function.
2141fn a() { }
2142",
2143 "
2144use console
2145use console.println
2146use http
2147
2148/// A function.
2149fn a() { }
2150",
2151 );
2152 }
2153
2154 #[test]
2155 fn separates_top_level_items_with_one_blank_line() {
2156 reformats(
2157 "
2158fn a() { }
2159fn b() { }
2160
2161
2162
2163fn c() { }
2164",
2165 "
2166fn a() { }
2167
2168fn b() { }
2169
2170fn c() { }
2171",
2172 );
2173 }
2174
2175 #[test]
2176 fn formats_function_declarations() {
2177 formatted(
2178 "
2179/// Documented.
2180export async fn run<T>(self, var count: Int, items: String...) -> Result<T, U> {
2181 count
2182}
2183",
2184 );
2185 }
2186
2187 #[test]
2188 fn formats_a_test_declaration() {
2189 formatted(
2190 "
2191/// Greeting names the person it greets.
2192test fn greetsByName() -> Result<Unit, Error> {
2193 assert(greet(\"Ada\") == \"Hello, Ada!\")?
2194 Ok(())
2195}
2196",
2197 );
2198 }
2199
2200 #[test]
2201 fn formats_a_mutating_receiver_and_a_default_parameter() {
2202 formatted(
2203 "
2204impl Counter {
2205 /// Bumps.
2206 fn bump(var self, by: Int = 2 * 21) -> Int {
2207 self.hits
2208 }
2209}
2210",
2211 );
2212 }
2213
2214 #[test]
2215 fn formats_a_trait_with_docs_defaults_and_an_associated_function() {
2216 formatted(
2217 "
2218/// A value that can render itself for a human.
2219export trait Display {
2220 /// Returns the human-readable form.
2221 fn describe(self) -> String
2222
2223 /// Returns a short label.
2224 fn label(self) -> String {
2225 self.describe()
2226 }
2227
2228 /// Builds one from nothing.
2229 fn empty() -> Int
2230}
2231",
2232 );
2233 }
2234
2235 #[test]
2236 fn writes_an_empty_trait_on_one_line() {
2237 reformats(
2238 "
2239trait Marker {
2240}
2241",
2242 "
2243trait Marker { }
2244",
2245 );
2246 }
2247
2248 #[test]
2249 fn formats_a_conformance_and_keeps_it_apart_from_an_inherent_impl() {
2250 formatted(
2251 "
2252impl Display for Booking {
2253 fn describe(self) -> String {
2254 \"booking\"
2255 }
2256}
2257
2258impl Booking {
2259 /// The identifier.
2260 fn id(self) -> Int {
2261 1
2262 }
2263}
2264",
2265 );
2266 }
2267
2268 #[test]
2269 fn formats_bounds_on_type_parameters() {
2270 formatted(
2271 "
2272fn render<T: Display, U, V: Display + Ordered>(value: T, other: V) -> String {
2273 value.describe()
2274}
2275",
2276 );
2277 }
2278
2279 #[test]
2280 fn formats_dyn_types() {
2281 formatted(
2282 "
2283fn renderAll(values: Array<dyn Display>, one: dyn Display) -> dyn Display {
2284 one
2285}
2286",
2287 );
2288 }
2289
2290 #[test]
2291 fn keeps_comments_inside_a_trait() {
2292 formatted(
2293 "
2294trait Display {
2295 // Required.
2296 fn describe(self) -> String
2297
2298 /// Defaulted.
2299 fn label(self) -> String {
2300 // Falls back.
2301 self.describe()
2302 }
2303}
2304",
2305 );
2306 }
2307
2308 #[test]
2309 fn writes_struct_fields_one_per_line() {
2310 reformats(
2311 "
2312export struct Point { x: Int, y: Int }
2313
2314struct Tag(name: String, weight: Int)
2315
2316struct Empty { }
2317",
2318 "
2319export struct Point {
2320 x: Int
2321 y: Int
2322}
2323
2324struct Tag {
2325 name: String
2326 weight: Int
2327}
2328
2329struct Empty { }
2330",
2331 );
2332 }
2333
2334 #[test]
2337 fn writes_the_opaque_modifier_after_export() {
2338 reformats(
2339 "
2340opaque export struct User { id: Int, name: String }
2341",
2342 "
2343export opaque struct User {
2344 id: Int
2345 name: String
2346}
2347",
2348 );
2349 }
2350
2351 #[test]
2352 fn writes_enum_cases_one_per_line_with_their_docs() {
2353 reformats(
2354 "
2355export enum Status { Pending, Active(Int), Failed(String, Int) }
2356",
2357 "
2358export enum Status {
2359 Pending
2360 Active(Int)
2361 Failed(String, Int)
2362}
2363",
2364 );
2365 }
2366
2367 #[test]
2368 fn formats_documented_fields_and_cases() {
2369 formatted(
2370 "
2371struct Config {
2372 /// The port.
2373 port: Int
2374}
2375
2376enum Level {
2377 /// The quiet one.
2378 Debug
2379}
2380",
2381 );
2382 }
2383
2384 #[test]
2385 fn formats_impl_blocks_and_type_aliases() {
2386 formatted(
2387 "
2388export type Handler = async fn(request: http.Request) -> Result<Unit, Error>
2389
2390impl Metrics<T> {
2391 /// One.
2392 fn one(self) -> Int {
2393 1
2394 }
2395
2396 /// Two.
2397 export fn two() -> Int {
2398 2
2399 }
2400}
2401
2402impl Empty { }
2403",
2404 );
2405 }
2406
2407 #[test]
2408 fn keeps_a_multi_line_doc_comment_attached() {
2409 formatted(
2410 "
2411/// One line.
2412///
2413/// Another line.
2414fn documented() { }
2415",
2416 );
2417 }
2418
2419 #[test]
2420 fn removes_a_blank_line_between_a_doc_comment_and_its_declaration() {
2421 reformats(
2422 "
2423/// Documented.
2424
2425fn documented() { }
2426",
2427 "
2428/// Documented.
2429fn documented() { }
2430",
2431 );
2432 }
2433
2434 #[test]
2437 fn formats_every_type_form() {
2438 formatted(
2439 "
2440fn types(
2441 a: Int,
2442 b: Array<String>,
2443 c: Map<String, Array<Int>>,
2444 d: http.Request,
2445 e: (),
2446 f: fn(String) -> Int,
2447 g: async fn(name: String, var items: Int...),
2448) { }
2449",
2450 );
2451 }
2452
2453 #[test]
2456 fn formats_statements() {
2457 formatted(
2458 "
2459fn statements() {
2460 let plain = 1
2461 let typed: Array<Int> = [1, 2]
2462 var mutable = 3
2463 mutable = 4
2464 mutable += 1
2465 mutable -= 1
2466 mutable *= 1
2467 mutable /= 1
2468 mutable %= 1
2469 mutable
2470}
2471",
2472 );
2473 }
2474
2475 #[test]
2476 fn keeps_one_blank_line_between_statements_and_none_at_a_block_edge() {
2477 reformats(
2478 "
2479fn spaced() {
2480
2481 let a = 1
2482
2483
2484
2485 let b = 2
2486
2487}
2488",
2489 "
2490fn spaced() {
2491 let a = 1
2492
2493 let b = 2
2494}
2495",
2496 );
2497 }
2498
2499 #[test]
2500 fn formats_a_nested_declaration_inside_a_block() {
2501 formatted(
2502 "
2503fn outer() {
2504 /// Inner.
2505 fn inner() -> Int {
2506 1
2507 }
2508 inner()
2509}
2510",
2511 );
2512 }
2513
2514 #[test]
2524 fn formats_a_code_point_literal_and_keeps_its_spelling() {
2525 formatted(
2526 "
2527fn codePoints() {
2528 let letters = ['a', 'Z', '0']
2529 let punctuation = ['{', '}', ',', '\"']
2530 let escapes = ['\\n', '\\t', '\\r', '\\0', '\\\\', '\\'']
2531 let space = ' '
2532 let multibyte = '\u{e9}'
2533 space
2534}
2535",
2536 );
2537 }
2538
2539 #[test]
2540 fn formats_literals_and_keeps_their_spelling() {
2541 formatted(
2542 "
2543fn literals() {
2544 let ints = [1_000_000, 0xff, 0b1010]
2545 let floats = [1.5, 1_000.5, 1.5e3, 2e-2]
2546 let durations = [1ns, 500ms, 60s, 1h]
2547 let bools = [true, false]
2548 let unit = ()
2549 let text = \"escapes \\\\ \\\" \\n and {ints} interpolation\"
2550 text
2551}
2552",
2553 );
2554 }
2555
2556 #[test]
2557 fn formats_is_at_the_same_precedence_as_comparison() {
2558 formatted(
2559 "
2560fn identity(a: Vector<Int>, b: Vector<Int>) {
2561 a is b && a == b
2562}
2563",
2564 );
2565 }
2566
2567 #[test]
2568 fn formats_calls_labels_spreads_generics_and_trailing_closures() {
2569 formatted(
2570 "
2571fn calls(var output: Vector<Int>) {
2572 plain(1, 2)
2573 labeled(low: 1, high: 2)
2574 fill(var output)
2575 joinAll(\"-\", ...ready)
2576 api.fetch<Array<Booking>>(\"/bookings\")
2577 tasks.spawn { fetch() }
2578 clock.timeout(500ms) { fetch() }
2579 Point(x: 1, y: 2)
2580}
2581",
2582 );
2583 }
2584
2585 #[test]
2586 fn formats_lambdas_with_and_without_parameters() {
2587 formatted(
2588 "
2589fn lambdas() {
2590 let one = fn(n) {
2591 n * 2
2592 }
2593 let none = async fn() {
2594 one
2595 }
2596 none
2597}
2598",
2599 );
2600 }
2601
2602 #[test]
2603 fn parenthesises_only_where_precedence_requires_it() {
2604 reformats(
2605 "
2606fn precedence() {
2607 let a = (1 + 2) * 3
2608 let b = 1 + (2 * 3)
2609 let c = -(1 + 2)
2610 let d = !(a && b)
2611 let e = (a + b)?
2612 let f = (a + b).field
2613 let g = a || b && c
2614 let h = (a || b) && c
2615 let i = (0..<3).isEmpty()
2616 let j = 1 - (2 - 3)
2617 j
2618}
2619",
2620 "
2621fn precedence() {
2622 let a = (1 + 2) * 3
2623 let b = 1 + 2 * 3
2624 let c = -(1 + 2)
2625 let d = !(a && b)
2626 let e = (a + b)?
2627 let f = (a + b).field
2628 let g = a || b && c
2629 let h = (a || b) && c
2630 let i = (0..<3).isEmpty()
2631 let j = 1 - (2 - 3)
2632 j
2633}
2634",
2635 );
2636 }
2637
2638 #[test]
2639 fn writes_await_before_the_question_mark_it_propagates() {
2640 formatted(
2641 "
2642async fn awaiting() {
2643 await task()?
2644 await task()?.field
2645 await task()
2646}
2647",
2648 );
2649 }
2650
2651 #[test]
2652 fn formats_control_flow_across_lines() {
2653 formatted(
2654 "
2655fn control(items: Array<Int>) -> Int {
2656 if items.isEmpty() {
2657 0
2658 } else if items.length() == 1 {
2659 1
2660 } else {
2661 2
2662 }
2663
2664 for item in items {
2665 item
2666 }
2667
2668 for index in 0..<3 {
2669 index
2670 }
2671
2672 while items.isEmpty() {
2673 items
2674 }
2675
2676 scope tasks {
2677 tasks
2678 }
2679
2680 {
2681 let inner = 1
2682 inner
2683 }
2684
2685 return 1
2686}
2687",
2688 );
2689 }
2690
2691 #[test]
2696 fn keeps_a_bare_break_or_return_on_its_own_line() {
2697 formatted(
2698 "
2699fn stops(items: Array<Int>) -> Int {
2700 var seen = 0
2701 while true {
2702 seen += 1
2703 break
2704 seen = 99
2705 }
2706
2707 for item in items {
2708 break item
2709 }
2710
2711 return
2712 seen = 99
2713}
2714",
2715 );
2716 }
2717
2718 #[test]
2719 fn writes_match_arms_one_per_line() {
2720 reformats(
2721 "
2722fn matching(value: Card) -> String {
2723 match value {
2724 -1 => \"minus one\",
2725 \"yes\" => \"literal\",
2726 true => \"flag\",
2727 Card.Blank => \"blank\",
2728 Card.Numbered(count) => { let doubled = count * 2
2729 \"{doubled}\" }
2730 other => other,
2731 _ => \"wildcard\",
2732 }
2733}
2734",
2735 "
2736fn matching(value: Card) -> String {
2737 match value {
2738 -1 => \"minus one\"
2739 \"yes\" => \"literal\"
2740 true => \"flag\"
2741 Card.Blank => \"blank\"
2742 Card.Numbered(count) => {
2743 let doubled = count * 2
2744 \"{doubled}\"
2745 }
2746 other => other
2747 _ => \"wildcard\"
2748 }
2749}
2750",
2751 );
2752 }
2753
2754 #[test]
2755 fn parenthesises_a_header_that_would_otherwise_end_in_a_brace() {
2756 let source = src("
2757fn headers() {
2758 if (tasks.spawn { ready() }) {
2759 1
2760 }
2761}
2762");
2763 assert_eq!(format(&source), source);
2764 assert_eq!(
2765 without_spans(&parse(&source)),
2766 without_spans(&parse(&format(&source)))
2767 );
2768 }
2769
2770 #[test]
2771 fn writes_an_empty_block_on_one_line() {
2772 formatted(
2773 "
2774fn empty() {
2775 let nothing = { }
2776 nothing
2777}
2778",
2779 );
2780 }
2781
2782 #[test]
2785 fn breaks_an_argument_list_one_per_line_with_a_trailing_comma() {
2786 reformats(
2787 "
2788fn wide() {
2789 configure(alphaValueHere, betaValueHere, gammaValueHere, deltaValueHere, epsilon)
2790}
2791",
2792 "
2793fn wide() {
2794 configure(
2795 alphaValueHere,
2796 betaValueHere,
2797 gammaValueHere,
2798 deltaValueHere,
2799 epsilon,
2800 )
2801}
2802",
2803 );
2804 }
2805
2806 #[test]
2807 fn breaks_a_parameter_list_one_per_line_when_the_signature_is_too_wide() {
2808 reformats(
2809 "
2810fn wideSignature(alphaValue: String, betaValue: String, gammaValue: String) -> Int {
2811 1
2812}
2813",
2814 "
2815fn wideSignature(
2816 alphaValue: String,
2817 betaValue: String,
2818 gammaValue: String,
2819) -> Int {
2820 1
2821}
2822",
2823 );
2824 }
2825
2826 #[test]
2827 fn breaks_an_array_literal_one_element_per_line() {
2828 reformats(
2829 "
2830fn wideArray() {
2831 let items = [alphaValueHere, betaValueHere, gammaValueHere, deltaValueHere, eps]
2832 items
2833}
2834",
2835 "
2836fn wideArray() {
2837 let items = [
2838 alphaValueHere,
2839 betaValueHere,
2840 gammaValueHere,
2841 deltaValueHere,
2842 eps,
2843 ]
2844 items
2845}
2846",
2847 );
2848 }
2849
2850 #[test]
2851 fn breaks_a_binary_expression_after_its_operator() {
2852 reformats(
2853 "
2854fn wideSum() {
2855 let total = alphaValueHere + betaValueHere + gammaValueHere + deltaValueHereOne
2856 total
2857}
2858",
2859 "
2860fn wideSum() {
2861 let total = alphaValueHere + betaValueHere + gammaValueHere +
2862 deltaValueHereOne
2863 total
2864}
2865",
2866 );
2867 }
2868
2869 #[test]
2870 fn breaks_a_method_chain_before_each_dot() {
2871 reformats(
2872 "
2873fn wideChain(reading: Reading) {
2874 reading.normalise().withPrecision(4).formattedAsText().withoutTrailingZeroesAt()
2875}
2876",
2877 "
2878fn wideChain(reading: Reading) {
2879 reading.normalise()
2880 .withPrecision(4)
2881 .formattedAsText()
2882 .withoutTrailingZeroesAt()
2883}
2884",
2885 );
2886 }
2887
2888 #[test]
2889 fn breaks_an_argument_list_above_a_trailing_closure() {
2890 reformats(
2891 "
2892fn wideTrailing() {
2893 clock.timeout(alphaValueHere, betaValueHere, gammaValueHere, deltaValueHereOk) { work() }
2894 1
2895}
2896",
2897 "
2898fn wideTrailing() {
2899 clock.timeout(
2900 alphaValueHere,
2901 betaValueHere,
2902 gammaValueHere,
2903 deltaValueHereOk,
2904 ) {
2905 work()
2906 }
2907 1
2908}
2909",
2910 );
2911 }
2912
2913 #[test]
2914 fn keeps_a_call_head_on_one_line_and_expands_a_closure_below_it() {
2915 formatted(
2916 "
2917fn callbacks(app: App) {
2918 builder.get(\"/health\", withObservability(app, async fn(request) {
2919 Ok(request)
2920 }))
2921}
2922",
2923 );
2924 }
2925
2926 #[test]
2927 fn expands_a_sole_initializer_argument_in_place() {
2928 formatted(
2929 "
2930fn initializers(builder: RouterBuilder, path: String, handler: Handler) {
2931 builder.routes.push(Route(
2932 method: http.Method.Get,
2933 path: path,
2934 handler: handler,
2935 ))
2936}
2937",
2938 );
2939 }
2940
2941 #[test]
2942 fn leaves_a_lone_unbreakable_argument_where_it_is() {
2943 formatted(
2945 "
2946fn unbreakable() {
2947 println(\"a string literal so long that no line break anywhere can ever help\")?
2948}
2949",
2950 );
2951 }
2952
2953 #[test]
2954 fn breaks_a_function_type_that_does_not_fit() {
2955 reformats(
2956 "
2957export type Handler = async fn(request: http.Request, retries: Int) -> Result<http.Response, Error>
2958",
2959 "
2960export type Handler = async fn(
2961 request: http.Request,
2962 retries: Int,
2963) -> Result<http.Response, Error>
2964",
2965 );
2966 }
2967
2968 #[test]
2969 fn every_break_it_introduces_still_parses_the_same_way() {
2970 let sources = [
2971 "fn a() {\n configure(alphaValueHere, betaValueHere, gammaValueHere, deltaValueHere, epsilon)\n}\n",
2972 "fn a() {\n let total = alphaValueHere + betaValueHere + gammaValueHere + deltaValueHereOne\n}\n",
2973 "fn a(reading: R) {\n reading.normalise().withPrecision(4).formattedAsText().withoutTrailingZeroesAt()\n}\n",
2974 "fn a() {\n let items = [alphaValueHere, betaValueHere, gammaValueHere, deltaValueHere, eps]\n}\n",
2975 "fn wideSignature(alphaValue: String, betaValue: String, gammaValue: String) -> Int {\n 1\n}\n",
2976 "fn a() {\n builder.get(\"/health\", withObservability(app, async fn(request) { Ok(1) }))\n}\n",
2977 "fn a() {\n clock.timeout(alphaValueHere, betaValueHere, gammaValueHere, deltaValue) { w() }\n 1\n}\n",
2978 ];
2979 for source in sources {
2980 let formatted = format(source);
2981 assert_ne!(formatted, source, "this case is meant to be reformatted");
2982 assert_eq!(
2983 without_spans(&parse(source)),
2984 without_spans(&parse(&formatted)),
2985 "reformatting changed the tree of:\n{source}\ninto:\n{formatted}"
2986 );
2987 }
2988 }
2989
2990 #[test]
2993 fn keeps_a_comment_on_its_own_line_above_what_follows_it() {
2994 formatted(
2995 "
2996// Above the use.
2997use console.println
2998
2999// Above the item.
3000fn commented() {
3001 // Above the statement.
3002 let a = 1
3003
3004 // After a blank line.
3005 a
3006}
3007",
3008 );
3009 }
3010
3011 #[test]
3012 fn keeps_a_comment_at_the_end_of_the_line_it_was_written_on() {
3013 formatted(
3014 "
3015fn trailing() {
3016 let a = 1 // one
3017 a
3018}
3019",
3020 );
3021 }
3022
3023 #[test]
3024 fn aligns_a_run_of_trailing_comments() {
3025 reformats(
3026 "
3027fn aligned() {
3028 let a = 1 // one
3029 let longerName = 2 // two
3030 let c = 3 // three
3031
3032 let d = 4 // alone
3033 d
3034}
3035",
3036 "
3037fn aligned() {
3038 let a = 1 // one
3039 let longerName = 2 // two
3040 let c = 3 // three
3041
3042 let d = 4 // alone
3043 d
3044}
3045",
3046 );
3047 }
3048
3049 #[test]
3050 fn keeps_a_comment_before_a_closing_brace() {
3051 formatted(
3052 "
3053fn closing() {
3054 let a = 1
3055 // The last word.
3056}
3057",
3058 );
3059 }
3060
3061 #[test]
3062 fn keeps_a_comment_at_the_end_of_the_file() {
3063 formatted(
3064 "
3065fn done() { }
3066
3067// A closing remark
3068// over two lines.
3069",
3070 );
3071 }
3072
3073 #[test]
3074 fn keeps_a_block_comment_and_re_indents_its_continuation_lines() {
3075 reformats(
3076 "
3077fn blocks() {
3078 /* one
3079 two */
3080 let a = 1 /* trailing */
3081 a
3082}
3083",
3084 "
3085fn blocks() {
3086 /* one
3087 two */
3088 let a = 1 /* trailing */
3089 a
3090}
3091",
3092 );
3093 }
3094
3095 #[test]
3096 fn keeps_a_comment_inside_an_argument_list_by_breaking_the_call() {
3097 formatted(
3098 "
3099fn inside() {
3100 configure(
3101 // Why alpha.
3102 alpha,
3103 beta, // and beta
3104 )
3105}
3106",
3107 );
3108 }
3109
3110 #[test]
3111 fn keeps_a_comment_between_enum_cases_and_struct_fields() {
3112 formatted(
3113 "
3114struct Point {
3115 // The horizontal one.
3116 x: Int
3117 y: Int // the vertical one
3118}
3119
3120enum Level {
3121 // The quiet one.
3122 Debug
3123 Info
3124}
3125",
3126 );
3127 }
3128
3129 #[test]
3130 fn never_drops_a_comment() {
3131 let source = src("
3132// one
3133use console.println // two
3134
3135// three
3136fn a(/* four */ x: Int) { // five
3137 // six
3138 let y = x // seven
3139 /* eight */
3140 y
3141 // nine
3142}
3143// ten
3144");
3145 let output = format(&source);
3146 for word in [
3147 "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
3148 ] {
3149 assert!(
3150 output.contains(word),
3151 "comment `{word}` was dropped:\n{output}"
3152 );
3153 }
3154 assert_eq!(format(&output), output, "formatting is not idempotent");
3155 }
3156
3157 fn repo_root() -> PathBuf {
3160 Path::new(env!("CARGO_MANIFEST_DIR"))
3161 .join("..")
3162 .join("..")
3163 .canonicalize()
3164 .expect("the workspace root exists")
3165 }
3166
3167 fn cove_files() -> Vec<PathBuf> {
3168 fn walk(dir: &Path, found: &mut Vec<PathBuf>) {
3169 let Ok(entries) = std::fs::read_dir(dir) else {
3170 return;
3171 };
3172 let mut paths: Vec<PathBuf> =
3173 entries.filter_map(|e| e.ok().map(|e| e.path())).collect();
3174 paths.sort();
3175 for path in paths {
3176 let name = path.file_name().unwrap_or_default().to_string_lossy();
3177 if name.starts_with('.') || name == "target" {
3178 continue;
3179 }
3180 if path.is_dir() {
3181 walk(&path, found);
3182 } else if path.extension().and_then(|e| e.to_str()) == Some("cove") {
3183 found.push(path);
3184 }
3185 }
3186 }
3187 let mut found = Vec::new();
3188 walk(&repo_root(), &mut found);
3189 assert!(!found.is_empty(), "the repository has `.cove` files");
3190
3191 let mut formattable = Vec::new();
3195 let mut unparsable = Vec::new();
3196 for path in found {
3197 let Ok(source) = std::fs::read_to_string(&path) else {
3198 continue;
3199 };
3200 let mut sources = SourceMap::new();
3201 let file = sources.add(path.clone(), source);
3202 if crate::parse_file(&sources, file).is_ok() {
3203 formattable.push(path);
3204 } else {
3205 unparsable.push(path);
3206 }
3207 }
3208 assert!(
3209 formattable.len() > unparsable.len() * 4,
3210 "most of the repository should parse; {} of {} did not",
3211 unparsable.len(),
3212 formattable.len() + unparsable.len()
3213 );
3214 formattable
3215 }
3216
3217 #[test]
3218 fn formatting_every_repository_file_twice_changes_nothing() {
3219 for path in cove_files() {
3220 let source = std::fs::read_to_string(&path).expect("the file is readable");
3221 let once = format(&source);
3222 let twice = format(&once);
3223 assert_eq!(once, twice, "`{}` is not a fixed point", path.display());
3224 }
3225 }
3226
3227 #[test]
3228 fn formatting_every_repository_file_preserves_its_tree() {
3229 for path in cove_files() {
3230 let source = std::fs::read_to_string(&path).expect("the file is readable");
3231 let formatted = format(&source);
3232 assert_eq!(
3233 without_spans(&parse(&source)),
3234 without_spans(&parse(&formatted)),
3235 "formatting `{}` changed its tree",
3236 path.display()
3237 );
3238 }
3239 }
3240
3241 #[test]
3242 fn every_repository_file_formats_to_clean_layout() {
3243 for path in cove_files() {
3244 let source = std::fs::read_to_string(&path).expect("the file is readable");
3245 let formatted = format(&source);
3246 assert!(
3247 formatted.ends_with('\n') && !formatted.ends_with("\n\n"),
3248 "`{}` does not end in exactly one newline",
3249 path.display()
3250 );
3251 for (i, line) in formatted.lines().enumerate() {
3252 assert!(
3253 !line.contains('\t'),
3254 "`{}` line {} contains a tab",
3255 path.display(),
3256 i + 1
3257 );
3258 assert_eq!(
3259 line.trim_end(),
3260 line,
3261 "`{}` line {} has trailing whitespace",
3262 path.display(),
3263 i + 1
3264 );
3265 }
3266 }
3267 }
3268
3269 #[test]
3270 fn formatting_every_repository_file_keeps_every_comment() {
3271 for path in cove_files() {
3272 let source = std::fs::read_to_string(&path).expect("the file is readable");
3273 let formatted = format(&source);
3274 let before = scan_comments(&source);
3275 let after = scan_comments(&formatted);
3276 assert_eq!(
3277 before.len(),
3278 after.len(),
3279 "formatting `{}` changed how many comments it has",
3280 path.display()
3281 );
3282 }
3283 }
3284
3285 #[test]
3286 fn format_unit_formats_a_tree_without_its_source() {
3287 let unit = parse("fn a() {\n let x = 0xff\n x\n}\n");
3288 assert_eq!(format_unit(&unit), "fn a() {\n let x = 255\n x\n}\n");
3289 }
3290}