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