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