1use crate::ast;
27
28struct FormattingSource<'a> {
32 text: &'a str,
34 line_starts: Vec<usize>,
36}
37
38impl<'a> FormattingSource<'a> {
39 fn new(text: &'a str) -> Self {
41 Self {
42 text,
43 line_starts: cel_parser::trivia::line_start_byte_offsets(text),
44 }
45 }
46}
47
48impl std::ops::Deref for FormattingSource<'_> {
49 type Target = str;
51
52 fn deref(&self) -> &str {
54 self.text
55 }
56}
57
58fn output_column(out: &str) -> usize {
62 out.rsplit('\n').next().unwrap_or_default().chars().count()
63}
64
65fn write_expression(
70 out: &mut String,
71 source: &FormattingSource<'_>,
72 expr: &cel_parser::Expr,
73 depth: usize,
74 suffix_columns: usize,
75) {
76 let text = cel_parser::format_expr_with_layout(
77 expr,
78 source,
79 depth,
80 output_column(out),
81 100,
82 suffix_columns,
83 );
84 append_expression_layout(out, &text);
85}
86
87fn append_expression_layout(out: &mut String, text: &str) {
92 if text.starts_with('\n') {
93 out.truncate(out.trim_end_matches(' ').len());
94 }
95 out.push_str(text);
96}
97
98fn cell_clause_suffix(
100 filter: Option<&ast::CellFilter>,
101 require: Option<&ast::RequireBlock>,
102) -> usize {
103 if filter.is_some() {
104 8
105 } else if require.is_some() {
106 10
107 } else {
108 1
109 }
110}
111
112fn indent(depth: usize) -> String {
116 " ".repeat(depth)
117}
118
119fn write_comment_inline(out: &mut String, comment: &ast::Comment, depth: usize) {
131 match comment {
132 ast::Comment::Line(text) => {
133 let mut lines = text.split('\n');
134 if let Some(first) = lines.next() {
135 out.push_str("// ");
136 out.push_str(first);
137 out.push('\n');
138 }
139 for line in lines {
140 out.push_str(&indent(depth));
141 out.push_str("// ");
142 out.push_str(line);
143 out.push('\n');
144 }
145 }
146 ast::Comment::Block(text) => {
147 if text.contains('\n') {
148 out.push_str("/*\n");
149 for line in text.split('\n') {
150 out.push_str(&indent(depth + 1));
151 out.push_str(line);
152 out.push('\n');
153 }
154 out.push_str(&indent(depth));
155 out.push_str("*/\n");
156 } else {
157 out.push_str("/* ");
158 out.push_str(text);
159 out.push_str(" */\n");
160 }
161 }
162 }
163}
164
165fn write_comment(out: &mut String, comment: &ast::Comment, depth: usize) {
171 out.push_str(&indent(depth));
172 write_comment_inline(out, comment, depth);
173}
174
175fn write_doc_comment(out: &mut String, marker: &str, doc_comment: Option<&str>, depth: usize) {
183 if let Some(doc) = doc_comment {
184 for line in doc.split('\n') {
185 out.push_str(&indent(depth));
186 out.push_str(marker);
187 out.push_str(line);
188 out.push('\n');
189 }
190 }
191}
192
193fn write_trivia(
198 out: &mut String,
199 blank_line_before: bool,
200 leading_comment: Option<&ast::Comment>,
201 depth: usize,
202) {
203 if blank_line_before {
204 out.push('\n');
205 }
206 if let Some(comment) = leading_comment {
207 write_comment(out, comment, depth);
208 }
209}
210
211fn write_trailing_trivia(
218 out: &mut String,
219 blank_line_before_close: bool,
220 trailing_comment: Option<&ast::Comment>,
221 depth: usize,
222) {
223 if blank_line_before_close {
224 out.push('\n');
225 }
226 if let Some(comment) = trailing_comment {
227 write_comment(out, comment, depth);
228 }
229}
230
231fn write_line_end(
239 out: &mut String,
240 terminator: &str,
241 trailing_line_comment: Option<&ast::Comment>,
242 depth: usize,
243) {
244 out.push_str(terminator);
245 match trailing_line_comment {
246 Some(comment) => {
247 out.push(' ');
248 write_comment_inline(out, comment, depth);
249 }
250 None => out.push('\n'),
251 }
252}
253
254fn source_text_or_empty(span: ast::ExprSpan) -> String {
260 span.start.source_text().unwrap_or_default()
261}
262
263fn source_region<'a>(
268 source: &FormattingSource<'a>,
269 start: proc_macro2::LineColumn,
270 end: proc_macro2::LineColumn,
271) -> Option<&'a str> {
272 use cel_parser::trivia::line_column_to_byte;
273 if source.is_empty() {
274 return None;
275 }
276 let start = line_column_to_byte(source, &source.line_starts, start);
277 let end = line_column_to_byte(source, &source.line_starts, end);
278 source.text.get(start..end)
279}
280
281fn write_parameter_type(out: &mut String, source: &FormattingSource<'_>, ty: &ast::TypeExpr) {
288 let span = ty.span();
289 if let Some(text) = source_region(source, span.start.start(), span.end.end()) {
290 out.push_str(text);
291 } else {
292 out.push_str(&source_text_or_empty(span));
293 }
294}
295
296fn write_header_documentation(
305 out: &mut String,
306 source: &FormattingSource<'_>,
307 start: proc_macro2::Span,
308 keyword: ast::ExprSpan,
309 prefix: &str,
310 documentation: Option<&str>,
311 depth: usize,
312) {
313 if let Some(documentation) = documentation {
314 if let Some(region) = source_region(source, start.start(), keyword.start.start())
315 .filter(|region| !region.is_empty())
316 {
317 out.push_str(&indent(depth));
318 out.push_str(region.trim_end_matches([' ', '\t']));
319 } else {
320 write_doc_comment(out, prefix, Some(documentation), depth);
321 }
322 }
323}
324
325fn point_span(span: proc_macro2::Span) -> ast::ExprSpan {
329 ast::ExprSpan {
330 start: span,
331 end: span,
332 }
333}
334
335fn emit_decl_gap(
362 source: &FormattingSource<'_>,
363 prev_end: ast::ExprSpan,
364 next_start: ast::ExprSpan,
365 expected: &[&'static str],
366 depth: usize,
367) -> String {
368 use cel_parser::trivia::{GapPiece, line_column_to_byte, scan_gap};
369 let pieces = if source.is_empty() {
372 scan_gap("", expected)
373 } else {
374 let start = line_column_to_byte(source, &source.line_starts, prev_end.end.end());
375 let end = line_column_to_byte(source, &source.line_starts, next_start.start.start());
376 match source.get(start..end) {
377 Some(gap) => scan_gap(gap, expected),
378 None => scan_gap("", expected),
379 }
380 };
381 if pieces
382 .iter()
383 .all(|piece| matches!(piece, GapPiece::Punct(_)))
384 {
385 let compact = match expected {
386 ["("] => Some("("),
387 [","] => Some(", "),
388 [":"] => Some(": "),
389 ["::"] => Some("::"),
390 [")"] => Some(")"),
391 ["(", ")"] => Some("()"),
392 _ => None,
393 };
394 if let Some(compact) = compact {
395 return compact.to_owned();
396 }
397 }
398 let mut out = String::new();
399 for piece in pieces {
400 match piece {
401 GapPiece::Punct(tok) => {
402 if !out.ends_with(' ') {
403 out.push(' ');
404 }
405 out.push_str(tok);
406 out.push(' ');
407 }
408 GapPiece::Comment(cel_parser::Comment::Block(text)) => {
409 if !out.ends_with(' ') {
410 out.push(' ');
411 }
412 out.push_str("/* ");
413 out.push_str(&text);
414 out.push_str(" */");
415 }
416 GapPiece::Comment(cel_parser::Comment::Line(text)) => {
417 if !out.ends_with(' ') {
418 out.push(' ');
419 }
420 out.push_str("// ");
421 out.push_str(&text);
422 out.push('\n');
423 out.push_str(&indent(depth + 1));
424 }
425 }
426 }
427 if !expected.is_empty() && !out.is_empty() && !out.ends_with(' ') {
430 out.push(' ');
431 }
432 out
433}
434
435fn write_binding(
441 out: &mut String,
442 source: &FormattingSource<'_>,
443 binding: &ast::BindingDecl,
444 depth: usize,
445) {
446 write_trivia(
447 out,
448 binding.blank_line_before,
449 binding.leading_comment.as_ref(),
450 depth,
451 );
452 out.push_str(&indent(depth));
453 if binding.destructure {
454 out.push('(');
455 for (i, (name, _)) in binding.outputs.iter().enumerate() {
456 if i > 0 {
457 out.push_str(", ");
458 }
459 out.push_str(name);
460 }
461 if binding.outputs.len() == 1 {
462 out.push(',');
463 }
464 out.push(')');
465 } else {
466 debug_assert_eq!(
467 binding.outputs.len(),
468 1,
469 "a non-destructuring binding names exactly one output"
470 );
471 out.push_str(&binding.outputs[0].0);
472 }
473 let op_start = binding
475 .outputs
476 .last()
477 .map(|(_, span)| *span)
478 .unwrap_or(binding.span);
479 out.push_str(&emit_decl_gap(
480 source,
481 op_start,
482 binding.body.span(),
483 &[":="],
484 depth,
485 ));
486 write_expression(out, source, &binding.body, depth, 1);
487 out.push_str(&emit_decl_gap(
489 source,
490 binding.body.span(),
491 point_span(binding.span.end),
492 &[],
493 depth,
494 ));
495 write_line_end(out, ";", binding.trailing_line_comment.as_ref(), depth);
496}
497
498fn write_relationship(
503 out: &mut String,
504 source: &FormattingSource<'_>,
505 rel: &ast::RelationshipDecl,
506 depth: usize,
507) {
508 write_trivia(
509 out,
510 rel.blank_line_before,
511 rel.leading_comment.as_ref(),
512 depth,
513 );
514 write_doc_comment(out, "///", rel.doc_comment.as_deref(), depth);
515 out.push_str(&indent(depth));
516 out.push_str("relationship");
517 out.push_str(&emit_decl_gap(
520 source,
521 point_span(rel.span.start),
522 rel.open_brace_span,
523 &[],
524 depth,
525 ));
526 out.push_str(" {\n");
527 for binding in &rel.bindings {
528 write_binding(out, source, binding, depth + 1);
529 }
530 write_trailing_trivia(
531 out,
532 rel.blank_line_before_close,
533 rel.trailing_comment.as_ref(),
534 depth + 1,
535 );
536 out.push_str(&indent(depth));
537 write_line_end(out, "}", rel.trailing_line_comment.as_ref(), depth);
538}
539
540fn write_branch_relationships(
546 out: &mut String,
547 source: &FormattingSource<'_>,
548 relationships: &[ast::BranchItem],
549 trailing_comment: Option<&ast::Comment>,
550 blank_line_before_close: bool,
551 line_trailing_comment: Option<&ast::Comment>,
552 depth: usize,
553) {
554 out.push_str("{\n");
555 for rel in relationships {
556 match rel {
557 ast::BranchItem::Relationship(rel) => write_relationship(out, source, rel, depth + 1),
558 ast::BranchItem::RelationshipUse(call) => {
559 write_relationship_use(out, source, call, depth + 1)
560 }
561 }
562 }
563 write_trailing_trivia(out, blank_line_before_close, trailing_comment, depth + 1);
564 out.push_str(&indent(depth));
565 write_line_end(out, "}", line_trailing_comment, depth);
566}
567
568fn write_branch(
575 out: &mut String,
576 source: &FormattingSource<'_>,
577 branch: &ast::ConditionalBranch,
578 depth: usize,
579) {
580 write_trivia(
581 out,
582 branch.blank_line_before,
583 branch.leading_comment.as_ref(),
584 depth,
585 );
586 out.push_str(&indent(depth));
587 if branch.negated {
588 out.push('-');
589 }
590 out.push_str(&source_text_or_empty(branch.literal_span));
591 out.push_str(&emit_decl_gap(
594 source,
595 branch.literal_span,
596 branch.open_brace_span,
597 &["=>"],
598 depth,
599 ));
600 write_branch_relationships(
601 out,
602 source,
603 &branch.relationships,
604 branch.trailing_comment.as_ref(),
605 branch.blank_line_before_close,
606 branch.trailing_line_comment.as_ref(),
607 depth,
608 );
609}
610
611fn write_conditional(
619 out: &mut String,
620 source: &FormattingSource<'_>,
621 cond: &ast::ConditionalDecl,
622 depth: usize,
623) {
624 write_trivia(
625 out,
626 cond.blank_line_before,
627 cond.leading_comment.as_ref(),
628 depth,
629 );
630 write_doc_comment(out, "///", cond.doc_comment.as_deref(), depth);
631 out.push_str(&indent(depth));
632 out.push_str("conditional ");
633 let header = cel_parser::format_expr_header_with_layout(
634 &cond.match_expr,
635 source,
636 depth,
637 (cond.span.start.end(), cond.open_brace_span.start.start()),
638 output_column(out),
639 100,
640 2,
641 );
642 append_expression_layout(out, &header);
643 out.push_str(" {\n");
644 for branch in &cond.branches {
645 write_branch(out, source, branch, depth + 1);
646 }
647 if let Some(default) = &cond.default {
648 write_trivia(
649 out,
650 default.blank_line_before,
651 default.leading_comment.as_ref(),
652 depth + 1,
653 );
654 out.push_str(&indent(depth + 1));
655 out.push('_');
656 out.push_str(&emit_decl_gap(
658 source,
659 point_span(default.span.start),
660 default.open_brace_span,
661 &["=>"],
662 depth + 1,
663 ));
664 write_branch_relationships(
665 out,
666 source,
667 &default.relationships,
668 default.trailing_comment.as_ref(),
669 default.blank_line_before_close,
670 default.trailing_line_comment.as_ref(),
671 depth + 1,
672 );
673 }
674 write_trailing_trivia(
675 out,
676 cond.blank_line_before_close,
677 cond.trailing_comment.as_ref(),
678 depth + 1,
679 );
680 out.push_str(&indent(depth));
681 write_line_end(out, "}", cond.trailing_line_comment.as_ref(), depth);
682}
683
684fn write_cell(out: &mut String, source: &FormattingSource<'_>, cell: &ast::CellDecl, depth: usize) {
691 write_trivia(
692 out,
693 cell.blank_line_before,
694 cell.leading_comment.as_ref(),
695 depth,
696 );
697 write_doc_comment(out, "///", cell.doc_comment.as_deref(), depth);
698 out.push_str(&indent(depth));
699 out.push_str("cell ");
700 out.push_str(&cell.name);
701 if let Some(type_expr) = &cell.type_name {
702 out.push_str(": ");
703 out.push_str(&source_text_or_empty(type_expr.span()));
704 }
705 if let Some(expr) = &cell.initializer {
707 let op_start = cell
708 .type_name
709 .as_ref()
710 .map(ast::TypeExpr::span)
711 .unwrap_or(cell.name_span);
712 out.push_str(&emit_decl_gap(source, op_start, expr.span(), &["="], depth));
713 write_expression(
714 out,
715 source,
716 expr,
717 depth,
718 cell_clause_suffix(cell.filter.as_ref(), cell.require.as_ref()),
719 );
720 }
721 write_cell_clauses(
722 out,
723 source,
724 depth,
725 cell.initializer.as_ref().map(cel_parser::Expr::span),
726 cell.type_name.as_ref().map(ast::TypeExpr::span),
727 cell.name_span,
728 cell.filter.as_ref(),
729 cell.require.as_ref(),
730 cell.span.end,
731 );
732 write_line_end(out, ";", cell.trailing_line_comment.as_ref(), depth);
733}
734
735#[allow(clippy::too_many_arguments)]
743fn write_cell_clauses(
744 out: &mut String,
745 source: &FormattingSource<'_>,
746 depth: usize,
747 initializer_span: Option<ast::ExprSpan>,
748 type_span: Option<ast::ExprSpan>,
749 name_span: ast::ExprSpan,
750 filter: Option<&ast::CellFilter>,
751 require: Option<&ast::RequireBlock>,
752 terminator: proc_macro2::Span,
753) {
754 let mut prev = initializer_span.or(type_span).unwrap_or(name_span);
755 if let Some(filter) = filter {
756 out.push_str(&emit_decl_gap(
759 source,
760 prev,
761 filter.body.span(),
762 &["filter"],
763 depth,
764 ));
765 write_expression(
766 out,
767 source,
768 &filter.body,
769 depth,
770 if require.is_some() { 10 } else { 1 },
771 );
772 prev = filter.body.span();
773 }
774 if let Some(require) = require {
775 out.push_str(&emit_decl_gap(
779 source,
780 prev,
781 require.open_brace_span,
782 &["require"],
783 depth,
784 ));
785 write_require_block(out, source, require, depth);
786 prev = require.span;
787 }
788 out.push_str(&emit_decl_gap(
790 source,
791 prev,
792 point_span(terminator),
793 &[],
794 depth,
795 ));
796}
797
798fn write_requirement(
803 out: &mut String,
804 source: &FormattingSource<'_>,
805 req: &ast::RequirementDecl,
806 depth: usize,
807) {
808 write_trivia(
809 out,
810 req.blank_line_before,
811 req.leading_comment.as_ref(),
812 depth,
813 );
814 out.push_str(&indent(depth));
815 if let Some(name) = &req.name {
816 out.push('@');
817 out.push_str(name);
818 if let Some(name_span) = req.name_span {
820 out.push_str(&emit_decl_gap(
821 source,
822 name_span,
823 req.body.span(),
824 &[],
825 depth,
826 ));
827 }
828 out.push(' ');
829 }
830 write_expression(out, source, &req.body, depth, 1);
831 write_line_end(out, ";", req.trailing_line_comment.as_ref(), depth);
832}
833
834fn write_require_block(
842 out: &mut String,
843 source: &FormattingSource<'_>,
844 require: &ast::RequireBlock,
845 depth: usize,
846) {
847 out.push_str("{\n");
848 for req in &require.requirements {
849 write_requirement(out, source, req, depth + 1);
850 }
851 write_trailing_trivia(
852 out,
853 require.blank_line_before_close,
854 require.trailing_comment.as_ref(),
855 depth + 1,
856 );
857 out.push_str(&indent(depth));
858 out.push('}');
859}
860
861fn write_out(out: &mut String, source: &FormattingSource<'_>, decl: &ast::OutDecl, depth: usize) {
866 write_trivia(
867 out,
868 decl.blank_line_before,
869 decl.leading_comment.as_ref(),
870 depth,
871 );
872 write_doc_comment(out, "///", decl.doc_comment.as_deref(), depth);
873 out.push_str(&indent(depth));
874 out.push_str("out ");
875 out.push_str(&decl.name);
876 if let Some(type_expr) = &decl.type_name {
877 out.push_str(": ");
878 out.push_str(&source_text_or_empty(type_expr.span()));
879 }
880 let op_start = decl
882 .type_name
883 .as_ref()
884 .map(ast::TypeExpr::span)
885 .unwrap_or(decl.name_span);
886 out.push_str(&emit_decl_gap(
887 source,
888 op_start,
889 decl.initializer.span(),
890 &[":="],
891 depth,
892 ));
893 write_expression(
894 out,
895 source,
896 &decl.initializer,
897 depth,
898 cell_clause_suffix(decl.filter.as_ref(), decl.require.as_ref()),
899 );
900 write_cell_clauses(
901 out,
902 source,
903 depth,
904 Some(decl.initializer.span()),
905 decl.type_name.as_ref().map(ast::TypeExpr::span),
906 decl.name_span,
907 decl.filter.as_ref(),
908 decl.require.as_ref(),
909 decl.span.end,
910 );
911 write_line_end(out, ";", decl.trailing_line_comment.as_ref(), depth);
912}
913
914fn write_source(
920 out: &mut String,
921 source: &FormattingSource<'_>,
922 decl: &ast::SourceDecl,
923 depth: usize,
924) {
925 write_trivia(
926 out,
927 decl.blank_line_before,
928 decl.leading_comment.as_ref(),
929 depth,
930 );
931 write_doc_comment(out, "///", decl.doc_comment.as_deref(), depth);
932 out.push_str(&indent(depth));
933 out.push_str("source ");
934 out.push_str(&decl.name);
935 if let Some(type_expr) = &decl.type_name {
936 out.push_str(": ");
937 out.push_str(&source_text_or_empty(type_expr.span()));
938 }
939 if let Some(expr) = &decl.initializer {
941 let op_start = decl
942 .type_name
943 .as_ref()
944 .map(ast::TypeExpr::span)
945 .unwrap_or(decl.name_span);
946 out.push_str(&emit_decl_gap(source, op_start, expr.span(), &["="], depth));
947 write_expression(
948 out,
949 source,
950 expr,
951 depth,
952 cell_clause_suffix(decl.filter.as_ref(), decl.require.as_ref()),
953 );
954 }
955 write_cell_clauses(
956 out,
957 source,
958 depth,
959 decl.initializer.as_ref().map(cel_parser::Expr::span),
960 decl.type_name.as_ref().map(ast::TypeExpr::span),
961 decl.name_span,
962 decl.filter.as_ref(),
963 decl.require.as_ref(),
964 decl.span.end,
965 );
966 write_line_end(out, ";", decl.trailing_line_comment.as_ref(), depth);
967}
968
969fn write_sheet_item(
976 out: &mut String,
977 source: &FormattingSource<'_>,
978 item: &ast::SheetItem,
979 depth: usize,
980) {
981 match item {
982 ast::SheetItem::Cell(cell) => write_cell(out, source, cell, depth),
983 ast::SheetItem::Relationship(rel) => write_relationship(out, source, rel, depth),
984 ast::SheetItem::RelationshipUse(call) => write_relationship_use(out, source, call, depth),
985 ast::SheetItem::Conditional(cond) => write_conditional(out, source, cond, depth),
986 ast::SheetItem::Out(out_decl) => write_out(out, source, out_decl, depth),
987 ast::SheetItem::Source(decl) => write_source(out, source, decl, depth),
988 ast::SheetItem::Error { .. } => {
989 unreachable!("format_sheet is only called on a sheet with no recorded syntax errors")
990 }
991 }
992}
993
994pub fn format_sheet(sheet: &ast::Sheet, source: &str) -> String {
1019 format_sheet_indexed(sheet, &FormattingSource::new(source))
1020}
1021
1022fn format_sheet_indexed(sheet: &ast::Sheet, source: &FormattingSource<'_>) -> String {
1028 debug_assert!(
1029 sheet.errors.is_empty(),
1030 "format_sheet's precondition: no recorded syntax errors"
1031 );
1032 let mut out = String::new();
1033 write_trivia(&mut out, false, sheet.leading_comment.as_ref(), 0);
1034 write_header_documentation(
1035 &mut out,
1036 source,
1037 sheet.span.start,
1038 sheet.keyword_span,
1039 "//!",
1040 sheet.doc_comment.as_deref(),
1041 0,
1042 );
1043 out.push_str("sheet");
1044 out.push_str(&emit_decl_gap(
1045 source,
1046 sheet.keyword_span,
1047 sheet.name_span,
1048 &[],
1049 0,
1050 ));
1051 out.push(' ');
1052 out.push_str(&sheet.name);
1053 let mut previous = sheet.name_span;
1054 if let Some(parameters) = &sheet.parameters {
1055 for (index, parameter) in parameters.iter().enumerate() {
1056 out.push_str(&emit_decl_gap(
1057 source,
1058 previous,
1059 parameter.name_span,
1060 if index == 0 { &["("] } else { &[","] },
1061 0,
1062 ));
1063 out.push_str(¶meter.name);
1064 out.push_str(&emit_decl_gap(
1065 source,
1066 parameter.name_span,
1067 parameter.type_name.span(),
1068 &[":"],
1069 0,
1070 ));
1071 write_parameter_type(&mut out, source, ¶meter.type_name);
1072 previous = parameter.type_name.span();
1073 }
1074 out.push_str(&emit_decl_gap(
1075 source,
1076 previous,
1077 sheet.open_brace_span,
1078 if parameters.is_empty() {
1079 &["(", ")"]
1080 } else {
1081 &[")"]
1082 },
1083 0,
1084 ));
1085 } else {
1086 out.push_str(&emit_decl_gap(
1087 source,
1088 previous,
1089 sheet.open_brace_span,
1090 &[],
1091 0,
1092 ));
1093 }
1094 if !out.ends_with(' ') {
1095 out.push(' ');
1096 }
1097 out.push_str("{\n");
1098 for item in &sheet.items {
1099 write_sheet_item(&mut out, source, item, 1);
1100 }
1101 write_trailing_trivia(
1102 &mut out,
1103 sheet.blank_line_before_close,
1104 sheet.trailing_comment.as_ref(),
1105 1,
1106 );
1107 out.push_str("}\n");
1108 out
1109}
1110
1111#[derive(Debug)]
1120pub enum FormatSourceError {
1121 Parse(cel_parser::ParseError),
1123 Recovered(Vec<cel_parser::ParseError>),
1125}
1126
1127impl std::fmt::Display for FormatSourceError {
1128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1132 match self {
1133 FormatSourceError::Parse(error) => write!(f, "{error}"),
1134 FormatSourceError::Recovered(errors) => {
1135 for (i, error) in errors.iter().enumerate() {
1136 if i > 0 {
1137 writeln!(f)?;
1138 }
1139 write!(f, "{error}")?;
1140 }
1141 Ok(())
1142 }
1143 }
1144 }
1145}
1146
1147impl std::error::Error for FormatSourceError {}
1148
1149pub fn format_source(source: &str) -> Result<String, FormatSourceError> {
1178 let mut module = crate::AdamAstParser::new()
1179 .parse_module_str(source)
1180 .map_err(FormatSourceError::Parse)?;
1181 if module.items.is_empty() {
1182 return Err(FormatSourceError::Parse(cel_parser::ParseError::new(
1183 "expected `use` or `sheet`",
1184 proc_macro2::Span::call_site(),
1185 )));
1186 }
1187 if module
1188 .items
1189 .iter()
1190 .all(|item| matches!(item, ast::ModuleItem::Error(_)))
1191 {
1192 return Err(FormatSourceError::Parse(module.errors.remove(0)));
1193 }
1194 crate::attach_module_trivia(source, &mut module);
1195 format_module(&module, source)
1196}
1197
1198fn write_path(
1203 out: &mut String,
1204 source: &FormattingSource<'_>,
1205 path: &[(String, ast::ExprSpan)],
1206 depth: usize,
1207) {
1208 for (index, (name, span)) in path.iter().enumerate() {
1209 if index > 0 {
1210 out.push_str(&emit_decl_gap(
1211 source,
1212 path[index - 1].1,
1213 *span,
1214 &["::"],
1215 depth,
1216 ));
1217 }
1218 out.push_str(name);
1219 }
1220}
1221
1222fn write_relationship_use(
1228 out: &mut String,
1229 source: &FormattingSource<'_>,
1230 call: &ast::RelationshipUse,
1231 depth: usize,
1232) {
1233 for comment in &call.opening_comments {
1234 write_trivia(out, false, Some(comment), depth);
1235 }
1236 write_trivia(
1237 out,
1238 call.blank_line_before,
1239 call.leading_comment.as_ref(),
1240 depth,
1241 );
1242 write_header_documentation(
1243 out,
1244 source,
1245 call.span.start,
1246 call.keyword_span,
1247 "///",
1248 call.doc_comment.as_deref(),
1249 depth,
1250 );
1251 out.push_str(&indent(depth));
1252 out.push_str("relationship");
1253 out.push_str(&emit_decl_gap(
1254 source,
1255 call.keyword_span,
1256 call.path[0].1,
1257 &[],
1258 depth,
1259 ));
1260 out.push(' ');
1261 write_path(out, source, &call.path, depth);
1262 let mut previous = call.path.last().expect("parsed paths are nonempty").1;
1263 for (index, argument) in call.arguments.iter().enumerate() {
1264 out.push_str(&emit_decl_gap(
1265 source,
1266 previous,
1267 argument.name_span,
1268 if index == 0 { &["("] } else { &[","] },
1269 depth,
1270 ));
1271 out.push_str(&argument.name);
1272 out.push_str(&emit_decl_gap(
1273 source,
1274 argument.name_span,
1275 argument.cell_span,
1276 &[":"],
1277 depth,
1278 ));
1279 out.push_str(&argument.cell);
1280 previous = argument.cell_span;
1281 }
1282 out.push_str(&emit_decl_gap(
1283 source,
1284 previous,
1285 point_span(call.span.end),
1286 if call.arguments.is_empty() {
1287 &["(", ")"]
1288 } else {
1289 &[")"]
1290 },
1291 depth,
1292 ));
1293 write_line_end(out, ";", call.trailing_line_comment.as_ref(), depth);
1294}
1295
1296pub fn format_module(module: &ast::Module, source: &str) -> Result<String, FormatSourceError> {
1321 if !module.errors.is_empty() {
1322 return Err(FormatSourceError::Recovered(module.errors.clone()));
1323 }
1324 let source = &FormattingSource::new(source);
1325 let mut out = String::new();
1326 for (index, item) in module.items.iter().enumerate() {
1327 if let Some(comments) = module.comments.get(index) {
1328 for comment in comments {
1329 write_trivia(&mut out, false, Some(comment), 0);
1330 }
1331 }
1332 match item {
1333 ast::ModuleItem::Sheet(sheet)
1334 if sheet
1335 .items
1336 .iter()
1337 .any(|item| matches!(item, ast::SheetItem::Error { .. })) =>
1338 {
1339 return Err(FormatSourceError::Recovered(vec![
1340 cel_parser::ParseError::new("recovered sheet item", sheet.span.start),
1341 ]));
1342 }
1343 ast::ModuleItem::Sheet(sheet) if sheet.errors.is_empty() => {
1344 out.push_str(&format_sheet_indexed(sheet, source))
1345 }
1346 ast::ModuleItem::Use(import) => {
1347 write_header_documentation(
1348 &mut out,
1349 source,
1350 import.span.start,
1351 import.keyword_span,
1352 "//!",
1353 import.doc_comment.as_deref(),
1354 0,
1355 );
1356 out.push_str("use");
1357 out.push_str(&emit_decl_gap(
1358 source,
1359 import.keyword_span,
1360 import.path[0].1,
1361 &[],
1362 0,
1363 ));
1364 out.push(' ');
1365 write_path(&mut out, source, &import.path, 0);
1366 let mut previous = import.path.last().expect("parsed paths are nonempty").1;
1367 if let Some((alias, span)) = &import.alias {
1368 out.push_str(&emit_decl_gap(source, previous, *span, &["as"], 0));
1369 out.push_str(alias);
1370 previous = *span;
1371 }
1372 out.push_str(&emit_decl_gap(
1373 source,
1374 previous,
1375 point_span(import.span.end),
1376 &[],
1377 0,
1378 ));
1379 out.push_str(";\n");
1380 }
1381 ast::ModuleItem::Sheet(sheet) => {
1382 return Err(FormatSourceError::Recovered(sheet.errors.clone()));
1383 }
1384 ast::ModuleItem::Error(span) => {
1385 return Err(FormatSourceError::Recovered(vec![
1386 cel_parser::ParseError::new("recovered module item", span.start),
1387 ]));
1388 }
1389 }
1390 }
1391 if let Some(comments) = module.comments.get(module.items.len()) {
1392 for comment in comments {
1393 write_trivia(&mut out, false, Some(comment), 0);
1394 }
1395 }
1396 Ok(out)
1397}
1398
1399#[cfg(test)]
1400mod tests {
1401 #[test]
1403 fn output_column_tracks_the_active_line() {
1404 for (text, expected) in [
1405 ("", 0),
1406 ("cell é = ", 9),
1407 ("old prefix\n := ", 7),
1408 ("line\n", 0),
1409 ] {
1410 assert_eq!(super::output_column(text), expected);
1411 }
1412 }
1413
1414 #[test]
1416 fn grouped_conditional_headers_preserve_comments() {
1417 for header in [
1418 "(Sample { value: 1.0 }) /* first */",
1419 "/* first */ (Sample { value: 1.0 })",
1420 "(/* first */ Sample { value: 1.0 } /* second */) /* third */",
1421 "(/* first */ (/* second */ Sample { value: 1.0 }) /* third */) /* fourth */",
1422 "(Sample { value: 1.0 }) /* first /* nested ) */ end */",
1423 "(/* first /* nested ( */ end */ Sample { value: 1.0 }) /* second */",
1424 "((Sample { value: 1.0 }) /* first */) /* second */",
1425 "(// first\n Sample { value: 1.0 } // second\n) // third\n",
1426 "// first\n (/* second */ Sample { value: 1.0 }) // third\n",
1427 "(/* first */ p) /* second */",
1428 "(/* first */ (Sample { value: 1.0 }).value) /* second */",
1429 ] {
1430 let source = format!("sheet s {{ conditional {header} {{ _ => {{}} }} }}");
1431 let formatted = super::format_source(&source).unwrap();
1432 let mut previous = 0;
1433 for label in ["first", "second", "third", "fourth"] {
1434 if source.contains(label) {
1435 assert_eq!(
1436 formatted.matches(label).count(),
1437 1,
1438 "{source} -> {formatted}"
1439 );
1440 let position = formatted.find(label).unwrap();
1441 assert!(position >= previous, "{formatted}");
1442 previous = position;
1443 }
1444 }
1445 assert_eq!(super::format_source(&formatted).unwrap(), formatted);
1446 }
1447 }
1448
1449 use super::*;
1450 use crate::AdamAstParser;
1451
1452 fn format(source: &str) -> String {
1457 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1458 crate::attach_trivia(source, &mut sheet);
1459 format_sheet(&sheet, source)
1460 }
1461
1462 #[test]
1464 fn source_error_display_preserves_diagnostics_and_order() {
1465 let first = cel_parser::ParseError::new("first diagnostic", proc_macro2::Span::call_site());
1466 let second =
1467 cel_parser::ParseError::new("second diagnostic", proc_macro2::Span::call_site());
1468 assert_eq!(
1469 FormatSourceError::Parse(first.clone()).to_string(),
1470 first.to_string()
1471 );
1472 assert_eq!(
1473 FormatSourceError::Recovered(vec![first.clone(), second.clone()]).to_string(),
1474 format!("{first}\n{second}")
1475 );
1476 }
1477
1478 #[test]
1480 fn formats_an_empty_sheet() {
1481 assert_eq!(format("sheet s {}"), "sheet s {\n}\n");
1482 }
1483
1484 #[test]
1486 fn preserves_a_leading_comment_before_the_sheet_itself() {
1487 assert_eq!(
1488 format("// file header\nsheet s { cell x: i32 = 1; }"),
1489 "// file header\nsheet s {\n cell x: i32 = 1;\n}\n"
1490 );
1491 }
1492
1493 #[test]
1495 fn no_leading_comment_before_the_sheet_emits_nothing_extra() {
1496 assert_eq!(
1497 format("sheet s { cell x: i32 = 1; }"),
1498 "sheet s {\n cell x: i32 = 1;\n}\n"
1499 );
1500 }
1501
1502 #[test]
1504 fn formats_a_cell_with_type_and_initializer() {
1505 assert_eq!(
1506 format("sheet s { cell width: f64 = 1920.0; }"),
1507 "sheet s {\n cell width: f64 = 1920.0;\n}\n"
1508 );
1509 }
1510
1511 #[test]
1513 fn formats_a_source_decl() {
1514 assert_eq!(
1515 format("sheet s { source width: i32 = 4; }"),
1516 "sheet s {\n source width: i32 = 4;\n}\n"
1517 );
1518 }
1519
1520 #[test]
1522 fn formats_a_cell_with_only_a_type_annotation() {
1523 assert_eq!(
1524 format("sheet s { cell area: f64; }"),
1525 "sheet s {\n cell area: f64;\n}\n"
1526 );
1527 }
1528
1529 #[test]
1531 fn formats_a_cell_with_only_an_initializer() {
1532 assert_eq!(
1533 format("sheet s { cell mode = 0i32; }"),
1534 "sheet s {\n cell mode = 0i32;\n}\n"
1535 );
1536 }
1537
1538 #[test]
1540 fn packed_cells_stay_packed_and_a_blank_line_before_a_relationship_is_preserved() {
1541 let source = "sheet s {\n cell a: i32 = 1;\n cell b: i32 = 2;\n\n relationship { b := a; }\n}";
1542 let expected = "sheet s {\n cell a: i32 = 1;\n cell b: i32 = 2;\n\n relationship {\n b := a;\n }\n}\n";
1543 assert_eq!(format(source), expected);
1544 }
1545
1546 #[test]
1548 fn a_run_of_blank_lines_collapses_to_one() {
1549 let source = "sheet s {\n cell a: i32 = 1;\n\n\n\n cell b: i32 = 2;\n}";
1550 let expected = "sheet s {\n cell a: i32 = 1;\n\n cell b: i32 = 2;\n}\n";
1551 assert_eq!(format(source), expected);
1552 }
1553
1554 #[test]
1556 fn formats_a_relationship_with_multiple_bindings() {
1557 let source = "sheet s {\n relationship {\n area := width * height;\n width := area / height;\n }\n}";
1558 let expected = "sheet s {\n relationship {\n area := width * height;\n width := area / height;\n }\n}\n";
1559 assert_eq!(format(source), expected);
1560 }
1561
1562 #[test]
1564 fn formats_a_multi_output_destructuring_binding_with_parens() {
1565 let source =
1566 "sheet s {\n relationship {\n (sum, diff) := (a + b, a - b);\n }\n}";
1567 let expected =
1568 "sheet s {\n relationship {\n (sum, diff) := (a + b, a - b);\n }\n}\n";
1569 assert_eq!(format(source), expected);
1570 }
1571
1572 #[test]
1574 fn formats_a_single_element_tuple_destructuring_binding_with_a_trailing_comma() {
1575 let source = "sheet s {\n relationship {\n (x,) := (a,);\n }\n}";
1576 let expected = "sheet s {\n relationship {\n (x,) := (a,);\n }\n}\n";
1577 assert_eq!(format(source), expected);
1578 }
1579
1580 #[test]
1582 fn rejects_a_parenthesized_binding_target_without_a_comma() {
1583 let source = "sheet s {\n relationship {\n (x) := a;\n }\n}";
1584 assert!(format_source(source).is_err());
1585 }
1586
1587 #[test]
1589 fn preserves_a_comment_on_a_nested_binding() {
1590 let source = "sheet s {\n relationship {\n b := a;\n\n // second\n a := b;\n }\n}";
1591 let expected = "sheet s {\n relationship {\n b := a;\n\n // second\n a := b;\n }\n}\n";
1592 assert_eq!(format(source), expected);
1593 }
1594
1595 #[test]
1597 fn formats_a_conditional_with_branches_and_a_default_and_no_trailing_commas() {
1598 let source = "sheet s {\n conditional p {\n 0i32 => { relationship { b := a; } }\n _ => { relationship { a := b; } }\n }\n}";
1599 let expected = "sheet s {\n conditional p {\n 0i32 => {\n relationship {\n b := a;\n }\n }\n _ => {\n relationship {\n a := b;\n }\n }\n }\n}\n";
1600 assert_eq!(format(source), expected);
1601 }
1602
1603 #[test]
1605 fn formats_a_conditional_branch_with_a_negated_literal_key() {
1606 let source = "sheet s {\n conditional p {\n -1i32 => { relationship { b := a; } }\n _ => { relationship { a := b; } }\n }\n}";
1607 let expected = "sheet s {\n conditional p {\n -1i32 => {\n relationship {\n b := a;\n }\n }\n _ => {\n relationship {\n a := b;\n }\n }\n }\n}\n";
1608 assert_eq!(format(source), expected);
1609 }
1610
1611 #[test]
1613 fn formats_a_conditional_with_an_expression_match_subject() {
1614 let source = "sheet s {\n conditional a && b {\n _ => { relationship { d := c; } }\n }\n}";
1615 let expected = "sheet s {\n conditional a && b {\n _ => {\n relationship {\n d := c;\n }\n }\n }\n}\n";
1616 assert_eq!(format(source), expected);
1617 }
1618
1619 #[test]
1621 fn preserves_a_comment_on_a_conditional_branch() {
1622 let source = "sheet s {\n conditional m {\n 0i32 => { relationship { b := a; } }\n // one\n 1i32 => { relationship { b := a; } }\n }\n}";
1623 let expected = "sheet s {\n conditional m {\n 0i32 => {\n relationship {\n b := a;\n }\n }\n // one\n 1i32 => {\n relationship {\n b := a;\n }\n }\n }\n}\n";
1624 assert_eq!(format(source), expected);
1625 }
1626
1627 #[test]
1629 fn binding_body_delegates_precedence_aware_parenthesization_to_cel_parser() {
1630 let source = "sheet s { relationship { c := (a + b) * 2i32; } }";
1631 let expected = "sheet s {\n relationship {\n c := (a + b) * 2i32;\n }\n}\n";
1632 assert_eq!(format(source), expected);
1633 }
1634
1635 #[test]
1637 fn format_is_idempotent_through_a_reparse() {
1638 let source = "sheet demo {\n cell a: f64 = 2.0;\n cell b: f64 = 3.0;\n\n relationship {\n c := a * b;\n }\n}";
1639 let once = format(source);
1640 let twice = format(&once);
1641 assert_eq!(once, twice);
1642 }
1643
1644 #[test]
1646 fn format_is_idempotent_through_a_reparse_with_a_conditional() {
1647 let source = "sheet demo {\n cell p: i32 = 0;\n cell c: f64;\n\n conditional p {\n 0i32 => {\n relationship {\n c := c;\n }\n }\n _ => {\n relationship {\n c := c;\n }\n }\n }\n}";
1648 let once = format(source);
1649 let twice = format(&once);
1650 assert_eq!(once, twice);
1651 }
1652
1653 #[test]
1655 fn formats_an_out_with_explicit_type_and_no_requirements() {
1656 let source = "sheet s {\n out area: f64 := width * height;\n}";
1657 let expected = "sheet s {\n out area: f64 := width * height;\n}\n";
1658 assert_eq!(format(source), expected);
1659 }
1660
1661 #[test]
1663 fn formats_an_out_with_no_type_annotation() {
1664 let source = "sheet s {\n out area := width;\n}";
1665 let expected = "sheet s {\n out area := width;\n}\n";
1666 assert_eq!(format(source), expected);
1667 }
1668
1669 #[test]
1671 fn formats_an_out_with_requirements_in_declaration_order() {
1672 let source = "sheet s {\n out area: f64 := width * height require {\n @max_area width * height <= max_area;\n };\n}";
1673 let expected = "sheet s {\n out area: f64 := width * height require {\n @max_area width * height <= max_area;\n };\n}\n";
1674 assert_eq!(format(source), expected);
1675 }
1676
1677 #[test]
1679 fn formats_a_cell_with_a_require_block() {
1680 let source =
1681 "sheet s {\n cell x: i32 = 5 require {\n @positive x > 0;\n };\n}";
1682 assert_eq!(format(source), format!("{source}\n"));
1683 }
1684
1685 #[test]
1687 fn formats_a_source_with_a_require_block() {
1688 let source =
1689 "sheet s {\n source x: i32 = 5 require {\n @positive x > 0;\n };\n}";
1690 assert_eq!(format(source), format!("{source}\n"));
1691 }
1692
1693 #[test]
1695 fn formats_a_source_with_a_filter_clause() {
1696 let source = "sheet s {\n source x: i32 = 5 filter 0..=10;\n}";
1697 assert_eq!(format(source), format!("{source}\n"));
1698 }
1699
1700 #[test]
1702 fn formats_an_out_with_a_filter_clause() {
1703 let source =
1704 "sheet s {\n cell width: i32 = 4;\n out area := width filter 0..=100;\n}";
1705 assert_eq!(format(source), format!("{source}\n"));
1706 }
1707
1708 #[test]
1710 fn formats_a_cell_with_an_explicit_tuple_type() {
1711 assert_eq!(
1712 format("sheet s { cell a: (i32, f64) = (1, 2.5); }"),
1713 "sheet s {\n cell a: (i32, f64) = (1, 2.5);\n}\n"
1714 );
1715 }
1716
1717 #[test]
1719 fn formats_a_cell_with_a_nested_tuple_type() {
1720 assert_eq!(
1721 format("sheet s { cell a: (i32, (f64, String)); }"),
1722 "sheet s {\n cell a: (i32, (f64, String));\n}\n"
1723 );
1724 }
1725
1726 #[test]
1728 fn formats_an_out_with_an_explicit_tuple_type() {
1729 assert_eq!(
1730 format("sheet s { out a: (i32, i32) := (x, x); }"),
1731 "sheet s {\n out a: (i32, i32) := (x, x);\n}\n"
1732 );
1733 }
1734
1735 #[test]
1737 fn format_is_idempotent_through_a_reparse_with_a_tuple_cell() {
1738 let source = "sheet s {\n cell a: (i32, f64) = (1, 2.5);\n}";
1739 let once = format(source);
1740 let twice = format(&once);
1741 assert_eq!(once, twice);
1742 }
1743
1744 #[test]
1746 fn formats_a_single_line_block_comment_preserving_its_style() {
1747 let source =
1748 "sheet s {\n cell a: i32 = 1;\n /* the total */\n cell x: i32 = 1;\n}";
1749 let expected =
1750 "sheet s {\n cell a: i32 = 1;\n /* the total */\n cell x: i32 = 1;\n}\n";
1751 assert_eq!(format(source), expected);
1752 }
1753
1754 #[test]
1756 fn formats_a_multi_line_block_comment_preserving_its_style() {
1757 let source = "sheet s {\n cell a: i32 = 1;\n /*\n line one\n line two\n */\n cell x: i32 = 1;\n}";
1758 let expected = "sheet s {\n cell a: i32 = 1;\n /*\n line one\n line two\n */\n cell x: i32 = 1;\n}\n";
1759 assert_eq!(format(source), expected);
1760 }
1761
1762 #[test]
1764 fn formats_the_issue_105_license_header_repro_without_dropping_it() {
1765 let source =
1766 "/*\n Copyright 2013 Adobe\n ...\n*/\nsheet s {\n cell a: i32 = 1;\n}";
1767 let expected =
1768 "/*\n Copyright 2013 Adobe\n ...\n*/\nsheet s {\n cell a: i32 = 1;\n}\n";
1769 assert_eq!(format(source), expected);
1770 }
1771
1772 #[test]
1774 fn block_comment_formatting_is_idempotent_through_a_reparse() {
1775 let source = "sheet s {\n cell a: i32 = 1;\n /*\n line one\n line two\n */\n cell x: i32 = 1;\n}";
1776 let once = format(source);
1777 let twice = format(&once);
1778 assert_eq!(once, twice);
1779 }
1780
1781 #[test]
1783 fn formats_a_cell_with_a_doc_comment() {
1784 assert_eq!(
1785 format("sheet s { /// the total\n cell x: i32 = 1; }"),
1786 "sheet s {\n /// the total\n cell x: i32 = 1;\n}\n"
1787 );
1788 }
1789
1790 #[test]
1792 fn formats_a_sheet_level_doc_comment() {
1793 assert_eq!(
1794 format("//! module docs\nsheet s { cell x: i32 = 1; }"),
1795 "//! module docs\nsheet s {\n cell x: i32 = 1;\n}\n"
1796 );
1797 }
1798
1799 #[test]
1801 fn formats_a_plain_comment_and_doc_comment_together_in_source_order() {
1802 let source =
1803 "sheet s {\n cell w: i32 = 0;\n // TODO\n /// docs\n cell x: i32 = 1;\n}";
1804 let expected =
1805 "sheet s {\n cell w: i32 = 0;\n // TODO\n /// docs\n cell x: i32 = 1;\n}\n";
1806 assert_eq!(format(source), expected);
1807 }
1808
1809 #[test]
1811 fn formats_doc_comments_on_a_relationship_conditional_and_out() {
1812 let source = "sheet s {\n /// r\n relationship { b := a; }\n\n /// c\n conditional true { true => { relationship { b := a; } } }\n\n /// o\n out area: f64 := w;\n}";
1813 let expected = "sheet s {\n /// r\n relationship {\n b := a;\n }\n\n /// c\n conditional true {\n true => {\n relationship {\n b := a;\n }\n }\n }\n\n /// o\n out area: f64 := w;\n}\n";
1814 assert_eq!(format(source), expected);
1815 }
1816
1817 #[test]
1819 fn doc_comment_formatting_is_idempotent_through_a_reparse() {
1820 let source = "sheet s {\n /// the total\n cell x: i32 = 1;\n}";
1821 let once = format(source);
1822 let twice = format(&once);
1823 assert_eq!(once, twice);
1824 }
1825
1826 #[test]
1828 fn formats_a_trailing_comment_before_the_sheets_closing_brace() {
1829 assert_eq!(
1830 format("sheet s {\n cell x: i32 = 1;\n // trailing\n}"),
1831 "sheet s {\n cell x: i32 = 1;\n // trailing\n}\n"
1832 );
1833 }
1834
1835 #[test]
1837 fn formats_a_trailing_comment_in_an_empty_relationship() {
1838 assert_eq!(
1839 format("sheet s {\n relationship {\n // only this\n }\n}"),
1840 "sheet s {\n relationship {\n // only this\n }\n}\n"
1841 );
1842 }
1843
1844 #[test]
1846 fn formats_a_trailing_comment_before_a_relationships_closing_brace() {
1847 let source =
1848 "sheet s {\n relationship {\n b := a;\n // trailing\n }\n}";
1849 let expected =
1850 "sheet s {\n relationship {\n b := a;\n // trailing\n }\n}\n";
1851 assert_eq!(format(source), expected);
1852 }
1853
1854 #[test]
1856 fn formats_a_trailing_comment_before_a_conditionals_own_closing_brace() {
1857 let source = "sheet s {\n conditional m {\n 0i32 => { relationship { b := a; } }\n // trailing\n }\n}";
1858 let expected = "sheet s {\n conditional m {\n 0i32 => {\n relationship {\n b := a;\n }\n }\n // trailing\n }\n}\n";
1859 assert_eq!(format(source), expected);
1860 }
1861
1862 #[test]
1864 fn formats_a_trailing_comment_in_a_default_arm() {
1865 let source = "sheet s {\n conditional m {\n _ => {\n relationship { b := a; }\n // trailing\n }\n }\n}";
1866 let expected = "sheet s {\n conditional m {\n _ => {\n relationship {\n b := a;\n }\n // trailing\n }\n }\n}\n";
1867 assert_eq!(format(source), expected);
1868 }
1869
1870 #[test]
1872 fn formats_a_trailing_comment_before_a_requires_closing_brace() {
1873 let source = "sheet s {\n out area: f64 := w require {\n @c w <= 10.0;\n // trailing\n };\n}";
1874 let expected = "sheet s {\n out area: f64 := w require {\n @c w <= 10.0;\n // trailing\n };\n}\n";
1875 assert_eq!(format(source), expected);
1876 }
1877
1878 #[test]
1880 fn formats_a_trailing_comment_before_a_cells_requires_closing_brace() {
1881 let source = "sheet s {\n cell a: i32 = 1 require {\n @r a > 0;\n // trailing\n };\n}";
1886 let expected = "sheet s {\n cell a: i32 = 1 require {\n @r a > 0;\n // trailing\n };\n}\n";
1887 assert_eq!(format(source), expected);
1888 }
1889
1890 #[test]
1892 fn formats_a_trailing_comment_before_a_sources_requires_closing_brace() {
1893 let source = "sheet s {\n source a: i32 = 1 require {\n @r a > 0;\n // trailing\n };\n}";
1896 let expected = "sheet s {\n source a: i32 = 1 require {\n @r a > 0;\n // trailing\n };\n}\n";
1897 assert_eq!(format(source), expected);
1898 }
1899
1900 #[test]
1902 fn trailing_trivia_formatting_is_idempotent_through_a_reparse() {
1903 let source = "sheet s {\n cell a: i32 = 1;\n // trailing\n}";
1904 let once = format(source);
1905 let twice = format(&once);
1906 assert_eq!(once, twice);
1907 }
1908
1909 #[test]
1911 fn formats_a_cell_with_a_filter() {
1912 assert_eq!(
1913 format("sheet s { cell a: i32 = 1 filter _; }"),
1914 "sheet s {\n cell a: i32 = 1 filter _;\n}\n"
1915 );
1916 }
1917
1918 #[test]
1920 fn formats_a_cell_with_a_filter_referencing_a_cell() {
1921 assert_eq!(
1922 format("sheet s { cell hi: i32 = 100; cell a: i32 = 1 filter min(_, hi); }"),
1923 "sheet s {\n cell hi: i32 = 100;\n cell a: i32 = 1 filter min(_, hi);\n}\n"
1924 );
1925 }
1926
1927 #[test]
1929 fn format_is_idempotent_through_a_reparse_with_a_filter() {
1930 let source = "sheet s {\n cell a: i32 = 1 filter _;\n}";
1931 let once = format(source);
1932 let twice = format(&once);
1933 assert_eq!(once, twice);
1934 }
1935
1936 #[test]
1938 fn formats_a_filter() {
1939 let source = "sheet s { cell x: i32 = 0 filter 0..=10; }";
1940 let sheet = AdamAstParser::new().parse_str(source).unwrap();
1941 assert_eq!(
1942 format_sheet(&sheet, source),
1943 "sheet s {\n cell x: i32 = 0 filter 0..=10;\n}\n"
1944 );
1945 }
1946
1947 #[test]
1949 fn formats_a_requirement_with_no_label() {
1950 let source = "sheet s {\n cell x: i32 = 5 require {\n x > 0;\n };\n}";
1951 assert_eq!(format(source), format!("{source}\n"));
1952 }
1953
1954 #[test]
1956 fn format_source_formats_valid_source() {
1957 assert_eq!(
1958 format_source("sheet s{cell x:i32=1;}").unwrap(),
1959 "sheet s {\n cell x: i32 = 1;\n}\n"
1960 );
1961 }
1962
1963 #[test]
1965 fn format_source_errors_on_source_that_fails_to_parse() {
1966 assert!(matches!(
1967 format_source("not a sheet at all"),
1968 Err(FormatSourceError::Parse(_))
1969 ));
1970 }
1971
1972 #[test]
1974 fn format_source_errors_on_a_recovered_syntax_error() {
1975 assert!(matches!(
1976 format_source("sheet s { cell x unknown_syntax }"),
1977 Err(FormatSourceError::Recovered(_))
1978 ));
1979 }
1980
1981 #[test]
1986 fn formats_a_same_line_trailing_line_comment_on_a_cell() {
1987 let source =
1988 "sheet s {\n cell a: f64 = 2.0; // end of line comment\n cell b: f64 = 3.0;\n}";
1989 let expected =
1990 "sheet s {\n cell a: f64 = 2.0; // end of line comment\n cell b: f64 = 3.0;\n}\n";
1991 assert_eq!(format(source), expected);
1992 }
1993
1994 #[test]
1996 fn formats_a_same_line_trailing_block_comment_on_a_cell() {
1997 let source = "sheet s {\n cell a: f64 = 2.0; /* note */\n cell b: f64 = 3.0;\n}";
1998 let expected = "sheet s {\n cell a: f64 = 2.0; /* note */\n cell b: f64 = 3.0;\n}\n";
1999 assert_eq!(format(source), expected);
2000 }
2001
2002 #[test]
2004 fn formats_a_same_line_trailing_comment_on_the_last_item_before_the_sheets_closing_brace() {
2005 let source = "sheet s {\n cell a: i32 = 1; // note\n}";
2006 let expected = "sheet s {\n cell a: i32 = 1; // note\n}\n";
2007 assert_eq!(format(source), expected);
2008 }
2009
2010 #[test]
2012 fn formats_a_same_line_trailing_comment_on_a_binding() {
2013 let source =
2014 "sheet s {\n relationship {\n b := a; // note\n a := b;\n }\n}";
2015 let expected =
2016 "sheet s {\n relationship {\n b := a; // note\n a := b;\n }\n}\n";
2017 assert_eq!(format(source), expected);
2018 }
2019
2020 #[test]
2022 fn formats_a_same_line_block_comment_when_the_gap_has_no_newline_at_all() {
2023 let source = "sheet s {\n cell a: i32 = 1; /* note */ cell b: i32 = 2;\n}";
2027 let expected = "sheet s {\n cell a: i32 = 1; /* note */\n cell b: i32 = 2;\n}\n";
2028 assert_eq!(format(source), expected);
2029 }
2030
2031 #[test]
2033 fn formats_a_same_line_trailing_comment_before_a_conditional_branchs_own_closing_brace() {
2034 let source = "sheet s {\n conditional m {\n 0i32 => {\n relationship { b := a; } // note\n }\n }\n}";
2035 let expected = "sheet s {\n conditional m {\n 0i32 => {\n relationship {\n b := a;\n } // note\n }\n }\n}\n";
2036 assert_eq!(format(source), expected);
2037 }
2038
2039 #[test]
2041 fn formats_a_same_line_trailing_comment_before_a_default_arms_own_closing_brace() {
2042 let source = "sheet s {\n conditional m {\n _ => {\n relationship { b := a; } // note\n }\n }\n}";
2043 let expected = "sheet s {\n conditional m {\n _ => {\n relationship {\n b := a;\n } // note\n }\n }\n}\n";
2044 assert_eq!(format(source), expected);
2045 }
2046
2047 #[test]
2049 fn formats_a_same_line_trailing_comment_before_a_conditionals_own_closing_brace_with_only_branches()
2050 {
2051 let source = "sheet s {\n conditional m {\n 0i32 => { relationship { b := a; } } // note\n }\n}";
2052 let expected = "sheet s {\n conditional m {\n 0i32 => {\n relationship {\n b := a;\n }\n } // note\n }\n}\n";
2053 assert_eq!(format(source), expected);
2054 }
2055
2056 #[test]
2058 fn formats_a_same_line_trailing_comment_before_a_conditionals_own_closing_brace_with_a_default()
2059 {
2060 let source = "sheet s {\n conditional m {\n _ => { relationship { b := a; } } // note\n }\n}";
2061 let expected = "sheet s {\n conditional m {\n _ => {\n relationship {\n b := a;\n }\n } // note\n }\n}\n";
2062 assert_eq!(format(source), expected);
2063 }
2064
2065 #[test]
2067 fn formats_a_same_line_trailing_comment_before_a_requires_closing_brace() {
2068 let source =
2069 "sheet s {\n out area: f64 := w require {\n @c w <= 10.0; // note\n };\n}";
2070 let expected = "sheet s {\n out area: f64 := w require {\n @c w <= 10.0; // note\n };\n}\n";
2071 assert_eq!(format(source), expected);
2072 }
2073
2074 #[test]
2076 fn same_line_trailing_comment_formatting_is_idempotent_through_a_reparse() {
2077 let source =
2078 "sheet s {\n cell a: f64 = 2.0; // end of line comment\n cell b: f64 = 3.0;\n}";
2079 let once = format(source);
2080 let twice = format(&once);
2081 assert_eq!(once, twice);
2082 }
2083
2084 #[test]
2090 fn preserves_a_block_comment_between_a_cell_initializer_and_its_semicolon() {
2091 let source = "sheet s {\n cell a: i32 = 1 /* trailing */;\n}";
2092 assert_eq!(
2093 format(source),
2094 "sheet s {\n cell a: i32 = 1 /* trailing */;\n}\n"
2095 );
2096 }
2097
2098 #[test]
2100 fn preserves_a_block_comment_between_a_type_and_the_equals() {
2101 let source = "sheet s {\n cell a: i32 /* t */ = 1;\n}";
2102 assert_eq!(
2103 format(source),
2104 "sheet s {\n cell a: i32 /* t */ = 1;\n}\n"
2105 );
2106 }
2107
2108 #[test]
2110 fn a_declaration_with_no_intra_token_comments_is_unchanged() {
2111 assert_eq!(
2112 format("sheet s { cell a: i32 = 1; }"),
2113 "sheet s {\n cell a: i32 = 1;\n}\n"
2114 );
2115 }
2116
2117 #[test]
2119 fn a_cell_initializer_with_leading_inline_and_trailing_comments_round_trips_losslessly() {
2120 let source = "sheet s {\n cell a: i32 = /* p */ 1 /* q */ + 2 /* r */;\n}";
2121 let once = format_source(source).unwrap();
2122 for needle in ["/* p */", "/* q */", "/* r */"] {
2124 assert!(once.contains(needle), "comment {needle} lost: {once:?}");
2125 }
2126 let twice = format_source(&once).unwrap();
2128 assert_eq!(once, twice);
2129 }
2130
2131 #[test]
2133 fn preserves_a_line_comment_between_a_cell_initializer_and_its_semicolon() {
2134 let source = "sheet s {\n cell a: i32 = 1 // c\n ;\n}";
2138 let once = format_source(source).unwrap();
2139 assert!(once.contains("// c"), "comment must survive: {once:?}");
2140 assert!(
2141 once.trim_end().ends_with('}'),
2142 "the sheet's own closing brace must not have been commented out: {once:?}"
2143 );
2144 let twice = format_source(&once).unwrap();
2146 assert_eq!(once, twice, "formatting must be idempotent");
2147 }
2148
2149 #[test]
2151 fn preserves_a_line_comment_between_a_type_and_the_equals() {
2152 let source = "sheet s {\n cell a: i32 // c\n = 1;\n}";
2153 let once = format_source(source).unwrap();
2154 assert!(once.contains("// c"), "comment must survive: {once:?}");
2155 let twice = format_source(&once).unwrap();
2156 assert_eq!(once, twice, "formatting must be idempotent");
2157 }
2158
2159 #[test]
2161 fn preserves_comment_order_after_the_equals() {
2162 let source = "sheet s {\n cell a: i32 = /* after */ 1;\n}";
2163 let once = format_source(source).unwrap();
2164 let eq = once.find('=').expect("has =");
2165 let c = once.find("/* after */").expect("comment survives");
2166 assert!(c > eq, "comment must stay AFTER the =, got: {once:?}");
2167 }
2168
2169 #[test]
2171 fn preserves_comment_order_around_the_equals() {
2172 let source = "sheet s {\n cell a: i32 /* before */ = /* after */ 1;\n}";
2173 let once = format_source(source).unwrap();
2174 let before = once.find("/* before */").expect("before survives");
2175 let eq = once.find('=').expect("has =");
2176 let after = once.find("/* after */").expect("after survives");
2177 assert!(before < eq && eq < after, "order lost: {once:?}");
2178 }
2179
2180 #[test]
2182 fn preserves_a_comment_before_the_filter_keyword() {
2183 let source = "sheet s {\n cell a: i32 = 1 /* keep */ filter 0..=10;\n}";
2184 let once = format_source(source).unwrap();
2185 assert!(once.contains("/* keep */"), "comment dropped: {once:?}");
2186 }
2187
2188 #[test]
2190 fn preserves_a_comment_after_the_filter_body() {
2191 let source = "sheet s {\n cell a: i32 = 1 filter 0..=10 /* keep */;\n}";
2192 let once = format_source(source).unwrap();
2193 assert!(once.contains("/* keep */"), "comment dropped: {once:?}");
2194 }
2195
2196 #[test]
2198 fn preserves_a_comment_before_the_require_keyword() {
2199 let source = "sheet s {\n out area: f64 := w /* keep */ require {\n @c w <= 10.0;\n };\n}";
2200 let once = format_source(source).unwrap();
2201 assert!(once.contains("/* keep */"), "comment dropped: {once:?}");
2202 }
2203
2204 fn assert_comment_survives_and_is_idempotent(source: &str) {
2209 let once = format_source(source).unwrap();
2210 assert!(once.contains("/* keep */"), "comment dropped: {once:?}");
2211 let twice = format_source(&once).unwrap();
2212 assert_eq!(once, twice, "formatting must be idempotent: {once:?}");
2213 }
2214
2215 #[test]
2217 fn preserves_a_comment_between_the_filter_keyword_and_body() {
2218 assert_comment_survives_and_is_idempotent(
2219 "sheet s {\n cell a: i32 = 1 filter /* keep */ 0..=10;\n}",
2220 );
2221 }
2222
2223 #[test]
2225 fn preserves_a_comment_between_the_require_keyword_and_brace() {
2226 assert_comment_survives_and_is_idempotent(
2227 "sheet s {\n out area: f64 := w require /* keep */ {\n @c w <= 10.0;\n };\n}",
2228 );
2229 }
2230
2231 #[test]
2233 fn preserves_a_comment_between_a_branch_literal_and_the_fat_arrow() {
2234 assert_comment_survives_and_is_idempotent(
2235 "sheet s {\n conditional p {\n 0i32 /* keep */ => { relationship { b := a; } }\n }\n}",
2236 );
2237 }
2238
2239 #[test]
2241 fn preserves_a_comment_between_the_fat_arrow_and_a_branch_body() {
2242 assert_comment_survives_and_is_idempotent(
2243 "sheet s {\n conditional p {\n 0i32 => /* keep */ { relationship { b := a; } }\n }\n}",
2244 );
2245 }
2246
2247 #[test]
2249 fn preserves_a_comment_between_a_requirement_label_and_body() {
2250 assert_comment_survives_and_is_idempotent(
2251 "sheet s {\n out area: f64 := w require {\n @c /* keep */ w <= 10.0;\n };\n}",
2252 );
2253 }
2254
2255 #[test]
2257 fn preserves_a_comment_between_a_conditional_subject_and_its_brace() {
2258 assert_comment_survives_and_is_idempotent(
2259 "sheet s {\n conditional p /* keep */ {\n 0i32 => { relationship { b := a; } }\n }\n}",
2260 );
2261 }
2262
2263 #[test]
2265 fn preserves_a_comment_between_the_relationship_keyword_and_its_brace() {
2266 assert_comment_survives_and_is_idempotent(
2267 "sheet s {\n relationship /* keep */ {\n b := a;\n }\n}",
2268 );
2269 }
2270}