1use cel_parser::trivia::{line_column_to_byte, line_start_byte_offsets};
48use proc_macro2::LineColumn;
49
50use crate::ast::{
51 BindingDecl, CellDecl, ConditionalBranch, ConditionalDecl, ExprSpan, OutDecl, RelationshipDecl,
52 RequireBlock, RequirementDecl, Sheet, SourceDecl,
53};
54
55trait TriviaTarget {
57 fn opening_comments_mut(&mut self) -> Option<&mut Vec<crate::ast::Comment>> {
59 None
60 }
61
62 fn span(&self) -> ExprSpan;
64 fn set_leading_comment(&mut self, comment: crate::ast::Comment);
66 fn set_trailing_line_comment(&mut self, comment: crate::ast::Comment);
70 fn set_blank_line_before(&mut self, value: bool);
72}
73
74impl TriviaTarget for crate::ast::SheetItem {
75 fn opening_comments_mut(&mut self) -> Option<&mut Vec<crate::ast::Comment>> {
77 match self {
78 Self::RelationshipUse(call) => Some(&mut call.opening_comments),
79 _ => None,
80 }
81 }
82
83 fn span(&self) -> ExprSpan {
85 crate::ast::SheetItem::span(self)
86 }
87 fn set_leading_comment(&mut self, comment: crate::ast::Comment) {
89 crate::ast::SheetItem::set_leading_comment(self, comment)
90 }
91 fn set_trailing_line_comment(&mut self, comment: crate::ast::Comment) {
93 crate::ast::SheetItem::set_trailing_line_comment(self, comment)
94 }
95 fn set_blank_line_before(&mut self, value: bool) {
97 crate::ast::SheetItem::set_blank_line_before(self, value)
98 }
99}
100
101impl TriviaTarget for BindingDecl {
102 fn span(&self) -> ExprSpan {
104 self.span
105 }
106 fn set_leading_comment(&mut self, comment: crate::ast::Comment) {
108 self.leading_comment = Some(comment);
109 }
110 fn set_trailing_line_comment(&mut self, comment: crate::ast::Comment) {
112 self.trailing_line_comment = Some(comment);
113 }
114 fn set_blank_line_before(&mut self, value: bool) {
116 self.blank_line_before = value;
117 }
118}
119
120impl TriviaTarget for RelationshipDecl {
121 fn span(&self) -> ExprSpan {
123 self.span
124 }
125 fn set_leading_comment(&mut self, comment: crate::ast::Comment) {
127 self.leading_comment = Some(comment);
128 }
129 fn set_trailing_line_comment(&mut self, comment: crate::ast::Comment) {
131 self.trailing_line_comment = Some(comment);
132 }
133 fn set_blank_line_before(&mut self, value: bool) {
135 self.blank_line_before = value;
136 }
137}
138
139impl TriviaTarget for crate::ast::BranchItem {
140 fn opening_comments_mut(&mut self) -> Option<&mut Vec<crate::ast::Comment>> {
142 match self {
143 Self::RelationshipUse(call) => Some(&mut call.opening_comments),
144 _ => None,
145 }
146 }
147
148 fn span(&self) -> ExprSpan {
150 match self {
151 Self::Relationship(rel) => rel.span,
152 Self::RelationshipUse(call) => call.span,
153 }
154 }
155
156 fn set_leading_comment(&mut self, comment: crate::ast::Comment) {
158 match self {
159 Self::Relationship(rel) => rel.leading_comment = Some(comment),
160 Self::RelationshipUse(call) => call.leading_comment = Some(comment),
161 }
162 }
163
164 fn set_trailing_line_comment(&mut self, comment: crate::ast::Comment) {
166 match self {
167 Self::Relationship(rel) => rel.trailing_line_comment = Some(comment),
168 Self::RelationshipUse(call) => call.trailing_line_comment = Some(comment),
169 }
170 }
171
172 fn set_blank_line_before(&mut self, value: bool) {
174 match self {
175 Self::Relationship(rel) => rel.blank_line_before = value,
176 Self::RelationshipUse(call) => call.blank_line_before = value,
177 }
178 }
179}
180
181impl TriviaTarget for ConditionalBranch {
182 fn span(&self) -> ExprSpan {
184 self.span
185 }
186 fn set_leading_comment(&mut self, comment: crate::ast::Comment) {
188 self.leading_comment = Some(comment);
189 }
190 fn set_trailing_line_comment(&mut self, comment: crate::ast::Comment) {
192 self.trailing_line_comment = Some(comment);
193 }
194 fn set_blank_line_before(&mut self, value: bool) {
196 self.blank_line_before = value;
197 }
198}
199
200impl TriviaTarget for RequirementDecl {
201 fn span(&self) -> ExprSpan {
203 self.span
204 }
205 fn set_leading_comment(&mut self, comment: crate::ast::Comment) {
207 self.leading_comment = Some(comment);
208 }
209 fn set_trailing_line_comment(&mut self, comment: crate::ast::Comment) {
211 self.trailing_line_comment = Some(comment);
212 }
213 fn set_blank_line_before(&mut self, value: bool) {
215 self.blank_line_before = value;
216 }
217}
218
219trait TrailingTriviaTarget {
223 fn open_brace_span(&self) -> proc_macro2::Span;
226 fn close_span(&self) -> proc_macro2::Span;
228 fn set_trailing_comment(&mut self, comment: crate::ast::Comment);
230 fn set_blank_line_before_close(&mut self, value: bool);
232}
233
234impl TrailingTriviaTarget for Sheet {
235 fn open_brace_span(&self) -> proc_macro2::Span {
237 self.open_brace_span.end
238 }
239 fn close_span(&self) -> proc_macro2::Span {
241 self.span.end
242 }
243 fn set_trailing_comment(&mut self, comment: crate::ast::Comment) {
245 self.trailing_comment = Some(comment);
246 }
247 fn set_blank_line_before_close(&mut self, value: bool) {
249 self.blank_line_before_close = value;
250 }
251}
252
253impl TrailingTriviaTarget for RelationshipDecl {
254 fn open_brace_span(&self) -> proc_macro2::Span {
256 self.open_brace_span.end
257 }
258 fn close_span(&self) -> proc_macro2::Span {
260 self.span.end
261 }
262 fn set_trailing_comment(&mut self, comment: crate::ast::Comment) {
264 self.trailing_comment = Some(comment);
265 }
266 fn set_blank_line_before_close(&mut self, value: bool) {
268 self.blank_line_before_close = value;
269 }
270}
271
272impl TrailingTriviaTarget for RequireBlock {
273 fn open_brace_span(&self) -> proc_macro2::Span {
275 self.open_brace_span.end
276 }
277 fn close_span(&self) -> proc_macro2::Span {
279 self.span.end
280 }
281 fn set_trailing_comment(&mut self, comment: crate::ast::Comment) {
283 self.trailing_comment = Some(comment);
284 }
285 fn set_blank_line_before_close(&mut self, value: bool) {
287 self.blank_line_before_close = value;
288 }
289}
290
291impl TrailingTriviaTarget for ConditionalBranch {
292 fn open_brace_span(&self) -> proc_macro2::Span {
294 self.open_brace_span.end
295 }
296 fn close_span(&self) -> proc_macro2::Span {
298 self.span.end
299 }
300 fn set_trailing_comment(&mut self, comment: crate::ast::Comment) {
302 self.trailing_comment = Some(comment);
303 }
304 fn set_blank_line_before_close(&mut self, value: bool) {
306 self.blank_line_before_close = value;
307 }
308}
309
310impl TrailingTriviaTarget for crate::ast::DefaultBranch {
311 fn open_brace_span(&self) -> proc_macro2::Span {
313 self.open_brace_span.end
314 }
315 fn close_span(&self) -> proc_macro2::Span {
317 self.span.end
318 }
319 fn set_trailing_comment(&mut self, comment: crate::ast::Comment) {
321 self.trailing_comment = Some(comment);
322 }
323 fn set_blank_line_before_close(&mut self, value: bool) {
325 self.blank_line_before_close = value;
326 }
327}
328
329fn after_open_brace(source: &str, line_starts: &[usize], open_brace: proc_macro2::Span) -> usize {
339 line_column_to_byte(source, line_starts, open_brace.start()) + 1
340}
341
342fn before_close_brace(
349 source: &str,
350 line_starts: &[usize],
351 close_brace: proc_macro2::Span,
352) -> usize {
353 line_column_to_byte(source, line_starts, close_brace.end()) - 1
354}
355
356fn attach_trailing<C: TrailingTriviaTarget>(
373 source: &str,
374 line_starts: &[usize],
375 last_child_end: Option<LineColumn>,
376 container: &mut C,
377) -> Option<crate::ast::Comment> {
378 let start = match last_child_end {
379 Some(pos) => line_column_to_byte(source, line_starts, pos),
380 None => after_open_brace(source, line_starts, container.open_brace_span()),
381 };
382 let end = before_close_brace(source, line_starts, container.close_span());
383 if start >= end {
384 return None;
385 }
386 let gap_text = &source[start..end];
387 let (comment, blank_line_before_close, same_line) = analyze_gap(gap_text);
388 container.set_blank_line_before_close(blank_line_before_close);
389 match comment {
390 Some(comment) if same_line && last_child_end.is_some() => Some(comment),
391 Some(comment) => {
392 container.set_trailing_comment(comment);
393 None
394 }
395 None => None,
396 }
397}
398
399fn attach_conditional_trailing(
410 source: &str,
411 line_starts: &[usize],
412 cond: &mut ConditionalDecl,
413) -> Option<crate::ast::Comment> {
414 let has_last_child = cond.default.is_some() || !cond.branches.is_empty();
415 let start = if let Some(default) = &cond.default {
416 line_column_to_byte(source, line_starts, default.span.end.end())
417 } else if let Some(last_branch) = cond.branches.last() {
418 line_column_to_byte(source, line_starts, last_branch.span.end.end())
419 } else {
420 after_open_brace(source, line_starts, cond.open_brace_span.end)
421 };
422 let end = before_close_brace(source, line_starts, cond.span.end);
423 if start >= end {
424 return None;
425 }
426 let gap_text = &source[start..end];
427 let (comment, blank_line_before_close, same_line) = analyze_gap(gap_text);
428 cond.blank_line_before_close = blank_line_before_close;
429 match comment {
430 Some(comment) if same_line && has_last_child => Some(comment),
431 Some(comment) => {
432 cond.trailing_comment = Some(comment);
433 None
434 }
435 None => None,
436 }
437}
438
439pub fn attach_trivia(source: &str, sheet: &mut Sheet) {
459 let line_starts = line_start_byte_offsets(source);
460 let sheet_start = line_column_to_byte(source, &line_starts, sheet.span.start.start());
461 let (leading_comment, _, _) = analyze_gap(&source[..sheet_start]);
462 sheet.leading_comment = leading_comment;
463 attach_sheet_body(source, &line_starts, sheet);
464}
465
466fn attach_sheet_body(source: &str, line_starts: &[usize], sheet: &mut Sheet) {
472 attach_opening(source, line_starts, sheet.open_brace_span, &mut sheet.items);
473 attach_gaps(source, line_starts, &mut sheet.items);
474 let last_child_end = sheet.items.last().map(|item| item.span().end.end());
475 if let Some(comment) = attach_trailing(source, line_starts, last_child_end, sheet) {
476 sheet
477 .items
478 .last_mut()
479 .expect("same-line routing only returns Some when a last child exists")
480 .set_trailing_line_comment(comment);
481 }
482 for item in &mut sheet.items {
483 match item {
484 crate::ast::SheetItem::Relationship(rel) => {
485 attach_relationship(source, line_starts, rel)
486 }
487 crate::ast::SheetItem::Conditional(cond) => {
488 attach_conditional(source, line_starts, cond)
489 }
490 crate::ast::SheetItem::Out(out_decl) => attach_out(source, line_starts, out_decl),
491 crate::ast::SheetItem::Cell(cell) => attach_cell(source, line_starts, cell),
492 crate::ast::SheetItem::Source(source_decl) => {
493 attach_source(source, line_starts, source_decl)
494 }
495 crate::ast::SheetItem::RelationshipUse(_) | crate::ast::SheetItem::Error { .. } => {}
496 }
497 }
498}
499
500pub fn attach_module_trivia(source: &str, module: &mut crate::ast::Module) {
519 use cel_parser::trivia::{GapPiece, scan_gap};
520 let line_starts = line_start_byte_offsets(source);
521 module.comments.clear();
522 let mut previous_end = 0;
523 for item in &mut module.items {
524 let span = item.span();
525 let start = line_column_to_byte(source, &line_starts, span.start.start());
526 module.comments.push(
527 scan_gap(source.get(previous_end..start).unwrap_or(""), &[])
528 .into_iter()
529 .filter_map(|piece| match piece {
530 GapPiece::Comment(comment) => Some(comment),
531 GapPiece::Punct(_) => None,
532 })
533 .collect(),
534 );
535 previous_end = line_column_to_byte(source, &line_starts, span.end.end());
536 if let crate::ast::ModuleItem::Sheet(sheet) = item {
537 attach_sheet_body(source, &line_starts, sheet);
538 sheet.leading_comment = None;
539 }
540 }
541 module.comments.push(
542 scan_gap(&source[previous_end..], &[])
543 .into_iter()
544 .filter_map(|piece| match piece {
545 GapPiece::Comment(comment) => Some(comment),
546 GapPiece::Punct(_) => None,
547 })
548 .collect(),
549 );
550}
551
552fn attach_relationship(source: &str, line_starts: &[usize], rel: &mut RelationshipDecl) {
557 attach_opening(source, line_starts, rel.open_brace_span, &mut rel.bindings);
558 attach_gaps(source, line_starts, &mut rel.bindings);
559 let last_child_end = rel.bindings.last().map(|b| b.span().end.end());
560 if let Some(comment) = attach_trailing(source, line_starts, last_child_end, rel) {
561 rel.bindings
562 .last_mut()
563 .expect("same-line routing only returns Some when a last child exists")
564 .set_trailing_line_comment(comment);
565 }
566}
567
568fn attach_conditional(source: &str, line_starts: &[usize], cond: &mut ConditionalDecl) {
573 attach_opening(
574 source,
575 line_starts,
576 cond.open_brace_span,
577 &mut cond.branches,
578 );
579 attach_gaps(source, line_starts, &mut cond.branches);
580 for branch in &mut cond.branches {
581 attach_opening(
582 source,
583 line_starts,
584 branch.open_brace_span,
585 &mut branch.relationships,
586 );
587 attach_gaps(source, line_starts, &mut branch.relationships);
588 let last_child_end = branch.relationships.last().map(|r| r.span().end.end());
589 if let Some(comment) = attach_trailing(source, line_starts, last_child_end, branch) {
590 branch
591 .relationships
592 .last_mut()
593 .expect("same-line routing only returns Some when a last child exists")
594 .set_trailing_line_comment(comment);
595 }
596 for rel in &mut branch.relationships {
597 if let crate::ast::BranchItem::Relationship(rel) = rel {
598 attach_relationship(source, line_starts, rel);
599 }
600 }
601 }
602 if let Some(default) = &mut cond.default {
603 let start = cond.branches.last().map_or_else(
604 || after_open_brace(source, line_starts, cond.open_brace_span.end),
605 |branch| line_column_to_byte(source, line_starts, branch.span.end.end()),
606 );
607 let end = line_column_to_byte(source, line_starts, default.span.start.start());
608 let (comment, blank, same_line) = analyze_gap(source.get(start..end).unwrap_or(""));
609 default.blank_line_before = blank;
610 if same_line && let Some(previous) = cond.branches.last_mut() {
611 previous.trailing_line_comment = comment;
612 } else {
613 default.leading_comment = comment;
614 }
615 attach_opening(
616 source,
617 line_starts,
618 default.open_brace_span,
619 &mut default.relationships,
620 );
621 attach_gaps(source, line_starts, &mut default.relationships);
622 let last_child_end = default.relationships.last().map(|r| r.span().end.end());
623 if let Some(comment) = attach_trailing(source, line_starts, last_child_end, default) {
624 default
625 .relationships
626 .last_mut()
627 .expect("same-line routing only returns Some when a last child exists")
628 .set_trailing_line_comment(comment);
629 }
630 for rel in default.relationships.iter_mut() {
631 if let crate::ast::BranchItem::Relationship(rel) = rel {
632 attach_relationship(source, line_starts, rel);
633 }
634 }
635 }
636 if let Some(comment) = attach_conditional_trailing(source, line_starts, cond) {
637 if let Some(default) = &mut cond.default {
638 default.trailing_line_comment = Some(comment);
639 } else if let Some(last_branch) = cond.branches.last_mut() {
640 last_branch.set_trailing_line_comment(comment);
641 }
642 }
643}
644
645fn attach_require_block(source: &str, line_starts: &[usize], require: Option<&mut RequireBlock>) {
655 let Some(require) = require else {
656 return;
657 };
658 attach_opening(
659 source,
660 line_starts,
661 require.open_brace_span,
662 &mut require.requirements,
663 );
664 attach_gaps(source, line_starts, &mut require.requirements);
665 let last_child_end = require.requirements.last().map(|r| r.span().end.end());
666 if let Some(comment) = attach_trailing(source, line_starts, last_child_end, require) {
667 require
668 .requirements
669 .last_mut()
670 .expect("same-line routing only returns Some when a last child exists")
671 .set_trailing_line_comment(comment);
672 }
673}
674
675fn attach_out(source: &str, line_starts: &[usize], out_decl: &mut OutDecl) {
680 attach_require_block(source, line_starts, out_decl.require.as_mut());
681}
682
683fn attach_cell(source: &str, line_starts: &[usize], cell: &mut CellDecl) {
688 attach_require_block(source, line_starts, cell.require.as_mut());
689}
690
691fn attach_source(source: &str, line_starts: &[usize], source_decl: &mut SourceDecl) {
696 attach_require_block(source, line_starts, source_decl.require.as_mut());
697}
698
699fn attach_opening<T: TriviaTarget>(
704 source: &str,
705 line_starts: &[usize],
706 open: ExprSpan,
707 items: &mut [T],
708) {
709 if let Some(first) = items.first_mut() {
710 let start = after_open_brace(source, line_starts, open.end);
711 let end = line_column_to_byte(source, line_starts, first.span().start.start());
712 let gap = source.get(start..end).unwrap_or("");
713 if let Some(comments) = first.opening_comments_mut() {
714 use cel_parser::trivia::{GapPiece, scan_gap};
715 *comments = scan_gap(gap, &[])
716 .into_iter()
717 .filter_map(|piece| match piece {
718 GapPiece::Comment(comment) => Some(comment),
719 GapPiece::Punct(_) => None,
720 })
721 .collect();
722 return;
723 }
724 let (comment, blank, _) = analyze_gap(gap);
725 if let Some(comment) = comment {
726 first.set_leading_comment(comment);
727 }
728 first.set_blank_line_before(blank);
729 }
730}
731
732fn attach_gaps<T: TriviaTarget>(source: &str, line_starts: &[usize], items: &mut [T]) {
742 if items.len() < 2 {
743 return;
744 }
745 for i in 1..items.len() {
746 let start = line_column_to_byte(source, line_starts, items[i - 1].span().end.end());
747 let end = line_column_to_byte(source, line_starts, items[i].span().start.start());
748 let gap_text = &source[start..end];
749 let (comment, blank_line_before, same_line) = analyze_gap(gap_text);
750 items[i].set_blank_line_before(blank_line_before);
751 if let Some(comment) = comment {
752 if same_line {
753 items[i - 1].set_trailing_line_comment(comment);
754 } else {
755 items[i].set_leading_comment(comment);
756 }
757 }
758 }
759}
760
761fn has_blank_line(region: &str) -> bool {
765 region
766 .split_inclusive('\n')
767 .skip(1)
768 .any(|line| line.ends_with('\n') && line.trim().is_empty())
769}
770
771fn analyze_gap(gap: &str) -> (Option<crate::ast::Comment>, bool, bool) {
778 use crate::ast::Comment;
779 use cel_parser::trivia::{GapPiece, scan_gap};
780 let mut comments = scan_gap(gap, &[])
781 .into_iter()
782 .filter_map(|piece| match piece {
783 GapPiece::Comment(comment) => Some(comment),
784 GapPiece::Punct(_) => None,
785 });
786 let first = comments.next();
787 let prefix = &gap[..gap.len() - gap.trim_start().len()];
788 let same_line = first.is_some() && !prefix.contains('\n');
789 if let Some(second) = comments.next() {
790 let text = first
792 .into_iter()
793 .chain(std::iter::once(second))
794 .chain(comments)
795 .enumerate()
796 .fold(String::new(), |mut joined, (index, comment)| {
797 if index != 0 {
798 joined.push('\n');
799 }
800 match comment {
801 Comment::Line(text) | Comment::Block(text) => joined.push_str(&text),
802 }
803 joined
804 });
805 return (Some(Comment::Line(text)), has_blank_line(gap), same_line);
806 }
807 let blank_line_before = if first.is_some() {
808 let suffix = &gap[gap.trim_end().len()..];
809 has_blank_line(prefix) || has_blank_line(suffix)
810 } else {
811 has_blank_line(gap)
812 };
813 (first, blank_line_before, same_line)
814}
815
816#[cfg(test)]
817mod tests {
818 use super::*;
819 use crate::AdamAstParser;
820
821 #[test]
823 fn mixed_comment_runs_preserve_empty_first_payload() {
824 let (comment, _, _) = analyze_gap("\n//\n/* second */\n");
825 assert_eq!(comment, Some(crate::ast::Comment::Line("\nsecond".into())));
826 }
827
828 #[test]
830 fn mixed_comment_blank_lines_allow_whitespace_and_crlf() {
831 for separator in ["\n \t\n", "\r\n \t\r\n"] {
832 let gap = format!("\n// first{separator}/* second */\n ");
833 let (comment, blank, same_line) = analyze_gap(&gap);
834 assert!(blank, "{gap:?}");
835 assert!(!same_line);
836 assert_eq!(
837 comment,
838 Some(crate::ast::Comment::Line("first\nsecond".into()))
839 );
840 }
841 let (_, blank, _) = analyze_gap("\n// first\n/* second */\n ");
842 assert!(!blank, "next-item indentation is not a blank line");
843 }
844
845 #[test]
847 fn attaches_a_multi_line_comment_block() {
848 let source = "sheet s {\n cell a: i32 = 1;\n // line one\n // line two\n cell b: i32 = 2;\n}";
849 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
850 attach_trivia(source, &mut sheet);
851 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
852 panic!("expected Cell");
853 };
854 assert_eq!(
855 b.leading_comment,
856 Some(crate::ast::Comment::Line("line one\nline two".to_string()))
857 );
858 }
859
860 #[test]
862 fn attaches_a_single_line_block_comment() {
863 let source =
864 "sheet s {\n cell a: i32 = 1;\n /* the total */\n cell b: i32 = 2;\n}";
865 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
866 attach_trivia(source, &mut sheet);
867 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
868 panic!("expected Cell");
869 };
870 assert_eq!(
871 b.leading_comment,
872 Some(crate::ast::Comment::Block("the total".to_string()))
873 );
874 }
875
876 #[test]
878 fn attaches_a_multi_line_block_comment() {
879 let source = "sheet s {\n cell a: i32 = 1;\n /*\n line one\n line two\n */\n cell b: i32 = 2;\n}";
880 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
881 attach_trivia(source, &mut sheet);
882 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
883 panic!("expected Cell");
884 };
885 assert_eq!(
886 b.leading_comment,
887 Some(crate::ast::Comment::Block("line one\nline two".to_string()))
888 );
889 }
890
891 #[test]
893 fn attaches_the_issue_105_license_header_repro_as_a_block_comment() {
894 let source =
895 "/*\n Copyright 2013 Adobe\n ...\n*/\nsheet s {\n cell a: i32 = 1;\n}";
896 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
897 attach_trivia(source, &mut sheet);
898 assert_eq!(
899 sheet.leading_comment,
900 Some(crate::ast::Comment::Block(
901 "Copyright 2013 Adobe\n...".to_string()
902 ))
903 );
904 }
905
906 #[test]
908 fn preserves_a_comment_separated_by_a_blank_line() {
909 let source =
910 "sheet s {\n cell a: i32 = 1;\n // stale comment\n\n cell b: i32 = 2;\n}";
911 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
912 attach_trivia(source, &mut sheet);
913 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
914 panic!("expected Cell");
915 };
916 assert_eq!(
917 b.leading_comment,
918 Some(crate::ast::Comment::Line("stale comment".into()))
919 );
920 }
921
922 #[test]
924 fn no_comment_in_the_gap_leaves_leading_comment_none() {
925 let source = "sheet s {\n cell a: i32 = 1;\n cell b: i32 = 2;\n}";
926 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
927 attach_trivia(source, &mut sheet);
928 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
929 panic!("expected Cell");
930 };
931 assert_eq!(b.leading_comment, None);
932 }
933
934 #[test]
936 fn attaches_comments_correctly_across_more_than_one_gap() {
937 let source = "sheet s {\n cell a: i32 = 1;\n // first\n cell b: i32 = 2;\n // second\n cell c: i32 = 3;\n}";
938 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
939 attach_trivia(source, &mut sheet);
940 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
941 panic!("expected Cell");
942 };
943 assert_eq!(
944 b.leading_comment,
945 Some(crate::ast::Comment::Line("first".to_string()))
946 );
947 let crate::ast::SheetItem::Cell(c) = &sheet.items[2] else {
948 panic!("expected Cell");
949 };
950 assert_eq!(
951 c.leading_comment,
952 Some(crate::ast::Comment::Line("second".to_string()))
953 );
954 }
955
956 #[test]
958 fn attaches_a_comment_preceding_a_recovered_error_item() {
959 let source = "sheet s {\n cell a: i32 = 1;\n // fix me\n cell bad unknown_syntax\n cell c: i32 = 2;\n}";
960 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
961 attach_trivia(source, &mut sheet);
962 let crate::ast::SheetItem::Error {
963 leading_comment, ..
964 } = &sheet.items[1]
965 else {
966 panic!("expected Error");
967 };
968 assert_eq!(
969 leading_comment,
970 &Some(crate::ast::Comment::Line("fix me".to_string()))
971 );
972 }
973
974 #[test]
976 fn recovery_span_that_abuts_the_next_keyword_does_not_invert_the_gap() {
977 let source = "sheet s { cell bad relationship { y := x; } }";
978 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
979 attach_trivia(source, &mut sheet); assert!(matches!(
981 sheet.items[0],
982 crate::ast::SheetItem::Error { .. }
983 ));
984 assert!(matches!(
985 sheet.items[1],
986 crate::ast::SheetItem::Relationship(_)
987 ));
988 }
989
990 #[test]
992 fn sets_blank_line_before_true_when_a_blank_line_separates_two_items() {
993 let source = "sheet s {\n cell a: i32 = 1;\n\n cell b: i32 = 2;\n}";
994 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
995 attach_trivia(source, &mut sheet);
996 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
997 panic!("expected Cell");
998 };
999 assert!(b.blank_line_before);
1000 }
1001
1002 #[test]
1004 fn sets_blank_line_before_false_when_items_are_packed_tight() {
1005 let source = "sheet s {\n cell a: i32 = 1;\n cell b: i32 = 2;\n}";
1006 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1007 attach_trivia(source, &mut sheet);
1008 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
1009 panic!("expected Cell");
1010 };
1011 assert!(!b.blank_line_before);
1012 }
1013
1014 #[test]
1016 fn a_run_of_several_blank_lines_still_just_sets_the_flag_true() {
1017 let source = "sheet s {\n cell a: i32 = 1;\n\n\n\n cell b: i32 = 2;\n}";
1018 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1019 attach_trivia(source, &mut sheet);
1020 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
1021 panic!("expected Cell");
1022 };
1023 assert!(b.blank_line_before);
1024 }
1025
1026 #[test]
1028 fn blank_line_before_an_attached_comment_still_sets_the_flag_true() {
1029 let source = "sheet s {\n cell a: i32 = 1;\n\n // c\n cell b: i32 = 2;\n}";
1033 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1034 attach_trivia(source, &mut sheet);
1035 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
1036 panic!("expected Cell");
1037 };
1038 assert_eq!(
1039 b.leading_comment,
1040 Some(crate::ast::Comment::Line("c".to_string()))
1041 );
1042 assert!(b.blank_line_before);
1043 }
1044
1045 #[test]
1047 fn attaches_a_comment_and_blank_line_to_a_binding_inside_a_relationship() {
1048 let source = "sheet s {\n relationship {\n b := a;\n\n // second\n a := b;\n }\n}";
1049 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1050 attach_trivia(source, &mut sheet);
1051 let crate::ast::SheetItem::Relationship(rel) = &sheet.items[0] else {
1052 panic!("expected Relationship");
1053 };
1054 assert_eq!(
1055 rel.bindings[1].leading_comment,
1056 Some(crate::ast::Comment::Line("second".to_string()))
1057 );
1058 assert!(rel.bindings[1].blank_line_before);
1059 }
1060
1061 #[test]
1063 fn attaches_a_comment_to_a_conditional_branch() {
1064 let source = "sheet s {\n conditional m {\n 0i32 => { relationship { b := a; } }\n // one\n 1i32 => { relationship { b := a; } }\n }\n}";
1065 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1066 attach_trivia(source, &mut sheet);
1067 let crate::ast::SheetItem::Conditional(cond) = &sheet.items[0] else {
1068 panic!("expected Conditional");
1069 };
1070 assert_eq!(
1071 cond.branches[1].leading_comment,
1072 Some(crate::ast::Comment::Line("one".to_string()))
1073 );
1074 }
1075
1076 #[test]
1078 fn attaches_a_comment_to_a_relationship_nested_inside_a_conditional_branch() {
1079 let source = "sheet s {\n conditional m {\n 0i32 => {\n relationship { b := a; }\n // second\n relationship { a := b; }\n }\n }\n}";
1080 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1081 attach_trivia(source, &mut sheet);
1082 let crate::ast::SheetItem::Conditional(cond) = &sheet.items[0] else {
1083 panic!("expected Conditional");
1084 };
1085 assert_eq!(
1086 match &cond.branches[0].relationships[1] {
1087 crate::ast::BranchItem::Relationship(rel) => &rel.leading_comment,
1088 crate::ast::BranchItem::RelationshipUse(call) => &call.leading_comment,
1089 },
1090 &Some(crate::ast::Comment::Line("second".to_string()))
1091 );
1092 }
1093
1094 #[test]
1096 fn attaches_a_comment_to_a_relationship_nested_inside_the_default_branch() {
1097 let source = "sheet s {\n conditional m {\n _ => {\n relationship { b := a; }\n // second\n relationship { a := b; }\n }\n }\n}";
1098 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1099 attach_trivia(source, &mut sheet);
1100 let crate::ast::SheetItem::Conditional(cond) = &sheet.items[0] else {
1101 panic!("expected Conditional");
1102 };
1103 let default = cond.default.as_ref().expect("default branch present");
1104 assert_eq!(
1105 match &default.relationships[1] {
1106 crate::ast::BranchItem::Relationship(rel) => &rel.leading_comment,
1107 crate::ast::BranchItem::RelationshipUse(call) => &call.leading_comment,
1108 },
1109 &Some(crate::ast::Comment::Line("second".to_string()))
1110 );
1111 }
1112
1113 #[test]
1115 fn attaches_a_leading_comment_before_the_sheet_itself() {
1116 let source = "// file header\nsheet s {\n cell a: i32 = 1;\n}";
1117 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1118 attach_trivia(source, &mut sheet);
1119 assert_eq!(
1120 sheet.leading_comment,
1121 Some(crate::ast::Comment::Line("file header".to_string()))
1122 );
1123 }
1124
1125 #[test]
1127 fn attaches_a_multi_line_leading_comment_before_the_sheet_itself() {
1128 let source = "// line one\n// line two\nsheet s {\n cell a: i32 = 1;\n}";
1129 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1130 attach_trivia(source, &mut sheet);
1131 assert_eq!(
1132 sheet.leading_comment,
1133 Some(crate::ast::Comment::Line("line one\nline two".to_string()))
1134 );
1135 }
1136
1137 #[test]
1139 fn no_leading_comment_before_the_sheet_leaves_it_none() {
1140 let source = "sheet s {\n cell a: i32 = 1;\n}";
1141 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1142 attach_trivia(source, &mut sheet);
1143 assert_eq!(sheet.leading_comment, None);
1144 }
1145
1146 #[test]
1148 fn attaches_a_comment_to_a_requirement_inside_an_out_declaration() {
1149 let source = "sheet s {\n out area: f64 := width * height require {\n @max_area width * height <= max_area;\n // second\n @c width <= 10.0;\n };\n}";
1159 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1160 attach_trivia(source, &mut sheet);
1161 let crate::ast::SheetItem::Out(out) = &sheet.items[0] else {
1162 panic!("expected Out");
1163 };
1164 let require = out.require.as_ref().expect("require block present");
1165 assert_eq!(
1166 require.requirements[1].leading_comment,
1167 Some(crate::ast::Comment::Line("second".to_string()))
1168 );
1169 }
1170
1171 #[test]
1173 fn recovers_a_trailing_comment_before_a_sheets_closing_brace() {
1174 let source = "sheet s {\n cell a: i32 = 1;\n // trailing\n}";
1175 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1176 attach_trivia(source, &mut sheet);
1177 assert_eq!(
1178 sheet.trailing_comment,
1179 Some(crate::ast::Comment::Line("trailing".to_string()))
1180 );
1181 }
1182
1183 #[test]
1185 fn recovers_a_trailing_comment_in_an_empty_relationship_block() {
1186 let source = "sheet s {\n relationship {\n // only this\n }\n}";
1187 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1188 attach_trivia(source, &mut sheet);
1189 let crate::ast::SheetItem::Relationship(rel) = &sheet.items[0] else {
1190 panic!("expected Relationship");
1191 };
1192 assert_eq!(
1193 rel.trailing_comment,
1194 Some(crate::ast::Comment::Line("only this".to_string()))
1195 );
1196 }
1197
1198 #[test]
1200 fn recovers_a_trailing_comment_before_a_relationships_closing_brace() {
1201 let source =
1202 "sheet s {\n relationship {\n b := a;\n // trailing\n }\n}";
1203 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1204 attach_trivia(source, &mut sheet);
1205 let crate::ast::SheetItem::Relationship(rel) = &sheet.items[0] else {
1206 panic!("expected Relationship");
1207 };
1208 assert_eq!(
1209 rel.trailing_comment,
1210 Some(crate::ast::Comment::Line("trailing".to_string()))
1211 );
1212 }
1213
1214 #[test]
1216 fn recovers_a_trailing_comment_before_a_conditional_branchs_closing_brace() {
1217 let source = "sheet s {\n conditional m {\n 0i32 => {\n relationship { b := a; }\n // trailing\n }\n }\n}";
1218 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1219 attach_trivia(source, &mut sheet);
1220 let crate::ast::SheetItem::Conditional(cond) = &sheet.items[0] else {
1221 panic!("expected Conditional");
1222 };
1223 assert_eq!(
1224 cond.branches[0].trailing_comment,
1225 Some(crate::ast::Comment::Line("trailing".to_string()))
1226 );
1227 }
1228
1229 #[test]
1231 fn recovers_a_trailing_comment_in_a_default_arm() {
1232 let source = "sheet s {\n conditional m {\n _ => {\n relationship { b := a; }\n // trailing\n }\n }\n}";
1233 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1234 attach_trivia(source, &mut sheet);
1235 let crate::ast::SheetItem::Conditional(cond) = &sheet.items[0] else {
1236 panic!("expected Conditional");
1237 };
1238 let default = cond.default.as_ref().expect("default branch present");
1239 assert_eq!(
1240 default.trailing_comment,
1241 Some(crate::ast::Comment::Line("trailing".to_string()))
1242 );
1243 }
1244
1245 #[test]
1247 fn recovers_a_trailing_comment_before_a_conditionals_own_closing_brace() {
1248 let source = "sheet s {\n conditional m {\n 0i32 => { relationship { b := a; } }\n // trailing\n }\n}";
1249 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1250 attach_trivia(source, &mut sheet);
1251 let crate::ast::SheetItem::Conditional(cond) = &sheet.items[0] else {
1252 panic!("expected Conditional");
1253 };
1254 assert_eq!(
1255 cond.trailing_comment,
1256 Some(crate::ast::Comment::Line("trailing".to_string()))
1257 );
1258 }
1259
1260 #[test]
1268 fn recovers_a_trailing_comment_before_a_requires_closing_brace() {
1269 let source = "sheet s {\n out area: f64 := w require {\n @c w <= 10.0;\n // trailing\n };\n}";
1270 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1271 attach_trivia(source, &mut sheet);
1272 let crate::ast::SheetItem::Out(out) = &sheet.items[0] else {
1273 panic!("expected Out");
1274 };
1275 let require = out.require.as_ref().expect("require block present");
1276 assert_eq!(
1277 require.trailing_comment,
1278 Some(crate::ast::Comment::Line("trailing".to_string()))
1279 );
1280 }
1281
1282 #[test]
1284 fn attaches_a_comment_to_a_requirement_inside_a_cell_declaration() {
1285 let source = "sheet s {\n cell a: i32 = 1 require {\n @r1 a > 0;\n // second\n @r2 a < 10;\n };\n}";
1289 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1290 attach_trivia(source, &mut sheet);
1291 let crate::ast::SheetItem::Cell(cell) = &sheet.items[0] else {
1292 panic!("expected Cell");
1293 };
1294 let require = cell.require.as_ref().expect("require block present");
1295 assert_eq!(
1296 require.requirements[1].leading_comment,
1297 Some(crate::ast::Comment::Line("second".to_string()))
1298 );
1299 }
1300
1301 #[test]
1303 fn recovers_a_trailing_comment_before_a_cells_requires_closing_brace() {
1304 let source = "sheet s {\n cell a: i32 = 1 require {\n @r a > 0;\n // trailing\n };\n}";
1307 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1308 attach_trivia(source, &mut sheet);
1309 let crate::ast::SheetItem::Cell(cell) = &sheet.items[0] else {
1310 panic!("expected Cell");
1311 };
1312 let require = cell.require.as_ref().expect("require block present");
1313 assert_eq!(
1314 require.trailing_comment,
1315 Some(crate::ast::Comment::Line("trailing".to_string()))
1316 );
1317 }
1318
1319 #[test]
1321 fn attaches_a_comment_to_a_requirement_inside_a_source_declaration() {
1322 let source = "sheet s {\n source a: i32 = 1 require {\n @r1 a > 0;\n // second\n @r2 a < 10;\n };\n}";
1325 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1326 attach_trivia(source, &mut sheet);
1327 let crate::ast::SheetItem::Source(source_decl) = &sheet.items[0] else {
1328 panic!("expected Source");
1329 };
1330 let require = source_decl.require.as_ref().expect("require block present");
1331 assert_eq!(
1332 require.requirements[1].leading_comment,
1333 Some(crate::ast::Comment::Line("second".to_string()))
1334 );
1335 }
1336
1337 #[test]
1339 fn recovers_a_trailing_comment_before_a_sources_requires_closing_brace() {
1340 let source = "sheet s {\n source a: i32 = 1 require {\n @r a > 0;\n // trailing\n };\n}";
1343 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1344 attach_trivia(source, &mut sheet);
1345 let crate::ast::SheetItem::Source(source_decl) = &sheet.items[0] else {
1346 panic!("expected Source");
1347 };
1348 let require = source_decl.require.as_ref().expect("require block present");
1349 assert_eq!(
1350 require.trailing_comment,
1351 Some(crate::ast::Comment::Line("trailing".to_string()))
1352 );
1353 }
1354
1355 #[test]
1357 fn sets_blank_line_before_close_when_a_blank_line_precedes_the_closing_brace() {
1358 let source = "sheet s {\n cell a: i32 = 1;\n\n}";
1359 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1360 attach_trivia(source, &mut sheet);
1361 assert!(sheet.blank_line_before_close);
1362 }
1363
1364 #[test]
1366 fn no_trailing_comment_leaves_trailing_comment_none() {
1367 let source = "sheet s {\n cell a: i32 = 1;\n}";
1368 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1369 attach_trivia(source, &mut sheet);
1370 assert_eq!(sheet.trailing_comment, None);
1371 }
1372
1373 #[test]
1378 fn a_same_line_trailing_line_comment_attaches_to_the_previous_cell_not_the_next() {
1379 let source =
1380 "sheet s {\n cell a: f64 = 2.0; // end of line comment\n cell b: f64 = 3.0;\n}";
1381 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1382 attach_trivia(source, &mut sheet);
1383 let crate::ast::SheetItem::Cell(a) = &sheet.items[0] else {
1384 panic!("expected Cell");
1385 };
1386 assert_eq!(
1387 a.trailing_line_comment,
1388 Some(crate::ast::Comment::Line("end of line comment".to_string()))
1389 );
1390 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
1391 panic!("expected Cell");
1392 };
1393 assert_eq!(b.leading_comment, None);
1394 assert!(!b.blank_line_before);
1395 }
1396
1397 #[test]
1399 fn a_same_line_trailing_block_comment_attaches_to_the_previous_cell_not_the_next() {
1400 let source = "sheet s {\n cell a: f64 = 2.0; /* note */\n cell b: f64 = 3.0;\n}";
1401 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1402 attach_trivia(source, &mut sheet);
1403 let crate::ast::SheetItem::Cell(a) = &sheet.items[0] else {
1404 panic!("expected Cell");
1405 };
1406 assert_eq!(
1407 a.trailing_line_comment,
1408 Some(crate::ast::Comment::Block("note".to_string()))
1409 );
1410 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
1411 panic!("expected Cell");
1412 };
1413 assert_eq!(b.leading_comment, None);
1414 }
1415
1416 #[test]
1418 fn a_comment_on_its_own_line_still_attaches_as_a_leading_comment() {
1419 let source = "sheet s {\n cell a: i32 = 1;\n // the total\n cell b: i32 = 2;\n}";
1422 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1423 attach_trivia(source, &mut sheet);
1424 let crate::ast::SheetItem::Cell(a) = &sheet.items[0] else {
1425 panic!("expected Cell");
1426 };
1427 assert_eq!(a.trailing_line_comment, None);
1428 let crate::ast::SheetItem::Cell(b) = &sheet.items[1] else {
1429 panic!("expected Cell");
1430 };
1431 assert_eq!(
1432 b.leading_comment,
1433 Some(crate::ast::Comment::Line("the total".to_string()))
1434 );
1435 }
1436
1437 #[test]
1439 fn a_same_line_trailing_comment_attaches_to_a_binding_inside_a_relationship() {
1440 let source =
1441 "sheet s {\n relationship {\n b := a; // note\n a := b;\n }\n}";
1442 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1443 attach_trivia(source, &mut sheet);
1444 let crate::ast::SheetItem::Relationship(rel) = &sheet.items[0] else {
1445 panic!("expected Relationship");
1446 };
1447 assert_eq!(
1448 rel.bindings[0].trailing_line_comment,
1449 Some(crate::ast::Comment::Line("note".to_string()))
1450 );
1451 assert_eq!(rel.bindings[1].leading_comment, None);
1452 }
1453
1454 #[test]
1456 fn a_same_line_trailing_comment_before_a_sheets_closing_brace_attaches_to_the_last_item() {
1457 let source = "sheet s {\n cell a: i32 = 1; // note\n}";
1458 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1459 attach_trivia(source, &mut sheet);
1460 let crate::ast::SheetItem::Cell(a) = &sheet.items[0] else {
1461 panic!("expected Cell");
1462 };
1463 assert_eq!(
1464 a.trailing_line_comment,
1465 Some(crate::ast::Comment::Line("note".to_string()))
1466 );
1467 assert_eq!(sheet.trailing_comment, None);
1468 }
1469
1470 #[test]
1472 fn same_line_gap_with_no_newline_still_recovers_a_block_comment() {
1473 let source = "sheet s {\n cell a: i32 = 1; /* note */ cell b: i32 = 2;\n}";
1477 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1478 attach_trivia(source, &mut sheet);
1479 let crate::ast::SheetItem::Cell(a) = &sheet.items[0] else {
1480 panic!("expected Cell");
1481 };
1482 assert_eq!(
1483 a.trailing_line_comment,
1484 Some(crate::ast::Comment::Block("note".to_string()))
1485 );
1486 }
1487
1488 #[test]
1490 fn a_same_line_trailing_comment_before_a_relationships_closing_brace_attaches_to_the_last_binding()
1491 {
1492 let source = "sheet s {\n relationship {\n b := a; // note\n }\n}";
1493 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1494 attach_trivia(source, &mut sheet);
1495 let crate::ast::SheetItem::Relationship(rel) = &sheet.items[0] else {
1496 panic!("expected Relationship");
1497 };
1498 assert_eq!(
1499 rel.bindings[0].trailing_line_comment,
1500 Some(crate::ast::Comment::Line("note".to_string()))
1501 );
1502 assert_eq!(rel.trailing_comment, None);
1503 }
1504
1505 #[test]
1507 fn a_same_line_trailing_comment_before_a_conditional_branchs_own_closing_brace_attaches_to_the_last_relationship()
1508 {
1509 let source = "sheet s {\n conditional m {\n 0i32 => {\n relationship { b := a; } // note\n }\n }\n}";
1510 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1511 attach_trivia(source, &mut sheet);
1512 let crate::ast::SheetItem::Conditional(cond) = &sheet.items[0] else {
1513 panic!("expected Conditional");
1514 };
1515 assert_eq!(
1516 match &cond.branches[0].relationships[0] {
1517 crate::ast::BranchItem::Relationship(rel) => &rel.trailing_line_comment,
1518 crate::ast::BranchItem::RelationshipUse(call) => &call.trailing_line_comment,
1519 },
1520 &Some(crate::ast::Comment::Line("note".to_string()))
1521 );
1522 assert_eq!(cond.branches[0].trailing_comment, None);
1523 }
1524
1525 #[test]
1527 fn a_same_line_trailing_comment_before_a_default_arms_own_closing_brace_attaches_to_the_last_relationship()
1528 {
1529 let source = "sheet s {\n conditional m {\n _ => {\n relationship { b := a; } // note\n }\n }\n}";
1530 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1531 attach_trivia(source, &mut sheet);
1532 let crate::ast::SheetItem::Conditional(cond) = &sheet.items[0] else {
1533 panic!("expected Conditional");
1534 };
1535 let default = cond.default.as_ref().expect("default branch present");
1536 assert_eq!(
1537 match &default.relationships[0] {
1538 crate::ast::BranchItem::Relationship(rel) => &rel.trailing_line_comment,
1539 crate::ast::BranchItem::RelationshipUse(call) => &call.trailing_line_comment,
1540 },
1541 &Some(crate::ast::Comment::Line("note".to_string()))
1542 );
1543 assert_eq!(default.trailing_comment, None);
1544 }
1545
1546 #[test]
1548 fn a_same_line_trailing_comment_before_a_conditionals_own_closing_brace_with_only_branches_attaches_to_the_last_branch()
1549 {
1550 let source = "sheet s {\n conditional m {\n 0i32 => { relationship { b := a; } } // note\n }\n}";
1551 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1552 attach_trivia(source, &mut sheet);
1553 let crate::ast::SheetItem::Conditional(cond) = &sheet.items[0] else {
1554 panic!("expected Conditional");
1555 };
1556 assert_eq!(
1557 cond.branches[0].trailing_line_comment,
1558 Some(crate::ast::Comment::Line("note".to_string()))
1559 );
1560 assert_eq!(cond.trailing_comment, None);
1561 }
1562
1563 #[test]
1565 fn a_same_line_trailing_comment_before_a_conditionals_own_closing_brace_with_a_default_attaches_to_the_default()
1566 {
1567 let source = "sheet s {\n conditional m {\n _ => { relationship { b := a; } } // note\n }\n}";
1568 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1569 attach_trivia(source, &mut sheet);
1570 let crate::ast::SheetItem::Conditional(cond) = &sheet.items[0] else {
1571 panic!("expected Conditional");
1572 };
1573 let default = cond.default.as_ref().expect("default branch present");
1574 assert_eq!(
1575 default.trailing_line_comment,
1576 Some(crate::ast::Comment::Line("note".to_string()))
1577 );
1578 assert_eq!(cond.trailing_comment, None);
1579 }
1580
1581 #[test]
1583 fn a_same_line_trailing_comment_before_a_requires_closing_brace_attaches_to_the_last_requirement()
1584 {
1585 let source =
1586 "sheet s {\n out area: f64 := w require {\n @c w <= 10.0; // note\n };\n}";
1587 let mut sheet = AdamAstParser::new().parse_str(source).unwrap();
1588 attach_trivia(source, &mut sheet);
1589 let crate::ast::SheetItem::Out(out) = &sheet.items[0] else {
1590 panic!("expected Out");
1591 };
1592 let require = out.require.as_ref().expect("require block present");
1593 assert_eq!(
1594 require.requirements[0].trailing_line_comment,
1595 Some(crate::ast::Comment::Line("note".to_string()))
1596 );
1597 assert_eq!(require.trailing_comment, None);
1598 }
1599}