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