Skip to main content

rustfmt_nightly/
utils.rs

1use std::borrow::Cow;
2
3use rustc_ast::YieldKind;
4use rustc_ast::ast::{
5    self, Attribute, ImplRestriction, MetaItem, MetaItemInner, MetaItemKind, MutRestriction,
6    NodeId, Path, RestrictionKind, Visibility, VisibilityKind,
7};
8use rustc_ast_pretty::pprust;
9use rustc_feature::is_builtin_attr_name;
10use rustc_span::{BytePos, LocalExpnId, Span, Symbol, SyntaxContext, sym, symbol};
11use unicode_width::UnicodeWidthStr;
12
13use crate::comment::{CharClasses, FullCodeCharKind, LineClasses, filter_normal_code};
14use crate::config::{Config, StyleEdition};
15use crate::rewrite::RewriteContext;
16use crate::shape::{Indent, Shape};
17
18#[inline]
19pub(crate) fn depr_skip_annotation() -> Symbol {
20    Symbol::intern("rustfmt_skip")
21}
22
23#[inline]
24pub(crate) fn skip_annotation() -> Symbol {
25    Symbol::intern("rustfmt::skip")
26}
27
28pub(crate) fn rewrite_ident<'a>(context: &'a RewriteContext<'_>, ident: symbol::Ident) -> &'a str {
29    context.snippet(ident.span)
30}
31
32// Computes the length of a string's last line, minus offset.
33pub(crate) fn extra_offset(text: &str, shape: Shape) -> usize {
34    match text.rfind('\n') {
35        // 1 for newline character
36        Some(idx) => text.len().saturating_sub(idx + 1 + shape.used_width()),
37        None => text.len(),
38    }
39}
40
41pub(crate) fn is_same_visibility(a: &Visibility, b: &Visibility) -> bool {
42    match (&a.kind, &b.kind) {
43        (
44            VisibilityKind::Restricted { path: p, .. },
45            VisibilityKind::Restricted { path: q, .. },
46        ) => pprust::path_to_string(p) == pprust::path_to_string(q),
47        (VisibilityKind::Public, VisibilityKind::Public)
48        | (VisibilityKind::Inherited, VisibilityKind::Inherited) => true,
49        _ => false,
50    }
51}
52
53// Uses Cow to avoid allocating in the common cases.
54pub(crate) fn format_visibility(
55    context: &RewriteContext<'_>,
56    vis: &Visibility,
57) -> Cow<'static, str> {
58    match vis.kind {
59        VisibilityKind::Public => Cow::from("pub "),
60        VisibilityKind::Inherited => Cow::from(""),
61        VisibilityKind::Restricted { ref path, .. } => {
62            let Path { ref segments, .. } = **path;
63            let mut segments_iter = segments.iter().map(|seg| rewrite_ident(context, seg.ident));
64            if path.is_global() {
65                segments_iter
66                    .next()
67                    .expect("Non-global path in pub(restricted)?");
68            }
69            let is_keyword = |s: &str| s == "crate" || s == "self" || s == "super";
70            let path = segments_iter.collect::<Vec<_>>().join("::");
71            let in_str = if is_keyword(&path) { "" } else { "in " };
72
73            Cow::from(format!("pub({in_str}{path}) "))
74        }
75    }
76}
77
78pub(crate) fn format_impl_restriction(
79    context: &RewriteContext<'_>,
80    impl_restriction: &ImplRestriction,
81) -> String {
82    format_restriction("impl", context, &impl_restriction.kind)
83}
84
85pub(crate) fn format_mut_restriction(
86    context: &RewriteContext<'_>,
87    mut_restriction: &MutRestriction,
88) -> String {
89    format_restriction("mut", context, &mut_restriction.kind)
90}
91
92fn format_restriction(
93    kw: &'static str,
94    context: &RewriteContext<'_>,
95    restriction: &RestrictionKind,
96) -> String {
97    match restriction {
98        RestrictionKind::Unrestricted => String::new(),
99        RestrictionKind::Restricted {
100            ref path,
101            id: _,
102            shorthand,
103        } => {
104            let Path { ref segments, .. } = **path;
105            let mut segments_iter = segments.iter().map(|seg| rewrite_ident(context, seg.ident));
106            if path.is_global() && segments_iter.next().is_none() {
107                panic!("non-global path in {kw}(restricted)?");
108            }
109            // FIXME use `segments_iter.intersperse("::").collect::<String>()` once
110            // `#![feature(iter_intersperse)]` is re-stabilized.
111            let path = itertools::join(segments_iter, "::");
112            let in_str = if *shorthand { "" } else { "in " };
113
114            format!("{kw}({in_str}{path}) ")
115        }
116    }
117}
118
119#[inline]
120pub(crate) fn format_coro(coroutine_marker: ast::CoroutineMarker) -> &'static str {
121    match coroutine_marker.kind {
122        ast::CoroutineKind::Async => "async ",
123        ast::CoroutineKind::Gen => "gen ",
124        ast::CoroutineKind::AsyncGen => "async gen ",
125    }
126}
127
128#[inline]
129pub(crate) fn format_constness(constness: ast::Const) -> &'static str {
130    match constness {
131        ast::Const::Yes(..) => "const ",
132        ast::Const::No => "",
133    }
134}
135
136#[inline]
137pub(crate) fn format_defaultness(defaultness: ast::Defaultness) -> &'static str {
138    match defaultness {
139        ast::Defaultness::Implicit => "",
140        ast::Defaultness::Default(..) => "default ",
141        ast::Defaultness::Final(..) => "final ",
142    }
143}
144
145#[inline]
146pub(crate) fn format_safety(unsafety: ast::Safety) -> &'static str {
147    match unsafety {
148        ast::Safety::Unsafe(..) => "unsafe ",
149        ast::Safety::Safe(..) => "safe ",
150        ast::Safety::Default => "",
151    }
152}
153
154#[inline]
155pub(crate) fn format_auto(is_auto: ast::IsAuto) -> &'static str {
156    match is_auto {
157        ast::IsAuto::Yes => "auto ",
158        ast::IsAuto::No => "",
159    }
160}
161
162#[inline]
163pub(crate) fn format_mutability(mutability: ast::Mutability) -> &'static str {
164    match mutability {
165        ast::Mutability::Mut => "mut ",
166        ast::Mutability::Not => "",
167    }
168}
169
170#[inline]
171pub(crate) fn format_pinnedness_and_mutability(
172    pinnedness: ast::Pinnedness,
173    mutability: ast::Mutability,
174) -> (&'static str, &'static str) {
175    match (pinnedness, mutability) {
176        (ast::Pinnedness::Pinned, ast::Mutability::Mut) => ("pin ", "mut "),
177        (ast::Pinnedness::Pinned, ast::Mutability::Not) => ("pin ", "const "),
178        (ast::Pinnedness::Not, ast::Mutability::Mut) => ("", "mut "),
179        (ast::Pinnedness::Not, ast::Mutability::Not) => ("", ""),
180    }
181}
182
183#[inline]
184pub(crate) fn format_range_end(end: ast::RangeEnd) -> &'static str {
185    match end {
186        ast::RangeEnd::Included(ast::RangeSyntax::DotDotDot) => "...",
187        ast::RangeEnd::Included(ast::RangeSyntax::DotDotEq) => "..=",
188        ast::RangeEnd::Excluded => "..",
189    }
190}
191
192#[inline]
193pub(crate) fn format_extern(ext: ast::Extern, explicit_abi: bool) -> Cow<'static, str> {
194    match ext {
195        ast::Extern::None => Cow::from(""),
196        ast::Extern::Implicit(_) if explicit_abi => Cow::from("extern \"C\" "),
197        ast::Extern::Implicit(_) => Cow::from("extern "),
198        // turn `extern "C"` into `extern` when `explicit_abi` is set to false
199        ast::Extern::Explicit(abi, _) if abi.symbol_unescaped == sym::C && !explicit_abi => {
200            Cow::from("extern ")
201        }
202        ast::Extern::Explicit(abi, _) => {
203            Cow::from(format!(r#"extern "{}" "#, abi.symbol_unescaped))
204        }
205    }
206}
207
208#[inline]
209// Transform `Vec<Box<T>>` into `Vec<&T>`
210pub(crate) fn ptr_vec_to_ref_vec<T>(vec: &[Box<T>]) -> Vec<&T> {
211    vec.iter().map(|x| &**x).collect::<Vec<_>>()
212}
213
214#[inline]
215pub(crate) fn filter_attributes(
216    attrs: &[ast::Attribute],
217    style: ast::AttrStyle,
218) -> Vec<ast::Attribute> {
219    attrs
220        .iter()
221        .filter(|a| a.style == style)
222        .cloned()
223        .collect::<Vec<_>>()
224}
225
226#[inline]
227pub(crate) fn inner_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> {
228    filter_attributes(attrs, ast::AttrStyle::Inner)
229}
230
231#[inline]
232pub(crate) fn outer_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> {
233    filter_attributes(attrs, ast::AttrStyle::Outer)
234}
235
236#[inline]
237pub(crate) fn is_single_line(s: &str) -> bool {
238    !s.chars().any(|c| c == '\n')
239}
240
241#[inline]
242pub(crate) fn first_line_contains_single_line_comment(s: &str) -> bool {
243    s.lines().next().map_or(false, |l| l.contains("//"))
244}
245
246#[inline]
247pub(crate) fn last_line_contains_single_line_comment(s: &str) -> bool {
248    s.lines().last().map_or(false, |l| l.contains("//"))
249}
250
251#[inline]
252pub(crate) fn is_attributes_extendable(attrs_str: &str) -> bool {
253    !attrs_str.contains('\n') && !last_line_contains_single_line_comment(attrs_str)
254}
255
256/// The width of the first line in s.
257#[inline]
258pub(crate) fn first_line_width(s: &str) -> usize {
259    unicode_str_width(s.splitn(2, '\n').next().unwrap_or(""))
260}
261
262/// The width of the last line in s.
263#[inline]
264pub(crate) fn last_line_width(s: &str, tab_spaces: usize) -> usize {
265    let last_line = s.rsplitn(2, '\n').next().unwrap_or("");
266    let (prefix_width, prefix_end) = get_prefix_space_width_and_end(last_line, tab_spaces);
267    prefix_width + unicode_str_width(&last_line[prefix_end..])
268}
269
270/// The total used width of the last line.
271#[inline]
272pub(crate) fn last_line_used_width(s: &str, offset: usize, tab_spaces: usize) -> usize {
273    if s.contains('\n') {
274        last_line_width(s, tab_spaces)
275    } else {
276        offset + unicode_str_width(s)
277    }
278}
279
280#[inline]
281pub(crate) fn trimmed_last_line_width(s: &str) -> usize {
282    unicode_str_width(match s.rfind('\n') {
283        Some(n) => s[(n + 1)..].trim(),
284        None => s.trim(),
285    })
286}
287
288#[inline]
289pub(crate) fn last_line_extendable(s: &str) -> bool {
290    if s.ends_with("\"#") {
291        return true;
292    }
293    for c in s.chars().rev() {
294        match c {
295            '(' | ')' | ']' | '}' | '?' | '>' => continue,
296            '\n' => break,
297            _ if c.is_whitespace() => continue,
298            _ => return false,
299        }
300    }
301    true
302}
303
304#[inline]
305fn is_skip(meta_item: &MetaItem) -> bool {
306    match meta_item.kind {
307        MetaItemKind::Word => {
308            let path_str = pprust::path_to_string(&meta_item.path);
309            path_str == skip_annotation().as_str() || path_str == depr_skip_annotation().as_str()
310        }
311        MetaItemKind::List(ref l) => {
312            meta_item.has_name(sym::cfg_attr) && l.len() == 2 && is_skip_nested(&l[1])
313        }
314        _ => false,
315    }
316}
317
318#[inline]
319fn is_skip_nested(meta_item: &MetaItemInner) -> bool {
320    match meta_item {
321        MetaItemInner::MetaItem(ref mi) => is_skip(mi),
322        MetaItemInner::Lit(_) => false,
323    }
324}
325
326#[inline]
327pub(crate) fn contains_skip(attrs: &[Attribute]) -> bool {
328    attrs
329        .iter()
330        .any(|a| a.meta().map_or(false, |a| is_skip(&a)))
331}
332
333#[inline]
334pub(crate) fn contains_custom_attributes(attrs: &[Attribute]) -> bool {
335    attrs
336        .iter()
337        .any(|a| a.name().is_some_and(|name| !is_builtin_attr_name(name)))
338}
339
340#[inline]
341pub(crate) fn semicolon_for_expr(context: &RewriteContext<'_>, expr: &ast::Expr) -> bool {
342    // Never try to insert semicolons on expressions when we're inside
343    // a macro definition - this can prevent the macro from compiling
344    // when used in expression position
345    if context.is_macro_def {
346        return false;
347    }
348
349    match expr.kind {
350        ast::ExprKind::Ret(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Break(..) => {
351            context.config.trailing_semicolon()
352        }
353        _ => false,
354    }
355}
356
357#[inline]
358pub(crate) fn semicolon_for_stmt(
359    context: &RewriteContext<'_>,
360    stmt: &ast::Stmt,
361    is_last_expr: bool,
362) -> bool {
363    match stmt.kind {
364        ast::StmtKind::Semi(ref expr) => match expr.kind {
365            ast::ExprKind::While(..) | ast::ExprKind::Loop(..) | ast::ExprKind::ForLoop { .. } => {
366                false
367            }
368            ast::ExprKind::Break(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Ret(..) => {
369                // The only time we can skip the semi-colon is if the config option is set to false
370                // **and** this is the last expr (even though any following exprs are unreachable)
371                context.config.trailing_semicolon() || !is_last_expr
372            }
373            _ => true,
374        },
375        ast::StmtKind::Expr(..) => false,
376        _ => true,
377    }
378}
379
380#[inline]
381pub(crate) fn stmt_expr(stmt: &ast::Stmt) -> Option<&ast::Expr> {
382    match stmt.kind {
383        ast::StmtKind::Expr(ref expr) => Some(expr),
384        _ => None,
385    }
386}
387
388/// Returns the number of LF and CRLF respectively.
389pub(crate) fn count_lf_crlf(input: &str) -> (usize, usize) {
390    let mut lf = 0;
391    let mut crlf = 0;
392    let mut is_crlf = false;
393    for c in input.as_bytes() {
394        match c {
395            b'\r' => is_crlf = true,
396            b'\n' if is_crlf => crlf += 1,
397            b'\n' => lf += 1,
398            _ => is_crlf = false,
399        }
400    }
401    (lf, crlf)
402}
403
404pub(crate) fn count_newlines(input: &str) -> usize {
405    // Using bytes to omit UTF-8 decoding
406    bytecount::count(input.as_bytes(), b'\n')
407}
408
409// For format_missing and last_pos, need to use the source callsite (if applicable).
410// Required as generated code spans aren't guaranteed to follow on from the last span.
411macro_rules! source {
412    ($this:ident, $sp:expr) => {
413        $sp.source_callsite()
414    };
415}
416
417pub(crate) fn mk_sp(lo: BytePos, hi: BytePos) -> Span {
418    Span::new(lo, hi, SyntaxContext::root(), None)
419}
420
421pub(crate) fn mk_sp_lo_plus_one(lo: BytePos) -> Span {
422    Span::new(lo, lo + BytePos(1), SyntaxContext::root(), None)
423}
424
425// Returns `true` if the given span does not intersect with file lines.
426macro_rules! out_of_file_lines_range {
427    ($self:ident, $span:expr) => {
428        !$self.config.file_lines().is_all()
429            && !$self
430                .config
431                .file_lines()
432                .intersects(&$self.psess.lookup_line_range($span))
433    };
434}
435
436macro_rules! skip_out_of_file_lines_range_err {
437    ($self:ident, $span:expr) => {
438        if out_of_file_lines_range!($self, $span) {
439            return Err(RewriteError::SkipFormatting);
440        }
441    };
442}
443
444macro_rules! skip_out_of_file_lines_range_visitor {
445    ($self:ident, $span:expr) => {
446        if out_of_file_lines_range!($self, $span) {
447            $self.push_rewrite($span, None);
448            return;
449        }
450    };
451}
452
453// Wraps String in an Option. Returns Some when the string adheres to the
454// Rewrite constraints defined for the Rewrite trait and None otherwise.
455pub(crate) fn wrap_str(
456    s: String,
457    max_width: usize,
458    tab_spaces: usize,
459    shape: Shape,
460) -> Option<String> {
461    if filtered_str_fits(&s, max_width, tab_spaces, shape) {
462        Some(s)
463    } else {
464        None
465    }
466}
467
468pub(crate) fn filtered_str_fits(
469    snippet: &str,
470    max_width: usize,
471    tab_spaces: usize,
472    shape: Shape,
473) -> bool {
474    let snippet = &filter_normal_code(snippet);
475    if !snippet.is_empty() {
476        // First line must fits with `shape.width`.
477        if first_line_width(snippet) > shape.width {
478            return false;
479        }
480        // If the snippet does not include newline, we are done.
481        if is_single_line(snippet) {
482            return true;
483        }
484        // The other lines must fit within the maximum width.
485        if snippet
486            .lines()
487            .skip(1)
488            .any(|line| unicode_str_width(line) > max_width)
489        {
490            return false;
491        }
492        // A special check for the last line, since the caller may
493        // place trailing characters on this line.
494        if last_line_width(snippet, tab_spaces) > shape.used_width() + shape.width {
495            return false;
496        }
497    }
498    true
499}
500
501#[inline]
502pub(crate) fn colon_spaces(config: &Config) -> &'static str {
503    let before = config.space_before_colon();
504    let after = config.space_after_colon();
505    match (before, after) {
506        (true, true) => " : ",
507        (true, false) => " :",
508        (false, true) => ": ",
509        (false, false) => ":",
510    }
511}
512
513#[inline]
514pub(crate) fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr {
515    match e.kind {
516        ast::ExprKind::Call(ref e, _)
517        | ast::ExprKind::Binary(_, ref e, _)
518        | ast::ExprKind::Cast(ref e, _)
519        | ast::ExprKind::Type(ref e, _)
520        | ast::ExprKind::Assign(ref e, _, _)
521        | ast::ExprKind::AssignOp(_, ref e, _)
522        | ast::ExprKind::Field(ref e, _)
523        | ast::ExprKind::Index(ref e, _, _)
524        | ast::ExprKind::Range(Some(ref e), _, _)
525        | ast::ExprKind::Try(ref e) => left_most_sub_expr(e),
526        _ => e,
527    }
528}
529
530#[inline]
531pub(crate) fn starts_with_newline(s: &str) -> bool {
532    s.starts_with('\n') || s.starts_with("\r\n")
533}
534
535#[inline]
536pub(crate) fn first_line_ends_with(s: &str, c: char) -> bool {
537    s.lines().next().map_or(false, |l| l.ends_with(c))
538}
539
540// States whether an expression's last line exclusively consists of closing
541// parens, braces, and brackets in its idiomatic formatting.
542pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr: &str) -> bool {
543    match expr.kind {
544        ast::ExprKind::MacCall(..)
545        | ast::ExprKind::FormatArgs(..)
546        | ast::ExprKind::Call(..)
547        | ast::ExprKind::MethodCall(..)
548        | ast::ExprKind::Array(..)
549        | ast::ExprKind::Struct(..)
550        | ast::ExprKind::While(..)
551        | ast::ExprKind::If(..)
552        | ast::ExprKind::Block(..)
553        | ast::ExprKind::ConstBlock(..)
554        | ast::ExprKind::Gen(..)
555        | ast::ExprKind::Loop(..)
556        | ast::ExprKind::ForLoop { .. }
557        | ast::ExprKind::TryBlock(..)
558        | ast::ExprKind::Match(..) => repr.contains('\n'),
559        ast::ExprKind::Paren(ref expr)
560        | ast::ExprKind::Binary(_, _, ref expr)
561        | ast::ExprKind::Index(_, ref expr, _)
562        | ast::ExprKind::Unary(_, ref expr)
563        | ast::ExprKind::Try(ref expr)
564        | ast::ExprKind::Yield(YieldKind::Prefix(Some(ref expr)))
565        | ast::ExprKind::GcaMacro(ref expr) => is_block_expr(context, expr, repr),
566        ast::ExprKind::Closure(ref closure) => is_block_expr(context, &closure.body, repr),
567        // This can only be a string lit
568        ast::ExprKind::Lit(_) => {
569            repr.contains('\n') && trimmed_last_line_width(repr) <= context.config.tab_spaces()
570        }
571        ast::ExprKind::AddrOf(..)
572        | ast::ExprKind::Assign(..)
573        | ast::ExprKind::AssignOp(..)
574        | ast::ExprKind::Await(..)
575        | ast::ExprKind::Break(..)
576        | ast::ExprKind::Cast(..)
577        | ast::ExprKind::Continue(..)
578        | ast::ExprKind::Dummy
579        | ast::ExprKind::Err(_)
580        | ast::ExprKind::Field(..)
581        | ast::ExprKind::IncludedBytes(..)
582        | ast::ExprKind::InlineAsm(..)
583        | ast::ExprKind::Move(..)
584        | ast::ExprKind::OffsetOf(..)
585        | ast::ExprKind::UnsafeBinderCast(..)
586        | ast::ExprKind::Let(..)
587        | ast::ExprKind::Path(..)
588        | ast::ExprKind::Range(..)
589        | ast::ExprKind::Repeat(..)
590        | ast::ExprKind::Ret(..)
591        | ast::ExprKind::Become(..)
592        | ast::ExprKind::Yeet(..)
593        | ast::ExprKind::Tup(..)
594        | ast::ExprKind::Use(..)
595        | ast::ExprKind::Type(..)
596        | ast::ExprKind::Yield(..)
597        | ast::ExprKind::Underscore => false,
598    }
599}
600
601/// Removes trailing spaces from the specified snippet. We do not remove spaces
602/// inside strings or comments.
603pub(crate) fn remove_trailing_white_spaces(text: &str) -> String {
604    let mut buffer = String::with_capacity(text.len());
605    let mut space_buffer = String::with_capacity(128);
606    for (char_kind, c) in CharClasses::new(text.chars()) {
607        match c {
608            '\n' => {
609                if char_kind == FullCodeCharKind::InString {
610                    buffer.push_str(&space_buffer);
611                }
612                space_buffer.clear();
613                buffer.push('\n');
614            }
615            _ if c.is_whitespace() => {
616                space_buffer.push(c);
617            }
618            _ => {
619                if !space_buffer.is_empty() {
620                    buffer.push_str(&space_buffer);
621                    space_buffer.clear();
622                }
623                buffer.push(c);
624            }
625        }
626    }
627    buffer
628}
629
630/// Indent each line according to the specified `indent`.
631/// e.g.
632///
633/// ```rust,compile_fail
634/// foo!{
635/// x,
636/// y,
637/// foo(
638///     a,
639///     b,
640///     c,
641/// ),
642/// }
643/// ```
644///
645/// will become
646///
647/// ```rust,compile_fail
648/// foo!{
649///     x,
650///     y,
651///     foo(
652///         a,
653///         b,
654///         c,
655///     ),
656/// }
657/// ```
658pub(crate) fn trim_left_preserve_layout(
659    orig: &str,
660    indent: Indent,
661    config: &Config,
662) -> Option<String> {
663    let mut lines = LineClasses::new(orig);
664    let first_line = lines.next().map(|(_, s)| s.trim_end().to_owned())?;
665    let mut trimmed_lines = Vec::with_capacity(16);
666
667    let mut veto_trim = false;
668    let min_prefix_space_width = lines
669        .filter_map(|(kind, line)| {
670            let mut trimmed = true;
671            let prefix_space_width = if is_empty_line(&line) {
672                None
673            } else {
674                let (prefix_width, _) = get_prefix_space_width_and_end(&line, config.tab_spaces());
675                Some(prefix_width)
676            };
677
678            // just InString{Commented} in order to allow the start of a string to be indented
679            let new_veto_trim_value = (kind == FullCodeCharKind::InString
680                || (config.style_edition() >= StyleEdition::Edition2024
681                    && kind == FullCodeCharKind::InStringCommented))
682                && !line.ends_with('\\');
683            let line = if veto_trim || new_veto_trim_value {
684                veto_trim = new_veto_trim_value;
685                trimmed = false;
686                line
687            } else {
688                line.trim().to_owned()
689            };
690            trimmed_lines.push((trimmed, line, prefix_space_width));
691
692            // Because there is a veto against trimming and indenting lines within a string,
693            // such lines should not be taken into account when computing the minimum.
694            match kind {
695                FullCodeCharKind::InStringCommented | FullCodeCharKind::EndStringCommented
696                    if config.style_edition() >= StyleEdition::Edition2024 =>
697                {
698                    None
699                }
700                FullCodeCharKind::InString | FullCodeCharKind::EndString => None,
701                _ => prefix_space_width,
702            }
703        })
704        .min()?;
705
706    Some(
707        first_line
708            + "\n"
709            + &trimmed_lines
710                .iter()
711                .map(
712                    |&(trimmed, ref line, prefix_space_width)| match prefix_space_width {
713                        _ if !trimmed => line.to_owned(),
714                        Some(original_indent_width) => {
715                            let new_indent_width = indent.width()
716                                + original_indent_width.saturating_sub(min_prefix_space_width);
717                            let new_indent = Indent::from_width(config, new_indent_width);
718                            format!("{}{}", new_indent.to_string(config), line)
719                        }
720                        None => String::new(),
721                    },
722                )
723                .collect::<Vec<_>>()
724                .join("\n"),
725    )
726}
727
728/// Based on the given line, determine if the next line can be indented or not.
729/// This allows to preserve the indentation of multi-line literals when
730/// re-inserted a code block that has been formatted separately from the rest
731/// of the code, such as code in macro defs or code blocks doc comments.
732pub(crate) fn indent_next_line(kind: FullCodeCharKind, line: &str, config: &Config) -> bool {
733    if kind.is_string() {
734        // If the string ends with '\', the string has been wrapped over
735        // multiple lines. If `format_strings = true`, then the indentation of
736        // strings wrapped over multiple lines will have been adjusted while
737        // formatting the code block, therefore the string's indentation needs
738        // to be adjusted for the code surrounding the code block.
739        config.format_strings() && line.ends_with('\\')
740    } else if config.style_edition() >= StyleEdition::Edition2024 {
741        !kind.is_commented_string()
742    } else {
743        true
744    }
745}
746
747pub(crate) fn is_empty_line(s: &str) -> bool {
748    s.is_empty() || s.chars().all(char::is_whitespace)
749}
750
751fn get_prefix_space_width_and_end(s: &str, tab_spaces: usize) -> (usize, usize) {
752    let mut width = 0;
753
754    for (i, c) in s.char_indices() {
755        match c {
756            ' ' => width += 1,
757            '\t' => width += tab_spaces,
758            _ => return (width, i),
759        }
760    }
761    (width, s.len())
762}
763
764pub(crate) trait NodeIdExt {
765    fn root() -> Self;
766}
767
768impl NodeIdExt for NodeId {
769    fn root() -> NodeId {
770        NodeId::placeholder_from_expn_id(LocalExpnId::ROOT)
771    }
772}
773
774pub(crate) fn unicode_str_width(s: &str) -> usize {
775    s.width()
776}
777
778#[cfg(test)]
779mod test {
780    use super::*;
781
782    #[test]
783    fn test_remove_trailing_white_spaces() {
784        let s = "    r#\"\n        test\n    \"#";
785        assert_eq!(remove_trailing_white_spaces(s), s);
786    }
787
788    #[test]
789    fn test_trim_left_preserve_layout() {
790        let s = "aaa\n\tbbb\n    ccc";
791        let config = Config::default();
792        let indent = Indent::new(4, 0);
793        assert_eq!(
794            trim_left_preserve_layout(s, indent, &config),
795            Some("aaa\n    bbb\n    ccc".to_string())
796        );
797    }
798}