1#![allow(clippy::too_many_arguments)]
2use crate::{
3 FormatterConfig, InlineConfig,
4 pp::{self, BreakToken, SIZE_INFINITY, Token},
5 state::sol::BinOpGroup,
6};
7use foundry_common::{
8 comments::{Comment, CommentStyle, Comments, estimate_line_width, line_with_tabs},
9 iter::IterDelimited,
10};
11use foundry_config::fmt::{DocCommentStyle, IndentStyle};
12use solar::parse::{
13 ast::{self, Span},
14 interface::{BytePos, SourceMap},
15 token,
16};
17use std::{borrow::Cow, ops::Deref, sync::Arc};
18
19mod common;
20mod sol;
21mod yul;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub(super) enum CallContextKind {
26 Chained,
28
29 Nested,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub(super) struct CallContext {
36 pub(super) kind: CallContextKind,
38
39 pub(super) size: usize,
41}
42
43impl CallContext {
44 pub(super) fn nested(size: usize) -> Self {
45 Self { kind: CallContextKind::Nested, size }
46 }
47
48 pub(super) fn chained(size: usize) -> Self {
49 Self { kind: CallContextKind::Chained, size }
50 }
51
52 pub(super) fn is_nested(&self) -> bool {
53 matches!(self.kind, CallContextKind::Nested)
54 }
55
56 pub(super) fn is_chained(&self) -> bool {
57 matches!(self.kind, CallContextKind::Chained)
58 }
59}
60
61#[derive(Debug, Default)]
62pub(super) struct CallStack {
63 stack: Vec<CallContext>,
64}
65
66impl Deref for CallStack {
67 type Target = [CallContext];
68 fn deref(&self) -> &Self::Target {
69 &self.stack
70 }
71}
72
73impl CallStack {
74 pub(crate) fn push(&mut self, call: CallContext) {
75 self.stack.push(call);
76 }
77
78 pub(crate) fn pop(&mut self) -> Option<CallContext> {
79 self.stack.pop()
80 }
81
82 pub(crate) fn is_nested(&self) -> bool {
83 self.last().is_some_and(|call| call.is_nested())
84 }
85
86 pub(crate) fn has_chain(&self) -> bool {
87 self.stack.iter().any(|call| call.is_chained())
88 }
89}
90
91pub(super) struct State<'sess, 'ast> {
92 pub(super) s: pp::Printer,
94 ind: isize,
95
96 sm: &'sess SourceMap,
97 pub(super) comments: Comments,
98 config: Arc<FormatterConfig>,
99 inline_config: InlineConfig<()>,
100 cursor: SourcePos,
101
102 has_crlf: bool,
105 contract: Option<&'ast ast::ItemContract<'ast>>,
107 block_depth: usize,
109 call_stack: CallStack,
111
112 single_line_stmt: Option<bool>,
114 binary_expr: Option<BinOpGroup>,
116 return_bin_expr: bool,
118 call_with_opts_and_args: bool,
120 skip_index_break: bool,
122 emit_or_revert: bool,
124 var_init: bool,
126}
127
128impl std::ops::Deref for State<'_, '_> {
129 type Target = pp::Printer;
130
131 #[inline(always)]
132 fn deref(&self) -> &Self::Target {
133 &self.s
134 }
135}
136
137impl std::ops::DerefMut for State<'_, '_> {
138 #[inline(always)]
139 fn deref_mut(&mut self) -> &mut Self::Target {
140 &mut self.s
141 }
142}
143
144struct SourcePos {
145 pos: BytePos,
146 enabled: bool,
147}
148
149impl SourcePos {
150 pub(super) fn advance(&mut self, bytes: u32) {
151 self.pos += BytePos(bytes);
152 }
153
154 pub(super) fn advance_to(&mut self, pos: BytePos, enabled: bool) {
155 self.pos = std::cmp::max(pos, self.pos);
156 self.enabled = enabled;
157 }
158
159 pub(super) fn next_line(&mut self, is_at_crlf: bool) {
160 self.pos += if is_at_crlf { 2 } else { 1 };
161 }
162
163 pub(super) fn span(&self, to: BytePos) -> Span {
164 Span::new(self.pos, to)
165 }
166}
167
168pub(super) enum Separator {
169 Nbsp,
170 Space,
171 Hardbreak,
172 SpaceOrNbsp(bool),
173}
174
175impl Separator {
176 fn print(&self, p: &mut pp::Printer, cursor: &mut SourcePos, is_at_crlf: bool) {
177 match self {
178 Self::Nbsp => p.nbsp(),
179 Self::Space => p.space(),
180 Self::Hardbreak => p.hardbreak(),
181 Self::SpaceOrNbsp(breaks) => p.space_or_nbsp(*breaks),
182 }
183
184 cursor.next_line(is_at_crlf);
185 }
186}
187
188impl<'sess> State<'sess, '_> {
190 pub(super) fn new(
191 sm: &'sess SourceMap,
192 config: Arc<FormatterConfig>,
193 inline_config: InlineConfig<()>,
194 comments: Comments,
195 ) -> Self {
196 Self {
197 s: pp::Printer::new(
198 config.line_length,
199 if matches!(config.style, IndentStyle::Tab) {
200 Some(config.tab_width)
201 } else {
202 None
203 },
204 ),
205 ind: config.tab_width as isize,
206 sm,
207 comments,
208 config,
209 inline_config,
210 cursor: SourcePos { pos: BytePos::from_u32(0), enabled: true },
211 has_crlf: false,
212 contract: None,
213 single_line_stmt: None,
214 call_with_opts_and_args: false,
215 skip_index_break: false,
216 binary_expr: None,
217 return_bin_expr: false,
218 emit_or_revert: false,
219 var_init: false,
220 block_depth: 0,
221 call_stack: CallStack::default(),
222 }
223 }
224
225 fn check_crlf(&mut self, span: Span) {
231 if let Ok(snip) = self.sm.span_to_snippet(span)
232 && snip.contains('\r')
233 {
234 self.has_crlf = true;
235 }
236 }
237
238 fn is_at_crlf(&self) -> bool {
241 self.has_crlf && self.char_at(self.cursor.pos) == Some('\r')
242 }
243
244 fn space_left(&self) -> usize {
246 std::cmp::min(self.s.space_left(), self.max_space_left(0))
247 }
248
249 fn max_space_left(&self, prefix_len: usize) -> usize {
252 self.config
253 .line_length
254 .saturating_sub(self.block_depth * self.config.tab_width + prefix_len)
255 }
256
257 fn break_offset_if_not_bol(&mut self, n: usize, off: isize, search: bool) {
258 if search {
261 self.find_and_replace_last_token_still_buffered(
264 pp::Printer::hardbreak_tok_offset(off),
265 |token| token.is_hardbreak(),
266 );
267 return;
268 }
269
270 if !self.is_beginning_of_line() {
272 self.break_offset(n, off)
273 } else if off != 0
274 && let Some(last_token) = self.last_token_still_buffered()
275 && last_token.is_hardbreak()
276 {
277 self.replace_last_token_still_buffered(pp::Printer::hardbreak_tok_offset(off));
280 }
281 }
282
283 fn braces_break(&mut self) {
284 if self.config.bracket_spacing {
285 self.space();
286 } else {
287 self.zerobreak();
288 }
289 }
290}
291
292impl State<'_, '_> {
294 fn char_at(&self, pos: BytePos) -> Option<char> {
295 let res = self.sm.lookup_byte_offset(pos);
296 res.sf.src.get(res.pos.to_usize()..)?.chars().next()
297 }
298
299 fn print_span(&mut self, span: Span) {
300 match self.sm.span_to_snippet(span) {
301 Ok(s) => self.s.word(if matches!(self.config.style, IndentStyle::Tab) {
302 snippet_with_tabs(s, self.config.tab_width)
303 } else {
304 s
305 }),
306 Err(e) => panic!("failed to print {span:?}: {e:#?}"),
307 }
308 while let Some(cmnt) = self.peek_comment() {
310 if cmnt.pos() >= span.hi() {
311 break;
312 }
313 let _ = self.next_comment().unwrap();
314 }
315 self.cursor.advance_to(span.hi(), false);
317 }
318
319 #[must_use]
321 fn handle_span(&mut self, span: Span, skip_prev_cmnts: bool) -> bool {
322 if !skip_prev_cmnts {
323 self.print_comments(span.lo(), CommentConfig::default());
324 }
325 self.print_span_if_disabled(span)
326 }
327
328 #[inline]
330 #[must_use]
331 fn print_span_if_disabled(&mut self, span: Span) -> bool {
332 let cursor_span = self.cursor.span(span.hi());
333 if self.inline_config.is_disabled(cursor_span) {
334 self.print_span_cold(cursor_span);
335 return true;
336 }
337 if self.inline_config.is_disabled(span) {
338 self.print_span_cold(span);
339 return true;
340 }
341 false
342 }
343
344 #[cold]
345 fn print_span_cold(&mut self, span: Span) {
346 self.print_span(span);
347 }
348
349 fn print_tokens(&mut self, tokens: &[token::Token]) {
350 let span = Span::join_first_last(tokens.iter().map(|t| t.span));
352 self.print_span(span);
353 }
354
355 fn print_word(&mut self, w: impl Into<Cow<'static, str>>) {
356 let cow = w.into();
357 self.cursor.advance(cow.len() as u32);
358 self.word(cow);
359 }
360
361 fn print_sep(&mut self, sep: Separator) {
362 if self.handle_span(
363 self.cursor.span(self.cursor.pos + if self.is_at_crlf() { 2 } else { 1 }),
364 true,
365 ) {
366 return;
367 }
368
369 self.print_sep_unhandled(sep);
370 }
371
372 fn print_sep_unhandled(&mut self, sep: Separator) {
373 let is_at_crlf = self.is_at_crlf();
374 sep.print(&mut self.s, &mut self.cursor, is_at_crlf);
375 }
376
377 fn print_ident(&mut self, ident: &ast::Ident) {
378 if self.handle_span(ident.span, true) {
379 return;
380 }
381
382 self.print_comments(ident.span.lo(), CommentConfig::skip_ws());
383 self.word(ident.to_string());
384 }
385
386 fn print_inside_parens<F>(&mut self, f: F)
387 where
388 F: FnOnce(&mut Self),
389 {
390 self.print_word("(");
391 f(self);
392 self.print_word(")");
393 }
394
395 fn estimate_size(&self, span: Span) -> usize {
396 if let Ok(snip) = self.sm.span_to_snippet(span) {
397 let (mut size, mut first, mut prev_needs_space) = (0, true, false);
398
399 for line in snip.lines() {
400 let line = line.trim();
401
402 if prev_needs_space {
403 size += 1;
404 } else if !first && let Some(char) = line.chars().next() {
405 match char {
410 '&' | '|' | '=' | '>' | '<' | '+' | '-' | '*' | '/' | '%' | '^' | '?'
411 | ':' => size += 1,
412 '}' | ')' | ']' if self.config.bracket_spacing => size += 1,
413 _ => (),
414 }
415 }
416 first = false;
417
418 let mut search = line;
420 loop {
421 if let Some((lhs, comment)) = search.split_once(r#"/*"#) {
422 size += lhs.trim_end().len() + 2;
423 search = comment;
424 } else if let Some((comment, rhs)) = search.split_once(r#"*/"#) {
425 size += comment.len() + 2;
426 search = rhs;
427 } else {
428 size += search.trim().len();
429 break;
430 }
431 }
432
433 prev_needs_space = match line.chars().next_back() {
438 Some('[') | Some('(') | Some('{') => self.config.bracket_spacing,
439 Some(',') | Some(';') => true,
440 _ => false,
441 };
442 }
443 return size;
444 }
445
446 span.to_range().len()
447 }
448
449 fn same_source_line(&self, a: BytePos, b: BytePos) -> bool {
450 self.sm.lookup_char_pos(a).line == self.sm.lookup_char_pos(b).line
451 }
452}
453
454impl<'sess> State<'sess, '_> {
456 #[must_use]
458 fn handle_comment(&mut self, cmnt: Comment, skip_break: bool) -> Option<Comment> {
459 if self.cursor.enabled {
460 if self.inline_config.is_disabled(cmnt.span) {
461 if cmnt.style.is_trailing() && !self.last_token_is_space() {
462 self.nbsp();
463 }
464 self.print_span_cold(cmnt.span);
465 if !skip_break && (cmnt.style.is_isolated() || cmnt.style.is_trailing()) {
466 self.print_sep(Separator::Hardbreak);
467 }
468 return None;
469 }
470 } else if self.print_span_if_disabled(cmnt.span) {
471 if !skip_break && (cmnt.style.is_isolated() || cmnt.style.is_trailing()) {
472 self.print_sep(Separator::Hardbreak);
473 }
474 return None;
475 }
476 Some(cmnt)
477 }
478
479 fn cmnt_config(&self) -> CommentConfig {
480 CommentConfig { ..Default::default() }
481 }
482
483 fn print_docs(&mut self, docs: &'_ ast::DocComments<'_>) {
484 let _ = docs;
486 }
487
488 fn print_comments(&mut self, pos: BytePos, mut config: CommentConfig) -> Option<CommentStyle> {
493 let mut last_style: Option<CommentStyle> = None;
494 let mut is_leading = true;
495 let config_cache = config;
496 let mut buffered_blank = None;
497 while self.peek_comment().is_some_and(|c| c.pos() < pos) {
498 let mut cmnt = self.next_comment().unwrap();
499 let style_cache = cmnt.style;
500
501 if self.config.docs_style == foundry_config::fmt::DocCommentStyle::Block
503 && cmnt.is_doc
504 && cmnt.kind == ast::CommentKind::Line
505 {
506 let mut ref_line = self.sm.lookup_char_pos(cmnt.span.hi()).line;
507 while let Some(next_cmnt) = self.peek_comment() {
508 if !next_cmnt.is_doc
509 || next_cmnt.kind != ast::CommentKind::Line
510 || ref_line + 1 != self.sm.lookup_char_pos(next_cmnt.span.lo()).line
511 {
512 break;
513 }
514
515 let next_to_merge = self.next_comment().unwrap();
516 cmnt.lines.extend(next_to_merge.lines);
517 cmnt.span = cmnt.span.to(next_to_merge.span);
518 ref_line += 1;
519 }
520 }
521
522 if self.peek_comment_before(pos).is_some() {
524 config.iso_no_break = false;
525 config.trailing_no_break = false;
526 }
527
528 let Some(cmnt) = self.handle_comment(
530 cmnt,
531 if style_cache.is_isolated() {
532 config.iso_no_break
533 } else {
534 config.trailing_no_break
535 },
536 ) else {
537 last_style = Some(style_cache);
538 continue;
539 };
540
541 if cmnt.style.is_blank() {
542 match config.skip_blanks {
543 Some(Skip::All) => continue,
544 Some(Skip::Leading { resettable: true }) if is_leading => continue,
545 Some(Skip::Leading { resettable: false }) if last_style.is_none() => continue,
546 Some(Skip::Trailing) => {
547 buffered_blank = Some(cmnt);
548 continue;
549 }
550 _ => (),
551 }
552 } else if !cmnt.is_doc {
554 is_leading = false;
555 }
556
557 if let Some(blank) = buffered_blank.take() {
558 self.print_comment(blank, config);
559 }
560
561 if cmnt.style.is_mixed() {
563 if let Some(cmnt) = self.peek_comment_before(pos) {
564 config.mixed_no_break_prev = true;
565 config.mixed_no_break_post = true;
566 config.mixed_post_nbsp = cmnt.style.is_mixed();
567 }
568
569 if last_style.is_some_and(|s| s.is_mixed()) {
571 config.mixed_no_break_prev = true;
572 config.mixed_no_break_post = true;
573 config.mixed_prev_space = false;
574 }
575 } else if config.offset != 0
576 && cmnt.style.is_isolated()
577 && last_style.is_some_and(|s| s.is_isolated())
578 {
579 self.offset(config.offset);
580 }
581
582 last_style = Some(cmnt.style);
583 self.print_comment(cmnt, config);
584 config = config_cache;
585 }
586 last_style
587 }
588
589 fn print_wrapped_line(
591 &mut self,
592 line: &str,
593 prefix: &'static str,
594 break_offset: isize,
595 is_doc: bool,
596 ) {
597 if !line.starts_with(prefix) {
598 self.word(line.to_owned());
599 return;
600 }
601
602 fn post_break_prefix(prefix: &'static str, has_content: bool) -> &'static str {
603 if !has_content {
604 return prefix;
605 }
606 match prefix {
607 "///" => "/// ",
608 "//" => "// ",
609 "/*" => "/* ",
610 " *" => " * ",
611 _ => prefix,
612 }
613 }
614
615 self.ibox(0);
616 self.word(prefix);
617
618 let content = &line[prefix.len()..];
619 let content = if is_doc {
620 let ws_len = content
622 .char_indices()
623 .take_while(|(_, c)| c.is_whitespace())
624 .last()
625 .map_or(0, |(idx, c)| idx + c.len_utf8());
626 let (leading_ws, rest) = content.split_at(ws_len);
627 if !leading_ws.is_empty() {
628 self.word(leading_ws.to_owned());
629 }
630 rest
631 } else {
632 if let Some(first_char) = content.chars().next() {
634 if first_char.is_whitespace() {
635 self.nbsp();
636 &content[first_char.len_utf8()..]
637 } else {
638 content
639 }
640 } else {
641 ""
642 }
643 };
644
645 let post_break = post_break_prefix(prefix, !content.is_empty());
646
647 let (mut chars, mut current_word) = (content.chars().peekable(), String::new());
649 while let Some(ch) = chars.next() {
650 if ch.is_whitespace() {
651 if !current_word.is_empty() {
653 self.word(std::mem::take(&mut current_word));
654 }
655
656 let mut ws_count = 1;
658 while chars.peek().is_some_and(|c| c.is_whitespace()) {
659 ws_count += 1;
660 chars.next();
661 }
662 self.s.scan_break(BreakToken {
663 offset: break_offset,
664 blank_space: ws_count,
665 post_break: if post_break.starts_with("/*") { None } else { Some(post_break) },
666 ..Default::default()
667 });
668 continue;
669 }
670
671 current_word.push(ch);
672 }
673
674 if !current_word.is_empty() {
676 self.word(current_word);
677 }
678
679 self.end();
680 }
681
682 fn merge_comment_lines(&self, lines: &[String], prefix: &str) -> Vec<String> {
684 if lines.is_empty() || lines.len() < 2 || !prefix.starts_with("//") {
686 return lines.to_vec();
687 }
688
689 let mut result = Vec::new();
690 let mut i = 0;
691
692 while i < lines.len() {
693 let current_line = &lines[i];
694
695 if current_line.trim().is_empty() || !current_line.starts_with(prefix) {
697 result.push(current_line.clone());
698 i += 1;
699 continue;
700 }
701
702 if i + 1 < lines.len() {
703 let next_line = &lines[i + 1];
704
705 if next_line.starts_with(prefix) && !next_line.trim().is_empty() {
707 if estimate_line_width(current_line, self.config.tab_width) > self.space_left()
709 {
710 let merged_line = format!(
712 "{current_line} {next_content}",
713 next_content = &next_line[prefix.len()..].trim_start()
714 );
715 result.push(merged_line);
716
717 i += 2;
719 continue;
720 }
721 }
722 }
723
724 result.push(current_line.clone());
726 i += 1;
727 }
728
729 result
730 }
731
732 fn print_comment(&mut self, mut cmnt: Comment, mut config: CommentConfig) {
733 self.cursor.advance_to(cmnt.span.hi(), true);
734
735 if cmnt.is_doc {
736 cmnt = style_doc_comment(self.config.docs_style, cmnt);
737 }
738
739 match cmnt.style {
740 CommentStyle::Mixed => {
741 let Some(prefix) = cmnt.prefix() else { return };
742 let never_break = self.last_token_is_neverbreak();
743 if !self.is_bol_or_only_ind() {
744 match (never_break || config.mixed_no_break_prev, config.mixed_prev_space) {
745 (false, true) => config.space(&mut self.s),
746 (false, false) => config.zerobreak(&mut self.s),
747 (true, true) => self.nbsp(),
748 (true, false) => (),
749 };
750 }
751 if self.config.wrap_comments {
752 let merged_lines = self.merge_comment_lines(&cmnt.lines, prefix);
754 for (pos, line) in merged_lines.into_iter().delimited() {
755 self.print_wrapped_line(&line, prefix, 0, cmnt.is_doc);
756 if !pos.is_last {
757 self.hardbreak();
758 }
759 }
760 } else {
761 for (pos, line) in cmnt.lines.into_iter().delimited() {
763 self.word(line);
764 if !pos.is_last {
765 self.hardbreak();
766 }
767 }
768 }
769 if config.mixed_post_nbsp {
770 config.nbsp_or_space(self.config.wrap_comments, &mut self.s);
771 self.cursor.advance(1);
772 } else if !config.mixed_no_break_post {
773 config.space(&mut self.s);
774 self.cursor.advance(1);
775 }
776 }
777 CommentStyle::Isolated => {
778 let Some(mut prefix) = cmnt.prefix() else { return };
779 if !config.iso_no_break {
780 config.hardbreak_if_not_bol(self.is_bol_or_only_ind(), &mut self.s);
781 }
782
783 if self.config.wrap_comments {
784 let merged_lines = self.merge_comment_lines(&cmnt.lines, prefix);
786 for (pos, line) in merged_lines.into_iter().delimited() {
787 let hb = |this: &mut Self| {
788 this.hardbreak();
789 if pos.is_last {
790 this.cursor.next_line(this.is_at_crlf());
791 }
792 };
793 if line.is_empty() {
794 hb(self);
795 continue;
796 }
797 if pos.is_first {
798 self.ibox(config.offset);
799 if cmnt.is_doc && matches!(prefix, "/**") {
800 self.word(prefix);
801 hb(self);
802 prefix = " * ";
803 continue;
804 }
805 }
806
807 self.print_wrapped_line(&line, prefix, 0, cmnt.is_doc);
808
809 if pos.is_last {
810 self.end();
811 if !config.iso_no_break {
812 hb(self);
813 }
814 } else {
815 hb(self);
816 }
817 }
818 } else {
819 for (pos, line) in cmnt.lines.into_iter().delimited() {
821 let hb = |this: &mut Self| {
822 this.hardbreak();
823 if pos.is_last {
824 this.cursor.next_line(this.is_at_crlf());
825 }
826 };
827 if line.is_empty() {
828 hb(self);
829 continue;
830 }
831 if pos.is_first {
832 self.ibox(config.offset);
833 if cmnt.is_doc && matches!(prefix, "/**") {
834 self.word(prefix);
835 hb(self);
836 prefix = " * ";
837 continue;
838 }
839 }
840
841 self.word(line);
842
843 if pos.is_last {
844 self.end();
845 if !config.iso_no_break {
846 hb(self);
847 }
848 } else {
849 hb(self);
850 }
851 }
852 }
853 }
854 CommentStyle::Trailing => {
855 let Some(prefix) = cmnt.prefix() else { return };
856 self.neverbreak();
857 if !self.is_bol_or_only_ind() {
858 self.nbsp();
859 }
860
861 if !self.config.wrap_comments && cmnt.lines.len() == 1 {
862 self.word(cmnt.lines.pop().unwrap());
863 } else if self.config.wrap_comments {
864 if cmnt.is_doc || matches!(cmnt.kind, ast::CommentKind::Line) {
865 config.offset = 0;
866 } else {
867 config.offset = self.ind;
868 }
869 for (lpos, line) in cmnt.lines.into_iter().delimited() {
870 if !line.is_empty() {
871 self.print_wrapped_line(&line, prefix, config.offset, cmnt.is_doc);
872 }
873 if !lpos.is_last {
874 config.hardbreak(&mut self.s);
875 }
876 }
877 } else {
878 self.visual_align();
879 for (pos, line) in cmnt.lines.into_iter().delimited() {
880 if !line.is_empty() {
881 self.word(line);
882 if !pos.is_last {
883 self.hardbreak();
884 }
885 }
886 }
887 self.end();
888 }
889
890 if !config.trailing_no_break {
891 self.print_sep(Separator::Hardbreak);
892 }
893 }
894
895 CommentStyle::BlankLine => {
896 if !self.last_token_is_break() && !self.is_bol_or_only_ind() {
898 config.hardbreak(&mut self.s);
899 self.cursor.next_line(self.is_at_crlf());
900 }
901
902 let twice = match self.last_token() {
904 Some(Token::String(s)) => ";" == s,
905 Some(Token::Begin(_)) => true,
906 Some(Token::End) => true,
907 _ => false,
908 };
909 if twice {
910 config.hardbreak(&mut self.s);
911 self.cursor.next_line(self.is_at_crlf());
912 }
913 config.hardbreak(&mut self.s);
914 self.cursor.next_line(self.is_at_crlf());
915 }
916 }
917 }
918
919 fn peek_comment<'b>(&'b self) -> Option<&'b Comment>
920 where
921 'sess: 'b,
922 {
923 self.comments.peek()
924 }
925
926 fn peek_comment_before<'b>(&'b self, pos: BytePos) -> Option<&'b Comment>
927 where
928 'sess: 'b,
929 {
930 self.comments.iter().take_while(|c| c.pos() < pos).find(|c| !c.style.is_blank())
931 }
932
933 fn has_comment_before_with<F>(&self, pos: BytePos, f: F) -> bool
934 where
935 F: FnMut(&Comment) -> bool,
936 {
937 self.comments.iter().take_while(|c| c.pos() < pos).any(f)
938 }
939
940 fn peek_comment_between<'b>(&'b self, pos_lo: BytePos, pos_hi: BytePos) -> Option<&'b Comment>
941 where
942 'sess: 'b,
943 {
944 self.comments
945 .iter()
946 .take_while(|c| pos_lo < c.pos() && c.pos() < pos_hi)
947 .find(|c| !c.style.is_blank())
948 }
949
950 fn has_comment_between(&self, start_pos: BytePos, end_pos: BytePos) -> bool {
951 self.comments.iter().filter(|c| c.pos() > start_pos && c.pos() < end_pos).any(|_| true)
952 }
953
954 pub(crate) fn next_comment(&mut self) -> Option<Comment> {
955 self.comments.next()
956 }
957
958 fn peek_trailing_comment<'b>(
959 &'b self,
960 span_pos: BytePos,
961 next_pos: Option<BytePos>,
962 ) -> Option<&'b Comment>
963 where
964 'sess: 'b,
965 {
966 self.comments.peek_trailing(self.sm, span_pos, next_pos).map(|(cmnt, _)| cmnt)
967 }
968
969 fn print_trailing_comment_inner(
970 &mut self,
971 span_pos: BytePos,
972 next_pos: Option<BytePos>,
973 config: Option<CommentConfig>,
974 ) -> bool {
975 let mut printed = 0;
976 if let Some((_, n)) = self.comments.peek_trailing(self.sm, span_pos, next_pos) {
977 let config =
978 config.unwrap_or(CommentConfig::skip_ws().mixed_no_break().mixed_prev_space());
979 while printed <= n {
980 let cmnt = self.comments.next().unwrap();
981 if let Some(cmnt) = self.handle_comment(cmnt, config.trailing_no_break) {
982 self.print_comment(cmnt, config);
983 };
984 printed += 1;
985 }
986 }
987 printed != 0
988 }
989
990 fn print_trailing_comment(&mut self, span_pos: BytePos, next_pos: Option<BytePos>) -> bool {
991 self.print_trailing_comment_inner(span_pos, next_pos, None)
992 }
993
994 fn print_trailing_comment_no_break(&mut self, span_pos: BytePos, next_pos: Option<BytePos>) {
995 self.print_trailing_comment_inner(
996 span_pos,
997 next_pos,
998 Some(CommentConfig::skip_ws().trailing_no_break().mixed_no_break().mixed_prev_space()),
999 );
1000 }
1001
1002 fn print_remaining_comments(&mut self, skip_leading_ws: bool) {
1003 if self.peek_comment().is_none() && !self.is_bol_or_only_ind() {
1006 self.hardbreak();
1007 return;
1008 }
1009
1010 let mut is_leading = true;
1011 while let Some(cmnt) = self.next_comment() {
1012 if cmnt.style.is_blank() && skip_leading_ws && is_leading {
1013 continue;
1014 }
1015
1016 is_leading = false;
1017 if let Some(cmnt) = self.handle_comment(cmnt, false) {
1018 self.print_comment(cmnt, CommentConfig::default());
1019 } else if self.peek_comment().is_none() && !self.is_bol_or_only_ind() {
1020 self.hardbreak();
1021 }
1022 }
1023 }
1024}
1025
1026#[derive(Clone, Copy)]
1027enum Skip {
1028 All,
1029 Leading { resettable: bool },
1030 Trailing,
1031}
1032
1033#[derive(Default, Clone, Copy)]
1034pub(crate) struct CommentConfig {
1035 skip_blanks: Option<Skip>,
1037 offset: isize,
1038
1039 iso_no_break: bool,
1041 trailing_no_break: bool,
1043 mixed_prev_space: bool,
1045 mixed_post_nbsp: bool,
1046 mixed_no_break_prev: bool,
1047 mixed_no_break_post: bool,
1048}
1049
1050impl CommentConfig {
1051 pub(crate) fn skip_ws() -> Self {
1052 Self { skip_blanks: Some(Skip::All), ..Default::default() }
1053 }
1054
1055 pub(crate) fn skip_leading_ws(resettable: bool) -> Self {
1056 Self { skip_blanks: Some(Skip::Leading { resettable }), ..Default::default() }
1057 }
1058
1059 pub(crate) fn skip_trailing_ws() -> Self {
1060 Self { skip_blanks: Some(Skip::Trailing), ..Default::default() }
1061 }
1062
1063 pub(crate) fn offset(mut self, off: isize) -> Self {
1064 self.offset = off;
1065 self
1066 }
1067
1068 pub(crate) fn no_breaks(mut self) -> Self {
1069 self.iso_no_break = true;
1070 self.trailing_no_break = true;
1071 self.mixed_no_break_prev = true;
1072 self.mixed_no_break_post = true;
1073 self
1074 }
1075
1076 pub(crate) fn trailing_no_break(mut self) -> Self {
1077 self.trailing_no_break = true;
1078 self
1079 }
1080
1081 pub(crate) fn mixed_no_break(mut self) -> Self {
1082 self.mixed_no_break_prev = true;
1083 self.mixed_no_break_post = true;
1084 self
1085 }
1086
1087 pub(crate) fn mixed_no_break_post(mut self) -> Self {
1088 self.mixed_no_break_post = true;
1089 self
1090 }
1091
1092 pub(crate) fn mixed_prev_space(mut self) -> Self {
1093 self.mixed_prev_space = true;
1094 self
1095 }
1096
1097 pub(crate) fn mixed_post_nbsp(mut self) -> Self {
1098 self.mixed_post_nbsp = true;
1099 self
1100 }
1101
1102 pub(crate) fn hardbreak_if_not_bol(&self, is_bol: bool, p: &mut pp::Printer) {
1103 if self.offset != 0 && !is_bol {
1104 self.hardbreak(p);
1105 } else {
1106 p.hardbreak_if_not_bol();
1107 }
1108 }
1109
1110 pub(crate) fn hardbreak(&self, p: &mut pp::Printer) {
1111 p.break_offset(SIZE_INFINITY as usize, self.offset);
1112 }
1113
1114 pub(crate) fn space(&self, p: &mut pp::Printer) {
1115 p.break_offset(1, self.offset);
1116 }
1117
1118 pub(crate) fn nbsp_or_space(&self, breaks: bool, p: &mut pp::Printer) {
1119 if breaks {
1120 self.space(p);
1121 } else {
1122 p.nbsp();
1123 }
1124 }
1125
1126 pub(crate) fn zerobreak(&self, p: &mut pp::Printer) {
1127 p.break_offset(0, self.offset);
1128 }
1129}
1130
1131fn snippet_with_tabs(s: String, tab_width: usize) -> String {
1132 let trimmed = s.trim_start_matches('\n');
1134 let num_breaks = s.len() - trimmed.len();
1135 let mut formatted = std::iter::repeat_n('\n', num_breaks).collect::<String>();
1136
1137 for (pos, line) in trimmed.lines().delimited() {
1139 line_with_tabs(&mut formatted, line, tab_width, None);
1140 if !pos.is_last {
1141 formatted.push('\n');
1142 }
1143 }
1144
1145 formatted
1146}
1147
1148fn style_doc_comment(style: DocCommentStyle, mut cmnt: Comment) -> Comment {
1152 match style {
1153 DocCommentStyle::Line if cmnt.kind == ast::CommentKind::Block => {
1154 let mut new_lines = Vec::new();
1155 for (pos, line) in cmnt.lines.iter().delimited() {
1156 if pos.is_first || pos.is_last {
1157 continue;
1159 }
1160
1161 let trimmed = line.trim_start();
1163 if let Some(content) = trimmed.strip_prefix('*') {
1164 new_lines.push(format!("///{content}"));
1165 } else if !trimmed.is_empty() {
1166 new_lines.push(format!("/// {trimmed}"));
1167 }
1168 }
1169
1170 cmnt.lines = new_lines;
1171 cmnt.kind = ast::CommentKind::Line;
1172 cmnt
1173 }
1174 DocCommentStyle::Block if cmnt.kind == ast::CommentKind::Line => {
1175 let mut new_lines = vec!["/**".to_string()];
1176
1177 for line in &cmnt.lines {
1178 new_lines.push(format!(" *{content}", content = &line[3..]))
1180 }
1181
1182 new_lines.push(" */".to_string());
1183 cmnt.lines = new_lines;
1184 cmnt.kind = ast::CommentKind::Block;
1185 cmnt
1186 }
1187 _ => cmnt,
1189 }
1190}