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