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