1use super::{CommentConfig, Separator, State};
2use crate::pp::{BreakToken, Printer, SIZE_INFINITY};
3use foundry_common::iter::IterDelimited;
4use foundry_config::fmt as config;
5use itertools::{Either, Itertools};
6use solar::parse::{
7 Cursor,
8 ast::{self, Span},
9 interface::BytePos,
10};
11use std::{borrow::Cow, fmt::Debug};
12
13pub(crate) trait LitExt<'ast> {
14 fn is_str_concatenation(&self) -> bool;
15}
16
17impl<'ast> LitExt<'ast> for ast::Lit<'ast> {
18 fn is_str_concatenation(&self) -> bool {
20 if let ast::LitKind::Str(_, _, parts) = &self.kind { !parts.is_empty() } else { false }
21 }
22}
23
24impl<'ast> State<'_, 'ast> {
26 pub(super) fn print_lit(&mut self, lit: &'ast ast::Lit<'ast>) {
27 let ast::Lit { span, symbol, ref kind } = *lit;
28 if self.handle_span(span, false) {
29 return;
30 }
31
32 match *kind {
33 ast::LitKind::Str(kind, ..) => {
34 self.s.ibox(0);
35 for (pos, (span, symbol)) in lit.literals().delimited() {
36 if !self.handle_span(span, false) {
37 let quote_pos = span.lo() + kind.prefix().len() as u32;
38 self.print_str_lit(kind, quote_pos, symbol.as_str());
39 }
40 if !pos.is_last {
41 if !self.print_trailing_comment(span.hi(), None) {
42 self.space_if_not_bol();
43 }
44 } else {
45 self.neverbreak();
46 }
47 }
48 self.end();
49 }
50 ast::LitKind::Number(_) | ast::LitKind::Rational(_) => {
51 self.print_num_literal(symbol.as_str());
52 }
53 ast::LitKind::Address(value) => self.word(value.to_string()),
54 ast::LitKind::Bool(value) => self.word(if value { "true" } else { "false" }),
55 ast::LitKind::Err(_) => self.word(symbol.to_string()),
56 }
57 }
58
59 fn print_num_literal(&mut self, source: &str) {
60 fn strip_underscores_if(b: bool, s: &str) -> Cow<'_, str> {
61 if b && s.contains('_') { Cow::Owned(s.replace('_', "")) } else { Cow::Borrowed(s) }
62 }
63
64 fn add_underscores(
65 out: &mut String,
66 config: config::NumberUnderscore,
67 string: &str,
68 is_dec: bool,
69 reversed: bool,
70 ) {
71 if !config.is_thousands() || !is_dec || string.len() < 5 {
72 out.push_str(string);
73 return;
74 }
75
76 let chunks = if reversed {
77 Either::Left(string.as_bytes().chunks(3))
78 } else {
79 Either::Right(string.as_bytes().rchunks(3).rev())
80 }
81 .map(|chunk| std::str::from_utf8(chunk).unwrap());
82 for chunk in Itertools::intersperse(chunks, "_") {
83 out.push_str(chunk);
84 }
85 }
86
87 debug_assert!(source.is_ascii(), "{source:?}");
88
89 let config = self.config.number_underscore;
90 let is_dec = !["0x", "0b", "0o"].iter().any(|prefix| source.starts_with(prefix));
91
92 let (val, exp) = if !is_dec {
93 (source, "")
94 } else {
95 source.split_once(['e', 'E']).unwrap_or((source, ""))
96 };
97 let (val, fract) = val.split_once('.').unwrap_or((val, ""));
98
99 let strip_underscores = !config.is_preserve();
100 let mut val = &strip_underscores_if(strip_underscores, val)[..];
101 let mut exp = &strip_underscores_if(strip_underscores, exp)[..];
102 let mut fract = &strip_underscores_if(strip_underscores, fract)[..];
103
104 let mut exp_sign = "";
106 if is_dec {
107 val = val.trim_start_matches('0');
108 fract = fract.trim_end_matches('0');
109 (exp_sign, exp) =
110 if let Some(exp) = exp.strip_prefix('-') { ("-", exp) } else { ("", exp) };
111 exp = exp.trim_start_matches('0');
112 }
113
114 let mut out = String::with_capacity(source.len() * 2);
115 if val.is_empty() {
116 out.push('0');
117 } else {
118 add_underscores(&mut out, config, val, is_dec, false);
119 }
120 if source.contains('.') {
121 out.push('.');
122 if !fract.is_empty() {
123 add_underscores(&mut out, config, fract, is_dec, true);
124 } else {
125 out.push('0');
126 }
127 }
128 if !exp.is_empty() {
129 out.push('e');
130 out.push_str(exp_sign);
131 add_underscores(&mut out, config, exp, is_dec, false);
132 }
133
134 self.word(out);
135 }
136
137 pub(super) fn print_str_lit(&mut self, kind: ast::StrKind, quote_pos: BytePos, s: &str) {
139 self.print_comments(quote_pos, CommentConfig::default());
140 let s = self.str_lit_to_string(kind, quote_pos, s);
141 self.word(s);
142 }
143
144 fn str_lit_to_string(&self, kind: ast::StrKind, quote_pos: BytePos, s: &str) -> String {
146 let prefix = kind.prefix();
147 let quote = match self.config.quote_style {
148 config::QuoteStyle::Double => '\"',
149 config::QuoteStyle::Single => '\'',
150 config::QuoteStyle::Preserve => self.char_at(quote_pos).unwrap_or_default(),
151 };
152 debug_assert!(matches!(quote, '\"' | '\''), "{quote:?}");
153 let s = solar::parse::interface::data_structures::fmt::from_fn(move |f| {
154 if matches!(kind, ast::StrKind::Hex) {
155 match self.config.hex_underscore {
156 config::HexUnderscore::Preserve => {}
157 config::HexUnderscore::Remove | config::HexUnderscore::Bytes => {
158 let mut clean = s.to_string().replace('_', "");
159 if matches!(self.config.hex_underscore, config::HexUnderscore::Bytes) {
160 clean =
161 clean.chars().chunks(2).into_iter().map(|c| c.format("")).join("_");
162 }
163 return f.write_str(&clean);
164 }
165 };
166 }
167 f.write_str(s)
168 });
169 let mut s = format!("{prefix}{quote}{s}{quote}");
170
171 if Cursor::new(&s).exactly_one().is_err() {
173 let other_quote = if quote == '\"' { '\'' } else { '\"' };
174 {
175 let s = unsafe { s.as_bytes_mut() };
176 s[prefix.len()] = other_quote as u8;
177 s[s.len() - 1] = other_quote as u8;
178 }
179 debug_assert!(Cursor::new(&s).exactly_one().map(|_| true).unwrap());
180 }
181
182 s
183 }
184
185 pub(super) fn print_tuple_empty(&mut self, pos_lo: BytePos, pos_hi: BytePos) {
186 if self.handle_span(Span::new(pos_lo, pos_hi), true) {
187 return;
188 }
189
190 self.print_inside_parens(|state| {
191 state.s.cbox(state.ind);
192 if let Some(cmnt) =
193 state.print_comments(pos_hi, CommentConfig::skip_ws().mixed_prev_space())
194 {
195 if cmnt.is_mixed() {
196 state.s.offset(-state.ind);
197 } else {
198 state.break_offset_if_not_bol(0, -state.ind, false);
199 }
200 }
201 state.end();
202 });
203 }
204
205 pub(super) fn print_tuple<'a, T, P, S>(
206 &mut self,
207 values: &'a [T],
208 pos_lo: BytePos,
209 pos_hi: BytePos,
210 mut print: P,
211 mut get_span: S,
212 format: ListFormat,
213 ) where
214 P: FnMut(&mut Self, &'a T),
215 S: FnMut(&T) -> Span,
216 {
217 if self.handle_span(Span::new(pos_lo, pos_hi), true) {
218 return;
219 }
220
221 if values.is_empty() {
222 self.print_tuple_empty(pos_lo, pos_hi);
223 return;
224 }
225
226 if !(values.len() == 1 && format.is_inline()) {
227 self.print_inside_parens(|state| {
229 state.commasep(values, pos_lo, pos_hi, print, get_span, format)
230 });
231 return;
232 }
233
234 self.print_inside_parens(|state| {
236 let span = get_span(&values[0]);
237 state.s.cbox(state.ind);
238 let mut skip_break = true;
239 if state.peek_comment_before(span.hi()).is_some() {
240 state.hardbreak();
241 skip_break = false;
242 }
243
244 state.print_comments(span.lo(), CommentConfig::skip_ws().mixed_prev_space());
245 print(state, &values[0]);
246
247 if !state.print_trailing_comment(span.hi(), None) && skip_break {
248 state.neverbreak();
249 } else {
250 state.break_offset_if_not_bol(0, -state.ind, false);
251 }
252 state.end();
253 });
254 }
255
256 pub(super) fn print_array<'a, T, P, S>(
257 &mut self,
258 values: &'a [T],
259 span: Span,
260 print: P,
261 get_span: S,
262 ) where
263 P: FnMut(&mut Self, &'a T),
264 S: FnMut(&T) -> Span,
265 {
266 if self.handle_span(span, false) {
267 return;
268 }
269
270 self.print_word("[");
271 self.commasep(values, span.lo(), span.hi(), print, get_span, ListFormat::compact());
272 self.print_word("]");
273 }
274
275 pub(super) fn commasep_opening_logic<T, S>(
276 &mut self,
277 values: &[T],
278 mut get_span: S,
279 format: ListFormat,
280 ) -> bool
281 where
282 S: FnMut(&T) -> Span,
283 {
284 let Some(span) = values.first().map(&mut get_span) else {
285 return false;
286 };
287
288 if let Some((cmnt_span, cmnt_style)) =
290 self.peek_comment_before(span.lo()).map(|c| (c.span, c.style))
291 {
292 let cmnt_disabled = self.inline_config.is_disabled(cmnt_span);
293 if self.cursor.enabled && cmnt_disabled && cmnt_style.is_isolated() {
295 self.print_sep(Separator::Hardbreak);
296 if !format.with_delimiters {
297 self.s.offset(self.ind);
298 }
299 };
300
301 let cmnt_config = if format.with_delimiters {
302 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space()
303 } else {
304 CommentConfig::skip_ws().no_breaks().mixed_prev_space().offset(self.ind)
305 };
306 if let Some(last_style) = self.print_comments(span.lo(), cmnt_config) {
308 match (cmnt_style.is_mixed(), last_style.is_mixed()) {
309 (true, true) => {
310 if format.breaks_cmnts {
311 self.hardbreak();
312 } else {
313 self.space();
314 }
315 if !format.with_delimiters && !cmnt_disabled {
316 self.s.offset(self.ind);
317 }
318 }
319 (false, true) => {
320 self.nbsp();
321 }
322 (false, false) if !format.with_delimiters && !cmnt_disabled => {
323 self.hardbreak();
324 self.s.offset(self.ind);
325 }
326 _ => {}
327 }
328 }
329 if self.cursor.enabled {
330 self.cursor.advance_to(span.lo(), true);
331 }
332 return true;
333 }
334
335 if self.cursor.enabled {
336 self.cursor.advance_to(span.lo(), true);
337 }
338
339 if !values.is_empty() && !format.with_delimiters {
340 self.zerobreak();
341 self.s.offset(self.ind);
342 return true;
343 }
344
345 false
346 }
347
348 pub(super) fn commasep<'a, T, P, S>(
349 &mut self,
350 values: &'a [T],
351 _pos_lo: BytePos,
352 pos_hi: BytePos,
353 mut print: P,
354 mut get_span: S,
355 format: ListFormat,
356 ) where
357 P: FnMut(&mut Self, &'a T),
358 S: FnMut(&T) -> Span,
359 {
360 if values.is_empty() {
361 return;
362 }
363
364 let first = get_span(&values[0]);
365 let has_comments = self.peek_comment_before(first.lo()).is_some()
368 || self.peek_comment_between(first.hi(), pos_hi).is_some();
369 let is_single_without_cmnts = values.len() == 1 && !format.break_single && !has_comments;
370
371 let skip_first_break = if format.with_delimiters || format.is_inline() {
372 self.s.cbox(if format.no_ind { 0 } else { self.ind });
373 if is_single_without_cmnts {
374 true
375 } else {
376 self.commasep_opening_logic(values, &mut get_span, format)
377 }
378 } else {
379 let res = self.commasep_opening_logic(values, &mut get_span, format);
380 self.s.cbox(if format.no_ind { 0 } else { self.ind });
381 res
382 };
383
384 if let Some(sym) = format.prev_symbol() {
385 self.word_space(sym);
386 } else if is_single_without_cmnts && format.with_space {
387 self.nbsp();
388 } else if !skip_first_break && !format.is_inline() {
389 format.print_break(true, values.len(), &mut self.s);
390 }
391
392 if format.is_compact() && !(format.breaks_with_comments() && has_comments) {
393 self.s.cbox(0);
394 }
395
396 let mut skip_last_break =
397 is_single_without_cmnts || !format.with_delimiters || format.is_inline();
398 for (i, value) in values.iter().enumerate() {
399 let is_last = i == values.len() - 1;
400 if self
401 .print_comments(get_span(value).lo(), CommentConfig::skip_ws().mixed_prev_space())
402 .is_some_and(|cmnt| cmnt.is_mixed())
403 && format.breaks_cmnts
404 {
405 self.hardbreak(); }
407
408 print(self, value);
409
410 let next_span = if is_last { None } else { Some(get_span(&values[i + 1])) };
411 let next_pos = next_span.map(Span::lo).unwrap_or(pos_hi);
412 let cmnt_before_next =
413 self.peek_comment_before(next_pos).map(|cmnt| (cmnt.span, cmnt.style));
414
415 if !is_last {
416 if cmnt_before_next.is_some_and(|(cmnt_span, _)| {
418 let span = self.cursor.span(cmnt_span.lo());
419 self.inline_config.is_disabled(span)
420 && self.sm.span_to_snippet(span).is_ok_and(|snip| !snip.contains(','))
422 }) {
423 self.print_comments(
424 next_pos,
425 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
426 );
427 }
428 self.print_word(",");
429 }
430
431 if !is_last
432 && format.breaks_cmnts
433 && cmnt_before_next.is_some_and(|(cmnt_span, cmnt_style)| {
434 let disabled = self.inline_config.is_disabled(cmnt_span);
435 (cmnt_style.is_mixed() && !disabled) || (cmnt_style.is_isolated() && disabled)
436 })
437 {
438 self.hardbreak(); }
440
441 let comment_config = if !is_last || format.with_delimiters {
443 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space()
444 } else {
445 CommentConfig::skip_ws().no_breaks().mixed_prev_space()
446 };
447 self.print_comments(next_pos, comment_config);
448
449 if is_last && self.is_bol_or_only_ind() {
450 self.break_offset_if_not_bol(0, -self.ind, false);
453 skip_last_break = true;
454 }
455
456 if let Some(next_span) = next_span
458 && !self.is_bol_or_only_ind()
459 && !self.inline_config.is_disabled(next_span)
460 {
461 if next_span.is_dummy() && !matches!(format.kind, ListFormatKind::AlwaysBreak) {
462 self.zerobreak();
464 } else {
465 format.print_break(false, values.len(), &mut self.s);
466 }
467 }
468 }
469
470 if format.is_compact() && !(format.breaks_with_comments() && has_comments) {
471 self.end();
472 }
473 if !skip_last_break {
474 if let Some(sym) = format.post_symbol() {
475 format.print_break(false, values.len(), &mut self.s);
476 self.s.offset(-self.ind);
477 self.word(sym);
478 } else {
479 format.print_break(true, values.len(), &mut self.s);
480 self.s.offset(-self.ind);
481 }
482 } else if is_single_without_cmnts && format.with_space {
483 self.nbsp();
484 } else if let Some(sym) = format.post_symbol() {
485 self.nbsp();
486 self.word(sym);
487 }
488
489 self.end();
490 self.cursor.advance_to(pos_hi, true);
491
492 if !format.with_delimiters {
493 self.zerobreak();
494 }
495 }
496
497 pub(super) fn print_path(&mut self, path: &'ast ast::PathSlice, consistent_break: bool) {
498 if consistent_break {
499 self.s.cbox(self.ind);
500 } else {
501 self.s.ibox(self.ind);
502 }
503 for (pos, ident) in path.segments().iter().delimited() {
504 self.print_ident(ident);
505 if !pos.is_last {
506 self.zerobreak();
507 self.word(".");
508 }
509 }
510 self.end();
511 }
512
513 pub(super) fn print_block_inner<T: Debug>(
514 &mut self,
515 block: &'ast [T],
516 block_format: BlockFormat,
517 mut print: impl FnMut(&mut Self, &'ast T),
518 mut get_block_span: impl FnMut(&'ast T) -> Span,
519 pos_hi: BytePos,
520 ) {
521 if block_format.attempt_single_line() && block.len() == 1 {
523 self.print_single_line_block(block, block_format, print, get_block_span);
524 return;
525 }
526
527 if block.is_empty() {
529 self.print_empty_block(block_format, pos_hi);
530 return;
531 }
532
533 self.block_depth += 1;
535
536 let block_lo = get_block_span(&block[0]).lo();
538 match block_format {
539 BlockFormat::NoBraces(None) => {
540 if !self.handle_span(self.cursor.span(block_lo), false) {
541 self.print_comments(block_lo, CommentConfig::default());
542 }
543 self.s.cbox(0);
544 }
545 BlockFormat::NoBraces(Some(offset)) => {
546 let enabled =
547 !self.inline_config.is_disabled(Span::new(block_lo, block_lo + BytePos(1)))
548 && !self.handle_span(self.cursor.span(block_lo), true);
549 match self.peek_comment().and_then(|cmnt| {
550 if cmnt.span.hi() < block_lo { Some((cmnt.span, cmnt.style)) } else { None }
551 }) {
552 Some((span, style)) => {
553 if enabled {
554 if !self.inline_config.is_disabled(span) || style.is_isolated() {
556 self.cursor.advance_to(span.lo(), true);
557 self.break_offset(SIZE_INFINITY as usize, offset);
558 }
559 if let Some(cmnt) = self.print_comments(
560 block_lo,
561 CommentConfig::skip_leading_ws(false).offset(offset),
562 ) && !cmnt.is_mixed()
563 && !cmnt.is_blank()
564 {
565 self.s.offset(offset);
566 }
567 } else if style.is_isolated() {
568 self.print_sep_unhandled(Separator::Space);
569 self.s.offset(offset);
570 }
571 }
572 None => {
573 if enabled {
574 self.zerobreak();
575 self.s.offset(offset);
576 } else if self.cursor.enabled {
577 self.print_sep_unhandled(Separator::Space);
578 self.s.offset(offset);
579 self.cursor.advance_to(block_lo, true);
580 }
581 }
582 }
583 self.s.cbox(self.ind);
584 }
585 _ => {
586 self.print_word("{");
587 self.s.cbox(self.ind);
588 if !self.handle_span(self.cursor.span(block_lo), false)
589 && self
590 .print_comments(block_lo, CommentConfig::default())
591 .is_none_or(|cmnt| cmnt.is_mixed())
592 {
593 self.hardbreak_if_nonempty();
594 }
595 }
596 }
597
598 for (i, stmt) in block.iter().enumerate() {
600 let is_last = i == block.len() - 1;
601 print(self, stmt);
602
603 let is_disabled = self.inline_config.is_disabled(get_block_span(stmt));
604 let mut next_enabled = false;
605 let mut next_lo = None;
606 if !is_last {
607 let next_span = get_block_span(&block[i + 1]);
608 next_enabled = !self.inline_config.is_disabled(next_span);
609 next_lo =
610 self.peek_comment_before(next_span.lo()).is_none().then_some(next_span.lo());
611 }
612
613 if !is_disabled
615 && next_enabled
616 && (!is_last
617 || self.peek_comment_before(pos_hi).is_some_and(|cmnt| cmnt.style.is_mixed()))
618 {
619 self.hardbreak_if_not_bol();
620 continue;
621 }
622 if is_disabled
625 && next_enabled
626 && let Some(next_lo) = next_lo
627 && self
628 .peek_comment_before(next_lo)
629 .is_none_or(|cmnt| self.inline_config.is_disabled(cmnt.span))
630 {
631 self.hardbreak_if_not_bol()
632 }
633 }
634
635 self.print_comments(
636 pos_hi,
637 CommentConfig::skip_trailing_ws().mixed_no_break().mixed_prev_space(),
638 );
639 if !block_format.breaks() {
640 if !self.last_token_is_break() {
641 self.hardbreak();
642 }
643 self.s.offset(-self.ind);
644 }
645 self.end();
646 if block_format.with_braces() {
647 self.print_word("}");
648 }
649
650 self.block_depth -= 1;
652 }
653
654 fn print_single_line_block<T: Debug>(
655 &mut self,
656 block: &'ast [T],
657 block_format: BlockFormat,
658 mut print: impl FnMut(&mut Self, &'ast T),
659 mut get_block_span: impl FnMut(&'ast T) -> Span,
660 ) {
661 self.s.cbox(self.ind);
662
663 match block_format {
664 BlockFormat::Compact(true) => {
665 self.scan_break(BreakToken { pre_break: Some("{"), ..Default::default() });
666 print(self, &block[0]);
667 self.print_comments(get_block_span(&block[0]).hi(), CommentConfig::default());
668 self.s.scan_break(BreakToken { post_break: Some("}"), ..Default::default() });
669 self.s.offset(-self.ind);
670 }
671 _ => {
672 self.word("{");
673 self.space();
674 print(self, &block[0]);
675 self.print_comments(get_block_span(&block[0]).hi(), CommentConfig::default());
676 self.space_if_not_bol();
677 self.s.offset(-self.ind);
678 self.word("}");
679 }
680 }
681
682 self.end();
683 }
684
685 fn print_empty_block(&mut self, block_format: BlockFormat, pos_hi: BytePos) {
686 let has_braces = block_format.with_braces();
687
688 if self.peek_comment_before(pos_hi).is_none_or(|c| c.style.is_trailing()) {
690 if self.config.bracket_spacing {
691 if has_braces {
692 self.word("{ }");
693 } else {
694 self.nbsp();
695 }
696 } else if has_braces {
697 self.word("{}");
698 }
699 self.print_comments(pos_hi, CommentConfig::skip_ws());
700 return;
701 }
702
703 if has_braces {
705 self.word("{");
706 }
707 let mut offset = 0;
708 if let BlockFormat::NoBraces(Some(off)) = block_format {
709 offset = off;
710 }
711 self.print_comments(
712 pos_hi,
713 self.cmnt_config().offset(offset).mixed_no_break().mixed_prev_space().mixed_post_nbsp(),
714 );
715 self.print_comments(
716 pos_hi,
717 CommentConfig::default().mixed_no_break().mixed_prev_space().mixed_post_nbsp(),
718 );
719 if has_braces {
720 self.word("}");
721 }
722 }
723}
724
725#[derive(Debug, Clone, Copy, PartialEq, Eq)]
727pub(crate) struct ListFormat {
728 kind: ListFormatKind,
730 no_ind: bool,
732 break_single: bool,
734 breaks_cmnts: bool,
736 with_space: bool,
738 with_delimiters: bool,
740}
741
742#[derive(Debug, Clone, Copy, PartialEq, Eq)]
744pub(crate) enum ListFormatKind {
745 AlwaysBreak,
747 Consistent,
749 Compact,
751 Inline,
753 Yul { sym_prev: Option<&'static str>, sym_post: Option<&'static str> },
755}
756
757impl Default for ListFormat {
758 fn default() -> Self {
759 Self {
760 kind: ListFormatKind::Consistent,
761 no_ind: false,
762 break_single: false,
763 breaks_cmnts: false,
764 with_space: false,
765 with_delimiters: true,
766 }
767 }
768}
769
770impl ListFormat {
771 pub(crate) fn prev_symbol(&self) -> Option<&'static str> {
773 if let ListFormatKind::Yul { sym_prev, .. } = self.kind { sym_prev } else { None }
774 }
775
776 pub(crate) fn post_symbol(&self) -> Option<&'static str> {
777 if let ListFormatKind::Yul { sym_post, .. } = self.kind { sym_post } else { None }
778 }
779
780 pub(crate) fn is_compact(&self) -> bool {
781 matches!(self.kind, ListFormatKind::Compact)
782 }
783
784 pub(crate) fn is_inline(&self) -> bool {
785 matches!(self.kind, ListFormatKind::Inline)
786 }
787
788 pub(crate) fn breaks_with_comments(&self) -> bool {
789 self.breaks_cmnts
790 }
791
792 pub(crate) fn inline() -> Self {
794 Self { kind: ListFormatKind::Inline, ..Default::default() }
795 }
796
797 pub(crate) fn consistent() -> Self {
798 Self { kind: ListFormatKind::Consistent, ..Default::default() }
799 }
800
801 pub(crate) fn compact() -> Self {
802 Self { kind: ListFormatKind::Compact, ..Default::default() }
803 }
804
805 pub(crate) fn always_break() -> Self {
806 Self {
807 kind: ListFormatKind::AlwaysBreak,
808 breaks_cmnts: true,
809 break_single: true,
810 with_delimiters: true,
811 ..Default::default()
812 }
813 }
814
815 pub(crate) fn yul(sym_prev: Option<&'static str>, sym_post: Option<&'static str>) -> Self {
816 Self {
817 kind: ListFormatKind::Yul { sym_prev, sym_post },
818 breaks_cmnts: true,
819 with_delimiters: true,
820 ..Default::default()
821 }
822 }
823
824 pub(crate) fn without_ind(mut self, without: bool) -> Self {
825 if !matches!(self.kind, ListFormatKind::Inline) {
826 self.no_ind = without;
827 }
828 self
829 }
830
831 pub(crate) fn break_single(mut self, value: bool) -> Self {
832 if !matches!(self.kind, ListFormatKind::Inline) {
833 self.break_single = value;
834 }
835 self
836 }
837
838 pub(crate) fn break_cmnts(mut self) -> Self {
839 if !matches!(self.kind, ListFormatKind::Inline) {
840 self.breaks_cmnts = true;
841 }
842 self
843 }
844
845 pub(crate) fn with_space(mut self) -> Self {
846 if !matches!(self.kind, ListFormatKind::Inline) {
847 self.with_space = true;
848 }
849 self
850 }
851
852 pub(crate) fn no_delimiters(mut self) -> Self {
853 if matches!(self.kind, ListFormatKind::Compact | ListFormatKind::Consistent) {
854 self.with_delimiters = false;
855 }
856 self
857 }
858
859 pub(crate) fn print_break(&self, soft: bool, elems: usize, p: &mut Printer) {
861 match self.kind {
862 ListFormatKind::Inline => p.nbsp(), ListFormatKind::AlwaysBreak if elems > 1 || (self.break_single && elems == 1) => {
864 p.hardbreak()
865 }
866 _ => {
867 if soft && !self.with_space {
868 p.zerobreak();
869 } else {
870 p.space();
871 }
872 }
873 }
874 }
875}
876
877#[derive(Debug, Clone, Copy, PartialEq, Eq)]
879#[expect(dead_code)]
880pub(crate) enum BlockFormat {
881 Regular,
882 Compact(bool),
885 NoBraces(Option<isize>),
888}
889
890impl BlockFormat {
891 pub(crate) fn with_braces(&self) -> bool {
892 !matches!(self, Self::NoBraces(_))
893 }
894 pub(crate) fn breaks(&self) -> bool {
895 matches!(self, Self::NoBraces(None))
896 }
897
898 pub(crate) fn attempt_single_line(&self) -> bool {
899 matches!(self, Self::Compact(_))
900 }
901}