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 if matches!(config.style, IndentStyle::Tab) {
205 Some(config.tab_width)
206 } else {
207 None
208 },
209 ),
210 ind: config.tab_width as isize,
211 sm,
212 comments,
213 config,
214 inline_config,
215 cursor: SourcePos { pos: BytePos::from_u32(0), enabled: true },
216 has_crlf: false,
217 contract: None,
218 single_line_stmt: None,
219 call_with_opts_and_args: false,
220 skip_index_break: false,
221 binary_expr: None,
222 return_bin_expr: false,
223 emit_or_revert: false,
224 var_init: false,
225 block_depth: 0,
226 call_stack: CallStack::default(),
227 }
228 }
229
230 fn check_crlf(&mut self, span: Span) {
236 if let Ok(snip) = self.sm.span_to_snippet(span)
237 && snip.contains('\r')
238 {
239 self.has_crlf = true;
240 }
241 }
242
243 fn is_at_crlf(&self) -> bool {
246 self.has_crlf && self.char_at(self.cursor.pos) == Some('\r')
247 }
248
249 fn space_left(&self) -> usize {
251 std::cmp::min(self.s.space_left(), self.max_space_left(0))
252 }
253
254 fn max_space_left(&self, prefix_len: usize) -> usize {
257 self.config
258 .line_length
259 .saturating_sub(self.block_depth * self.config.tab_width + prefix_len)
260 }
261
262 fn break_offset_if_not_bol(&mut self, n: usize, off: isize, search: bool) {
263 if search {
266 self.find_and_replace_last_token_still_buffered(
269 pp::Printer::hardbreak_tok_offset(off),
270 |token| token.is_hardbreak(),
271 );
272 return;
273 }
274
275 if !self.is_beginning_of_line() {
277 self.break_offset(n, off)
278 } else if off != 0
279 && let Some(last_token) = self.last_token_still_buffered()
280 && last_token.is_hardbreak()
281 {
282 self.replace_last_token_still_buffered(pp::Printer::hardbreak_tok_offset(off));
285 }
286 }
287
288 fn braces_break(&mut self) {
289 if self.config.bracket_spacing {
290 self.space();
291 } else {
292 self.zerobreak();
293 }
294 }
295}
296
297impl State<'_, '_> {
299 fn char_at(&self, pos: BytePos) -> Option<char> {
300 let res = self.sm.lookup_byte_offset(pos);
301 res.sf.src.get(res.pos.to_usize()..)?.chars().next()
302 }
303
304 fn print_span(&mut self, span: Span) {
305 match self.sm.span_to_snippet(span) {
306 Ok(s) => self.s.word(if matches!(self.config.style, IndentStyle::Tab) {
307 snippet_with_tabs(s, self.config.tab_width)
308 } else {
309 s
310 }),
311 Err(e) => panic!("failed to print {span:?}: {e:#?}"),
312 }
313 while let Some(cmnt) = self.peek_comment() {
315 if cmnt.pos() >= span.hi() {
316 break;
317 }
318 let _ = self.next_comment().unwrap();
319 }
320 self.cursor.advance_to(span.hi(), false);
322 }
323
324 #[must_use]
326 fn handle_span(&mut self, span: Span, skip_prev_cmnts: bool) -> bool {
327 if !skip_prev_cmnts {
328 self.print_comments(span.lo(), CommentConfig::default());
329 }
330 self.print_span_if_disabled(span)
331 }
332
333 #[inline]
335 #[must_use]
336 fn print_span_if_disabled(&mut self, span: Span) -> bool {
337 let cursor_span = self.cursor.span(span.hi());
338 if self.inline_config.is_disabled(cursor_span) {
339 self.print_span_cold(cursor_span);
340 return true;
341 }
342 if self.inline_config.is_disabled(span) {
343 self.print_span_cold(span);
344 return true;
345 }
346 false
347 }
348
349 #[cold]
350 fn print_span_cold(&mut self, span: Span) {
351 self.print_span(span);
352 }
353
354 fn print_tokens(&mut self, tokens: &[token::Token]) {
355 let span = Span::join_first_last(tokens.iter().map(|t| t.span));
357 self.print_span(span);
358 }
359
360 fn print_word(&mut self, w: impl Into<Cow<'static, str>>) {
361 let cow = w.into();
362 self.cursor.advance(cow.len() as u32);
363 self.word(cow);
364 }
365
366 fn print_sep(&mut self, sep: Separator) {
367 if self.handle_span(
368 self.cursor.span(self.cursor.pos + if self.is_at_crlf() { 2 } else { 1 }),
369 true,
370 ) {
371 return;
372 }
373
374 self.print_sep_unhandled(sep);
375 }
376
377 fn print_sep_unhandled(&mut self, sep: Separator) {
378 let is_at_crlf = self.is_at_crlf();
379 sep.print(&mut self.s, &mut self.cursor, is_at_crlf);
380 }
381
382 fn print_ident(&mut self, ident: &ast::Ident) {
383 if self.handle_span(ident.span, true) {
384 return;
385 }
386
387 self.print_comments(ident.span.lo(), CommentConfig::skip_ws());
388 self.word(ident.to_string());
389 }
390
391 fn print_inside_parens<F>(&mut self, f: F)
392 where
393 F: FnOnce(&mut Self),
394 {
395 self.print_word("(");
396 f(self);
397 self.print_word(")");
398 }
399
400 fn estimate_size(&self, span: Span) -> usize {
401 if let Ok(snip) = self.sm.span_to_snippet(span) {
402 let (mut size, mut first, mut prev_needs_space) = (0, true, false);
403
404 for line in snip.lines() {
405 let line = line.trim();
406
407 if prev_needs_space {
408 size += 1;
409 } else if !first && let Some(char) = line.chars().next() {
410 match char {
415 '&' | '|' | '=' | '>' | '<' | '+' | '-' | '*' | '/' | '%' | '^' | '?'
416 | ':' => size += 1,
417 '}' | ')' | ']' if self.config.bracket_spacing => size += 1,
418 _ => (),
419 }
420 }
421 first = false;
422
423 let mut search = line;
425 loop {
426 if let Some((lhs, comment)) = search.split_once(r#"/*"#) {
427 size += lhs.trim_end().len() + 2;
428 search = comment;
429 } else if let Some((comment, rhs)) = search.split_once(r#"*/"#) {
430 size += comment.len() + 2;
431 search = rhs;
432 } else {
433 size += search.trim().len();
434 break;
435 }
436 }
437
438 prev_needs_space = match line.chars().next_back() {
443 Some('[') | Some('(') | Some('{') => self.config.bracket_spacing,
444 Some(',') | Some(';') => true,
445 _ => false,
446 };
447 }
448 return size;
449 }
450
451 span.to_range().len()
452 }
453
454 fn same_source_line(&self, a: BytePos, b: BytePos) -> bool {
455 self.sm.lookup_char_pos(a).line == self.sm.lookup_char_pos(b).line
456 }
457}
458
459impl<'sess> State<'sess, '_> {
461 #[must_use]
463 fn handle_comment(&mut self, cmnt: Comment, skip_break: bool) -> Option<Comment> {
464 if self.cursor.enabled {
465 if self.inline_config.is_disabled(cmnt.span) {
466 if cmnt.style.is_trailing() && !self.last_token_is_space() {
467 self.nbsp();
468 }
469 self.print_span_cold(cmnt.span);
470 if !skip_break && (cmnt.style.is_isolated() || cmnt.style.is_trailing()) {
471 self.print_sep(Separator::Hardbreak);
472 }
473 return None;
474 }
475 } else if self.print_span_if_disabled(cmnt.span) {
476 if !skip_break && (cmnt.style.is_isolated() || cmnt.style.is_trailing()) {
477 self.print_sep(Separator::Hardbreak);
478 }
479 return None;
480 }
481 Some(cmnt)
482 }
483
484 fn cmnt_config(&self) -> CommentConfig {
485 CommentConfig { ..Default::default() }
486 }
487
488 fn print_docs(&mut self, docs: &'_ ast::DocComments<'_>) {
489 let _ = docs;
491 }
492
493 fn print_comments(&mut self, pos: BytePos, mut config: CommentConfig) -> Option<CommentStyle> {
498 let mut last_style: Option<CommentStyle> = None;
499 let mut is_leading = true;
500 let config_cache = config;
501 let mut buffered_blank = None;
502 while self.peek_comment().is_some_and(|c| c.pos() < pos) {
503 let mut cmnt = self.next_comment().unwrap();
504 let style_cache = cmnt.style;
505
506 if self.config.docs_style == foundry_config::fmt::DocCommentStyle::Block
508 && cmnt.is_doc
509 && cmnt.kind == ast::CommentKind::Line
510 {
511 let mut ref_line = self.sm.lookup_char_pos(cmnt.span.hi()).line;
512 while let Some(next_cmnt) = self.peek_comment() {
513 if !next_cmnt.is_doc
514 || next_cmnt.kind != ast::CommentKind::Line
515 || ref_line + 1 != self.sm.lookup_char_pos(next_cmnt.span.lo()).line
516 {
517 break;
518 }
519
520 let next_to_merge = self.next_comment().unwrap();
521 cmnt.lines.extend(next_to_merge.lines);
522 cmnt.span = cmnt.span.to(next_to_merge.span);
523 ref_line += 1;
524 }
525 }
526
527 if self.peek_comment_before(pos).is_some() {
529 config.iso_no_break = false;
530 config.trailing_no_break = false;
531 }
532
533 let Some(cmnt) = self.handle_comment(
535 cmnt,
536 if style_cache.is_isolated() {
537 config.iso_no_break
538 } else {
539 config.trailing_no_break
540 },
541 ) else {
542 last_style = Some(style_cache);
543 continue;
544 };
545
546 if cmnt.style.is_blank() {
547 match config.skip_blanks {
548 Some(Skip::All) => continue,
549 Some(Skip::Leading { resettable: true }) if is_leading => continue,
550 Some(Skip::Leading { resettable: false }) if last_style.is_none() => continue,
551 Some(Skip::Trailing) => {
552 buffered_blank = Some(cmnt);
553 continue;
554 }
555 _ => (),
556 }
557 } else if !cmnt.is_doc {
559 is_leading = false;
560 }
561
562 if let Some(blank) = buffered_blank.take() {
563 self.print_comment(blank, config);
564 }
565
566 if cmnt.style.is_mixed() {
568 if let Some(cmnt) = self.peek_comment_before(pos) {
569 config.mixed_no_break_prev = true;
570 config.mixed_no_break_post = true;
571 config.mixed_post_nbsp = cmnt.style.is_mixed();
572 }
573
574 if last_style.is_some_and(|s| s.is_mixed()) {
576 config.mixed_no_break_prev = true;
577 config.mixed_no_break_post = true;
578 config.mixed_prev_space = false;
579 }
580 } else if config.offset != 0
581 && cmnt.style.is_isolated()
582 && last_style.is_some_and(|s| s.is_isolated())
583 {
584 self.offset(config.offset);
585 }
586
587 last_style = Some(cmnt.style);
588 self.print_comment(cmnt, config);
589 config = config_cache;
590 }
591 last_style
592 }
593
594 fn print_wrapped_line(
596 &mut self,
597 line: &str,
598 prefix: &'static str,
599 break_offset: isize,
600 is_doc: bool,
601 ) {
602 if !line.starts_with(prefix) {
603 self.word(line.to_owned());
604 return;
605 }
606
607 fn post_break_prefix(prefix: &'static str, has_content: bool) -> &'static str {
608 if !has_content {
609 return prefix;
610 }
611 match prefix {
612 "///" => "/// ",
613 "//" => "// ",
614 "/*" => "/* ",
615 " *" => " * ",
616 _ => prefix,
617 }
618 }
619
620 self.ibox(0);
621 self.word(prefix);
622
623 let content = &line[prefix.len()..];
624 let content = if is_doc {
625 let ws_len = content
627 .char_indices()
628 .take_while(|(_, c)| c.is_whitespace())
629 .last()
630 .map_or(0, |(idx, c)| idx + c.len_utf8());
631 let (leading_ws, rest) = content.split_at(ws_len);
632 if !leading_ws.is_empty() {
633 self.word(leading_ws.to_owned());
634 }
635 rest
636 } else {
637 if let Some(first_char) = content.chars().next() {
639 if first_char.is_whitespace() {
640 self.nbsp();
641 &content[first_char.len_utf8()..]
642 } else {
643 content
644 }
645 } else {
646 ""
647 }
648 };
649
650 let post_break = post_break_prefix(prefix, !content.is_empty());
651
652 let (mut chars, mut current_word) = (content.chars().peekable(), String::new());
654 while let Some(ch) = chars.next() {
655 if ch.is_whitespace() {
656 if !current_word.is_empty() {
658 self.word(std::mem::take(&mut current_word));
659 }
660
661 let mut ws_count = 1;
663 while chars.peek().is_some_and(|c| c.is_whitespace()) {
664 ws_count += 1;
665 chars.next();
666 }
667 self.s.scan_break(BreakToken {
668 offset: break_offset,
669 blank_space: ws_count,
670 post_break: if post_break.starts_with("/*") { None } else { Some(post_break) },
671 ..Default::default()
672 });
673 continue;
674 }
675
676 current_word.push(ch);
677 }
678
679 if !current_word.is_empty() {
681 self.word(current_word);
682 }
683
684 self.end();
685 }
686
687 fn merge_comment_lines(&self, lines: &[String], prefix: &str) -> Vec<String> {
689 if lines.is_empty() || lines.len() < 2 || !prefix.starts_with("//") {
691 return lines.to_vec();
692 }
693
694 let mut result = Vec::new();
695 let mut i = 0;
696
697 while i < lines.len() {
698 let current_line = &lines[i];
699
700 if current_line.trim().is_empty() || !current_line.starts_with(prefix) {
702 result.push(current_line.clone());
703 i += 1;
704 continue;
705 }
706
707 if i + 1 < lines.len() {
708 let next_line = &lines[i + 1];
709
710 if next_line.starts_with(prefix) && !next_line.trim().is_empty() {
712 if estimate_line_width(current_line, self.config.tab_width) > self.space_left()
714 {
715 let merged_line = format!(
717 "{current_line} {next_content}",
718 next_content = &next_line[prefix.len()..].trim_start()
719 );
720 result.push(merged_line);
721
722 i += 2;
724 continue;
725 }
726 }
727 }
728
729 result.push(current_line.clone());
731 i += 1;
732 }
733
734 result
735 }
736
737 fn print_comment(&mut self, mut cmnt: Comment, mut config: CommentConfig) {
738 self.cursor.advance_to(cmnt.span.hi(), true);
739
740 if cmnt.is_doc {
741 cmnt = style_doc_comment(self.config.docs_style, cmnt);
742 }
743
744 match cmnt.style {
745 CommentStyle::Mixed => {
746 let Some(prefix) = cmnt.prefix() else { return };
747 let never_break = self.last_token_is_neverbreak();
748 if !self.is_bol_or_only_ind() {
749 match (never_break || config.mixed_no_break_prev, config.mixed_prev_space) {
750 (false, true) => config.space(&mut self.s),
751 (false, false) => config.zerobreak(&mut self.s),
752 (true, true) => self.nbsp(),
753 (true, false) => (),
754 };
755 }
756 if self.config.wrap_comments {
757 let merged_lines = self.merge_comment_lines(&cmnt.lines, prefix);
759 for (pos, line) in merged_lines.into_iter().delimited() {
760 self.print_wrapped_line(&line, prefix, 0, cmnt.is_doc);
761 if !pos.is_last {
762 self.hardbreak();
763 }
764 }
765 } else {
766 for (pos, line) in cmnt.lines.into_iter().delimited() {
768 self.word(line);
769 if !pos.is_last {
770 self.hardbreak();
771 }
772 }
773 }
774 if config.mixed_post_nbsp {
775 config.nbsp_or_space(self.config.wrap_comments, &mut self.s);
776 self.cursor.advance(1);
777 } else if !config.mixed_no_break_post {
778 config.space(&mut self.s);
779 self.cursor.advance(1);
780 }
781 }
782 CommentStyle::Isolated => {
783 let Some(mut prefix) = cmnt.prefix() else { return };
784 if !config.iso_no_break {
785 config.hardbreak_if_not_bol(self.is_bol_or_only_ind(), &mut self.s);
786 }
787
788 if self.config.wrap_comments {
789 let merged_lines = self.merge_comment_lines(&cmnt.lines, prefix);
791 for (pos, line) in merged_lines.into_iter().delimited() {
792 let hb = |this: &mut Self| {
793 this.hardbreak();
794 if pos.is_last {
795 this.cursor.next_line(this.is_at_crlf());
796 }
797 };
798 if line.is_empty() {
799 hb(self);
800 continue;
801 }
802 if pos.is_first {
803 self.ibox(config.offset);
804 if cmnt.is_doc && matches!(prefix, "/**") {
805 self.word(prefix);
806 hb(self);
807 prefix = " * ";
808 continue;
809 }
810 }
811
812 self.print_wrapped_line(&line, prefix, 0, cmnt.is_doc);
813
814 if pos.is_last {
815 self.end();
816 if !config.iso_no_break {
817 hb(self);
818 }
819 } else {
820 hb(self);
821 }
822 }
823 } else {
824 for (pos, line) in cmnt.lines.into_iter().delimited() {
826 let hb = |this: &mut Self| {
827 this.hardbreak();
828 if pos.is_last {
829 this.cursor.next_line(this.is_at_crlf());
830 }
831 };
832 if line.is_empty() {
833 hb(self);
834 continue;
835 }
836 if pos.is_first {
837 self.ibox(config.offset);
838 if cmnt.is_doc && matches!(prefix, "/**") {
839 self.word(prefix);
840 hb(self);
841 prefix = " * ";
842 continue;
843 }
844 }
845
846 self.word(line);
847
848 if pos.is_last {
849 self.end();
850 if !config.iso_no_break {
851 hb(self);
852 }
853 } else {
854 hb(self);
855 }
856 }
857 }
858 }
859 CommentStyle::Trailing => {
860 let Some(prefix) = cmnt.prefix() else { return };
861 self.neverbreak();
862 if !self.is_bol_or_only_ind() {
863 self.nbsp();
864 }
865
866 if !self.config.wrap_comments && cmnt.lines.len() == 1 {
867 self.word(cmnt.lines.pop().unwrap());
868 } else if self.config.wrap_comments {
869 if cmnt.is_doc || matches!(cmnt.kind, ast::CommentKind::Line) {
870 config.offset = 0;
871 } else {
872 config.offset = self.ind;
873 }
874 for (lpos, line) in cmnt.lines.into_iter().delimited() {
875 if !line.is_empty() {
876 self.print_wrapped_line(&line, prefix, config.offset, cmnt.is_doc);
877 }
878 if !lpos.is_last {
879 config.hardbreak(&mut self.s);
880 }
881 }
882 } else {
883 self.visual_align();
884 for (pos, line) in cmnt.lines.into_iter().delimited() {
885 if !line.is_empty() {
886 self.word(line);
887 if !pos.is_last {
888 self.hardbreak();
889 }
890 }
891 }
892 self.end();
893 }
894
895 if !config.trailing_no_break {
896 self.print_sep(Separator::Hardbreak);
897 }
898 }
899
900 CommentStyle::BlankLine => {
901 if !self.last_token_is_break() && !self.is_bol_or_only_ind() {
903 config.hardbreak(&mut self.s);
904 self.cursor.next_line(self.is_at_crlf());
905 }
906
907 let twice = match self.last_token() {
909 Some(Token::String(s)) => ";" == s,
910 Some(Token::Begin(_)) => true,
911 Some(Token::End) => true,
912 _ => false,
913 };
914 if twice {
915 config.hardbreak(&mut self.s);
916 self.cursor.next_line(self.is_at_crlf());
917 }
918 config.hardbreak(&mut self.s);
919 self.cursor.next_line(self.is_at_crlf());
920 }
921 }
922 }
923
924 fn peek_comment<'b>(&'b self) -> Option<&'b Comment>
925 where
926 'sess: 'b,
927 {
928 self.comments.peek()
929 }
930
931 fn peek_comment_before<'b>(&'b self, pos: BytePos) -> Option<&'b Comment>
932 where
933 'sess: 'b,
934 {
935 self.comments.iter().take_while(|c| c.pos() < pos).find(|c| !c.style.is_blank())
936 }
937
938 fn has_comment_before_with<F>(&self, pos: BytePos, f: F) -> bool
939 where
940 F: FnMut(&Comment) -> bool,
941 {
942 self.comments.iter().take_while(|c| c.pos() < pos).any(f)
943 }
944
945 fn peek_comment_between<'b>(&'b self, pos_lo: BytePos, pos_hi: BytePos) -> Option<&'b Comment>
946 where
947 'sess: 'b,
948 {
949 self.comments
950 .iter()
951 .take_while(|c| pos_lo < c.pos() && c.pos() < pos_hi)
952 .find(|c| !c.style.is_blank())
953 }
954
955 fn has_comment_between(&self, start_pos: BytePos, end_pos: BytePos) -> bool {
956 self.comments.iter().filter(|c| c.pos() > start_pos && c.pos() < end_pos).any(|_| true)
957 }
958
959 pub(crate) fn next_comment(&mut self) -> Option<Comment> {
960 self.comments.next()
961 }
962
963 fn peek_trailing_comment<'b>(
964 &'b self,
965 span_pos: BytePos,
966 next_pos: Option<BytePos>,
967 ) -> Option<&'b Comment>
968 where
969 'sess: 'b,
970 {
971 self.comments.peek_trailing(self.sm, span_pos, next_pos).map(|(cmnt, _)| cmnt)
972 }
973
974 fn print_trailing_comment_inner(
975 &mut self,
976 span_pos: BytePos,
977 next_pos: Option<BytePos>,
978 config: Option<CommentConfig>,
979 ) -> bool {
980 let mut printed = 0;
981 if let Some((_, n)) = self.comments.peek_trailing(self.sm, span_pos, next_pos) {
982 let config =
983 config.unwrap_or(CommentConfig::skip_ws().mixed_no_break().mixed_prev_space());
984 while printed <= n {
985 let cmnt = self.comments.next().unwrap();
986 if let Some(cmnt) = self.handle_comment(cmnt, config.trailing_no_break) {
987 self.print_comment(cmnt, config);
988 };
989 printed += 1;
990 }
991 }
992 printed != 0
993 }
994
995 fn print_trailing_comment(&mut self, span_pos: BytePos, next_pos: Option<BytePos>) -> bool {
996 self.print_trailing_comment_inner(span_pos, next_pos, None)
997 }
998
999 fn print_trailing_comment_no_break(&mut self, span_pos: BytePos, next_pos: Option<BytePos>) {
1000 self.print_trailing_comment_inner(
1001 span_pos,
1002 next_pos,
1003 Some(CommentConfig::skip_ws().trailing_no_break().mixed_no_break().mixed_prev_space()),
1004 );
1005 }
1006
1007 fn print_remaining_comments(&mut self, skip_leading_ws: bool) {
1008 if self.peek_comment().is_none() && !self.is_bol_or_only_ind() {
1011 self.hardbreak();
1012 return;
1013 }
1014
1015 let mut is_leading = true;
1016 while let Some(cmnt) = self.next_comment() {
1017 if cmnt.style.is_blank() && skip_leading_ws && is_leading {
1018 continue;
1019 }
1020
1021 is_leading = false;
1022 if let Some(cmnt) = self.handle_comment(cmnt, false) {
1023 self.print_comment(cmnt, CommentConfig::default());
1024 } else if self.peek_comment().is_none() && !self.is_bol_or_only_ind() {
1025 self.hardbreak();
1026 }
1027 }
1028 }
1029}
1030
1031#[derive(Clone, Copy)]
1032enum Skip {
1033 All,
1034 Leading { resettable: bool },
1035 Trailing,
1036}
1037
1038#[derive(Default, Clone, Copy)]
1039pub(crate) struct CommentConfig {
1040 skip_blanks: Option<Skip>,
1042 offset: isize,
1043
1044 iso_no_break: bool,
1046 trailing_no_break: bool,
1048 mixed_prev_space: bool,
1050 mixed_post_nbsp: bool,
1051 mixed_no_break_prev: bool,
1052 mixed_no_break_post: bool,
1053}
1054
1055impl CommentConfig {
1056 pub(crate) fn skip_ws() -> Self {
1057 Self { skip_blanks: Some(Skip::All), ..Default::default() }
1058 }
1059
1060 pub(crate) fn skip_leading_ws(resettable: bool) -> Self {
1061 Self { skip_blanks: Some(Skip::Leading { resettable }), ..Default::default() }
1062 }
1063
1064 pub(crate) fn skip_trailing_ws() -> Self {
1065 Self { skip_blanks: Some(Skip::Trailing), ..Default::default() }
1066 }
1067
1068 pub(crate) fn offset(mut self, off: isize) -> Self {
1069 self.offset = off;
1070 self
1071 }
1072
1073 pub(crate) fn no_breaks(mut self) -> Self {
1074 self.iso_no_break = true;
1075 self.trailing_no_break = true;
1076 self.mixed_no_break_prev = true;
1077 self.mixed_no_break_post = true;
1078 self
1079 }
1080
1081 pub(crate) fn trailing_no_break(mut self) -> Self {
1082 self.trailing_no_break = true;
1083 self
1084 }
1085
1086 pub(crate) fn mixed_no_break(mut self) -> Self {
1087 self.mixed_no_break_prev = true;
1088 self.mixed_no_break_post = true;
1089 self
1090 }
1091
1092 pub(crate) fn mixed_no_break_post(mut self) -> Self {
1093 self.mixed_no_break_post = true;
1094 self
1095 }
1096
1097 pub(crate) fn mixed_prev_space(mut self) -> Self {
1098 self.mixed_prev_space = true;
1099 self
1100 }
1101
1102 pub(crate) fn mixed_post_nbsp(mut self) -> Self {
1103 self.mixed_post_nbsp = true;
1104 self
1105 }
1106
1107 pub(crate) fn hardbreak_if_not_bol(&self, is_bol: bool, p: &mut pp::Printer) {
1108 if self.offset != 0 && !is_bol {
1109 self.hardbreak(p);
1110 } else {
1111 p.hardbreak_if_not_bol();
1112 }
1113 }
1114
1115 pub(crate) fn hardbreak(&self, p: &mut pp::Printer) {
1116 p.break_offset(SIZE_INFINITY as usize, self.offset);
1117 }
1118
1119 pub(crate) fn space(&self, p: &mut pp::Printer) {
1120 p.break_offset(1, self.offset);
1121 }
1122
1123 pub(crate) fn nbsp_or_space(&self, breaks: bool, p: &mut pp::Printer) {
1124 if breaks {
1125 self.space(p);
1126 } else {
1127 p.nbsp();
1128 }
1129 }
1130
1131 pub(crate) fn zerobreak(&self, p: &mut pp::Printer) {
1132 p.break_offset(0, self.offset);
1133 }
1134}
1135
1136fn snippet_with_tabs(s: String, tab_width: usize) -> String {
1137 let trimmed = s.trim_start_matches('\n');
1139 let num_breaks = s.len() - trimmed.len();
1140 let mut formatted = std::iter::repeat_n('\n', num_breaks).collect::<String>();
1141
1142 for (pos, line) in trimmed.lines().delimited() {
1144 line_with_tabs(&mut formatted, line, tab_width, None);
1145 if !pos.is_last {
1146 formatted.push('\n');
1147 }
1148 }
1149
1150 formatted
1151}
1152
1153fn style_doc_comment(style: DocCommentStyle, mut cmnt: Comment) -> Comment {
1157 match style {
1158 DocCommentStyle::Line if cmnt.kind == ast::CommentKind::Block => {
1159 let mut new_lines = Vec::new();
1160 for (pos, line) in cmnt.lines.iter().delimited() {
1161 if pos.is_first || pos.is_last {
1162 continue;
1164 }
1165
1166 let trimmed = line.trim_start();
1168 if let Some(content) = trimmed.strip_prefix('*') {
1169 new_lines.push(format!("///{content}"));
1170 } else if !trimmed.is_empty() {
1171 new_lines.push(format!("/// {trimmed}"));
1172 }
1173 }
1174
1175 cmnt.lines = new_lines;
1176 cmnt.kind = ast::CommentKind::Line;
1177 cmnt
1178 }
1179 DocCommentStyle::Block if cmnt.kind == ast::CommentKind::Line => {
1180 let mut new_lines = vec!["/**".to_string()];
1181
1182 for line in &cmnt.lines {
1183 new_lines.push(format!(" *{content}", content = &line[3..]))
1185 }
1186
1187 new_lines.push(" */".to_string());
1188 cmnt.lines = new_lines;
1189 cmnt.kind = ast::CommentKind::Block;
1190 cmnt
1191 }
1192 _ => cmnt,
1194 }
1195}