]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
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; | |
136023e0 | 9 | use rustc_span::{sym, symbol, BytePos, LocalExpnId, Span, Symbol, SyntaxContext}; |
f20569fa XL |
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] | |
18 | pub(crate) fn depr_skip_annotation() -> Symbol { | |
19 | Symbol::intern("rustfmt_skip") | |
20 | } | |
21 | ||
22 | #[inline] | |
23 | pub(crate) fn skip_annotation() -> Symbol { | |
24 | Symbol::intern("rustfmt::skip") | |
25 | } | |
26 | ||
27 | 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. | |
32 | 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 | ||
40 | 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, .. }, | |
3c0e092e | 45 | ) => pprust::path_to_string(p) == pprust::path_to_string(q), |
f20569fa XL |
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. | |
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] | |
88 | 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] | |
96 | 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] | |
104 | 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] | |
112 | 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] | |
120 | 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] | |
128 | 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] | |
136 | 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] | |
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>` | |
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] | |
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] | |
183 | pub(crate) fn inner_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> { | |
184 | filter_attributes(attrs, ast::AttrStyle::Inner) | |
185 | } | |
186 | ||
187 | #[inline] | |
188 | pub(crate) fn outer_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> { | |
189 | filter_attributes(attrs, ast::AttrStyle::Outer) | |
190 | } | |
191 | ||
192 | #[inline] | |
193 | pub(crate) fn is_single_line(s: &str) -> bool { | |
94222f64 | 194 | !s.chars().any(|c| c == '\n') |
f20569fa XL |
195 | } |
196 | ||
197 | #[inline] | |
198 | 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] | |
203 | 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] | |
208 | 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] | |
214 | 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] | |
220 | 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] | |
226 | 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] | |
235 | 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] | |
243 | 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] | |
259 | 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); | |
94222f64 | 263 | path_str == *skip_annotation().as_str() || path_str == *depr_skip_annotation().as_str() |
f20569fa XL |
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] | |
273 | 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] | |
281 | 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] | |
288 | 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] | |
305 | 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] | |
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. | |
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 | ||
345 | 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 | ||
358 | pub(crate) fn mk_sp(lo: BytePos, hi: BytePos) -> Span { | |
c295e0f8 | 359 | Span::new(lo, hi, SyntaxContext::root(), None) |
f20569fa XL |
360 | } |
361 | ||
cdc7bbd5 | 362 | pub(crate) fn mk_sp_lo_plus_one(lo: BytePos) -> Span { |
c295e0f8 | 363 | Span::new(lo, lo + BytePos(1), SyntaxContext::root(), None) |
cdc7bbd5 XL |
364 | } |
365 | ||
f20569fa XL |
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. | |
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 | ||
404 | 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] | |
432 | 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] | |
444 | 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] | |
461 | pub(crate) fn starts_with_newline(s: &str) -> bool { | |
462 | s.starts_with('\n') || s.starts_with("\r\n") | |
463 | } | |
464 | ||
465 | #[inline] | |
466 | 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. | |
472 | 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. | |
525 | 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 | /// ``` | |
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. | |
651 | 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 | ||
655 | pub(crate) fn is_empty_line(s: &str) -> bool { | |
656 | s.is_empty() || s.chars().all(char::is_whitespace) | |
657 | } | |
658 | ||
659 | 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 { | |
672 | fn root() -> Self; | |
673 | } | |
674 | ||
675 | impl NodeIdExt for NodeId { | |
676 | fn root() -> NodeId { | |
136023e0 | 677 | NodeId::placeholder_from_expn_id(LocalExpnId::ROOT) |
f20569fa XL |
678 | } |
679 | } | |
680 | ||
681 | 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] | |
690 | fn test_remove_trailing_white_spaces() { | |
691 | let s = " r#\"\n test\n \"#"; | |
3c0e092e | 692 | assert_eq!(remove_trailing_white_spaces(s), s); |
f20569fa XL |
693 | } |
694 | ||
695 | #[test] | |
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!( | |
3c0e092e | 701 | trim_left_preserve_layout(s, indent, &config), |
f20569fa XL |
702 | Some("aaa\n bbb\n ccc".to_string()) |
703 | ); | |
704 | } | |
705 | } |