]> git.lizzy.rs Git - rust.git/blob - src/tools/rustfmt/src/utils.rs
Auto merge of #95770 - nrc:read-buf-builder, r=joshtriplett
[rust.git] / src / tools / rustfmt / src / utils.rs
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]
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, .. },
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.
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]
78 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]
86 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]
94 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]
102 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]
110 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]
118 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]
126 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]
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>`
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]
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]
173 pub(crate) fn inner_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> {
174     filter_attributes(attrs, ast::AttrStyle::Inner)
175 }
176
177 #[inline]
178 pub(crate) fn outer_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> {
179     filter_attributes(attrs, ast::AttrStyle::Outer)
180 }
181
182 #[inline]
183 pub(crate) fn is_single_line(s: &str) -> bool {
184     !s.chars().any(|c| c == '\n')
185 }
186
187 #[inline]
188 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]
193 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]
198 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]
204 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]
210 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]
216 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]
225 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]
233 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]
249 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]
263 fn is_skip_nested(meta_item: &NestedMetaItem) -> bool {
264     match meta_item {
265         NestedMetaItem::MetaItem(ref mi) => is_skip(mi),
266         NestedMetaItem::Literal(_) => false,
267     }
268 }
269
270 #[inline]
271 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]
278 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]
295 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]
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.
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
335 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
348 pub(crate) fn mk_sp(lo: BytePos, hi: BytePos) -> Span {
349     Span::new(lo, hi, SyntaxContext::root(), None)
350 }
351
352 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.
386 pub(crate) fn wrap_str(s: String, max_width: usize, shape: Shape) -> Option<String> {
387     if is_valid_str(&filter_normal_code(&s), max_width, shape) {
388         Some(s)
389     } else {
390         None
391     }
392 }
393
394 fn is_valid_str(snippet: &str, max_width: usize, shape: Shape) -> bool {
395     if !snippet.is_empty() {
396         // First line must fits with `shape.width`.
397         if first_line_width(snippet) > shape.width {
398             return false;
399         }
400         // If the snippet does not include newline, we are done.
401         if is_single_line(snippet) {
402             return true;
403         }
404         // The other lines must fit within the maximum width.
405         if snippet
406             .lines()
407             .skip(1)
408             .any(|line| unicode_str_width(line) > max_width)
409         {
410             return false;
411         }
412         // A special check for the last line, since the caller may
413         // place trailing characters on this line.
414         if last_line_width(snippet) > shape.used_width() + shape.width {
415             return false;
416         }
417     }
418     true
419 }
420
421 #[inline]
422 pub(crate) fn colon_spaces(config: &Config) -> &'static str {
423     let before = config.space_before_colon();
424     let after = config.space_after_colon();
425     match (before, after) {
426         (true, true) => " : ",
427         (true, false) => " :",
428         (false, true) => ": ",
429         (false, false) => ":",
430     }
431 }
432
433 #[inline]
434 pub(crate) fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr {
435     match e.kind {
436         ast::ExprKind::Call(ref e, _)
437         | ast::ExprKind::Binary(_, ref e, _)
438         | ast::ExprKind::Cast(ref e, _)
439         | ast::ExprKind::Type(ref e, _)
440         | ast::ExprKind::Assign(ref e, _, _)
441         | ast::ExprKind::AssignOp(_, ref e, _)
442         | ast::ExprKind::Field(ref e, _)
443         | ast::ExprKind::Index(ref e, _)
444         | ast::ExprKind::Range(Some(ref e), _, _)
445         | ast::ExprKind::Try(ref e) => left_most_sub_expr(e),
446         _ => e,
447     }
448 }
449
450 #[inline]
451 pub(crate) fn starts_with_newline(s: &str) -> bool {
452     s.starts_with('\n') || s.starts_with("\r\n")
453 }
454
455 #[inline]
456 pub(crate) fn first_line_ends_with(s: &str, c: char) -> bool {
457     s.lines().next().map_or(false, |l| l.ends_with(c))
458 }
459
460 // States whether an expression's last line exclusively consists of closing
461 // parens, braces, and brackets in its idiomatic formatting.
462 pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr: &str) -> bool {
463     match expr.kind {
464         ast::ExprKind::MacCall(..)
465         | ast::ExprKind::Call(..)
466         | ast::ExprKind::MethodCall(..)
467         | ast::ExprKind::Array(..)
468         | ast::ExprKind::Struct(..)
469         | ast::ExprKind::While(..)
470         | ast::ExprKind::If(..)
471         | ast::ExprKind::Block(..)
472         | ast::ExprKind::ConstBlock(..)
473         | ast::ExprKind::Async(..)
474         | ast::ExprKind::Loop(..)
475         | ast::ExprKind::ForLoop(..)
476         | ast::ExprKind::TryBlock(..)
477         | ast::ExprKind::Match(..) => repr.contains('\n'),
478         ast::ExprKind::Paren(ref expr)
479         | ast::ExprKind::Binary(_, _, ref expr)
480         | ast::ExprKind::Index(_, ref expr)
481         | ast::ExprKind::Unary(_, ref expr)
482         | ast::ExprKind::Closure(_, _, _, _, ref expr, _)
483         | ast::ExprKind::Try(ref expr)
484         | ast::ExprKind::Yield(Some(ref expr)) => is_block_expr(context, expr, repr),
485         // This can only be a string lit
486         ast::ExprKind::Lit(_) => {
487             repr.contains('\n') && trimmed_last_line_width(repr) <= context.config.tab_spaces()
488         }
489         ast::ExprKind::AddrOf(..)
490         | ast::ExprKind::Assign(..)
491         | ast::ExprKind::AssignOp(..)
492         | ast::ExprKind::Await(..)
493         | ast::ExprKind::Box(..)
494         | ast::ExprKind::Break(..)
495         | ast::ExprKind::Cast(..)
496         | ast::ExprKind::Continue(..)
497         | ast::ExprKind::Err
498         | ast::ExprKind::Field(..)
499         | ast::ExprKind::InlineAsm(..)
500         | ast::ExprKind::Let(..)
501         | ast::ExprKind::Path(..)
502         | ast::ExprKind::Range(..)
503         | ast::ExprKind::Repeat(..)
504         | ast::ExprKind::Ret(..)
505         | ast::ExprKind::Yeet(..)
506         | ast::ExprKind::Tup(..)
507         | ast::ExprKind::Type(..)
508         | ast::ExprKind::Yield(None)
509         | ast::ExprKind::Underscore => false,
510     }
511 }
512
513 /// Removes trailing spaces from the specified snippet. We do not remove spaces
514 /// inside strings or comments.
515 pub(crate) fn remove_trailing_white_spaces(text: &str) -> String {
516     let mut buffer = String::with_capacity(text.len());
517     let mut space_buffer = String::with_capacity(128);
518     for (char_kind, c) in CharClasses::new(text.chars()) {
519         match c {
520             '\n' => {
521                 if char_kind == FullCodeCharKind::InString {
522                     buffer.push_str(&space_buffer);
523                 }
524                 space_buffer.clear();
525                 buffer.push('\n');
526             }
527             _ if c.is_whitespace() => {
528                 space_buffer.push(c);
529             }
530             _ => {
531                 if !space_buffer.is_empty() {
532                     buffer.push_str(&space_buffer);
533                     space_buffer.clear();
534                 }
535                 buffer.push(c);
536             }
537         }
538     }
539     buffer
540 }
541
542 /// Indent each line according to the specified `indent`.
543 /// e.g.
544 ///
545 /// ```rust,compile_fail
546 /// foo!{
547 /// x,
548 /// y,
549 /// foo(
550 ///     a,
551 ///     b,
552 ///     c,
553 /// ),
554 /// }
555 /// ```
556 ///
557 /// will become
558 ///
559 /// ```rust,compile_fail
560 /// foo!{
561 ///     x,
562 ///     y,
563 ///     foo(
564 ///         a,
565 ///         b,
566 ///         c,
567 ///     ),
568 /// }
569 /// ```
570 pub(crate) fn trim_left_preserve_layout(
571     orig: &str,
572     indent: Indent,
573     config: &Config,
574 ) -> Option<String> {
575     let mut lines = LineClasses::new(orig);
576     let first_line = lines.next().map(|(_, s)| s.trim_end().to_owned())?;
577     let mut trimmed_lines = Vec::with_capacity(16);
578
579     let mut veto_trim = false;
580     let min_prefix_space_width = lines
581         .filter_map(|(kind, line)| {
582             let mut trimmed = true;
583             let prefix_space_width = if is_empty_line(&line) {
584                 None
585             } else {
586                 Some(get_prefix_space_width(config, &line))
587             };
588
589             // just InString{Commented} in order to allow the start of a string to be indented
590             let new_veto_trim_value = (kind == FullCodeCharKind::InString
591                 || (config.version() == Version::Two
592                     && kind == FullCodeCharKind::InStringCommented))
593                 && !line.ends_with('\\');
594             let line = if veto_trim || new_veto_trim_value {
595                 veto_trim = new_veto_trim_value;
596                 trimmed = false;
597                 line
598             } else {
599                 line.trim().to_owned()
600             };
601             trimmed_lines.push((trimmed, line, prefix_space_width));
602
603             // Because there is a veto against trimming and indenting lines within a string,
604             // such lines should not be taken into account when computing the minimum.
605             match kind {
606                 FullCodeCharKind::InStringCommented | FullCodeCharKind::EndStringCommented
607                     if config.version() == Version::Two =>
608                 {
609                     None
610                 }
611                 FullCodeCharKind::InString | FullCodeCharKind::EndString => None,
612                 _ => prefix_space_width,
613             }
614         })
615         .min()?;
616
617     Some(
618         first_line
619             + "\n"
620             + &trimmed_lines
621                 .iter()
622                 .map(
623                     |&(trimmed, ref line, prefix_space_width)| match prefix_space_width {
624                         _ if !trimmed => line.to_owned(),
625                         Some(original_indent_width) => {
626                             let new_indent_width = indent.width()
627                                 + original_indent_width.saturating_sub(min_prefix_space_width);
628                             let new_indent = Indent::from_width(config, new_indent_width);
629                             format!("{}{}", new_indent.to_string(config), line)
630                         }
631                         None => String::new(),
632                     },
633                 )
634                 .collect::<Vec<_>>()
635                 .join("\n"),
636     )
637 }
638
639 /// Based on the given line, determine if the next line can be indented or not.
640 /// This allows to preserve the indentation of multi-line literals when
641 /// re-inserted a code block that has been formatted separately from the rest
642 /// of the code, such as code in macro defs or code blocks doc comments.
643 pub(crate) fn indent_next_line(kind: FullCodeCharKind, line: &str, config: &Config) -> bool {
644     if kind.is_string() {
645         // If the string ends with '\', the string has been wrapped over
646         // multiple lines. If `format_strings = true`, then the indentation of
647         // strings wrapped over multiple lines will have been adjusted while
648         // formatting the code block, therefore the string's indentation needs
649         // to be adjusted for the code surrounding the code block.
650         config.format_strings() && line.ends_with('\\')
651     } else if config.version() == Version::Two {
652         !kind.is_commented_string()
653     } else {
654         true
655     }
656 }
657
658 pub(crate) fn is_empty_line(s: &str) -> bool {
659     s.is_empty() || s.chars().all(char::is_whitespace)
660 }
661
662 fn get_prefix_space_width(config: &Config, s: &str) -> usize {
663     let mut width = 0;
664     for c in s.chars() {
665         match c {
666             ' ' => width += 1,
667             '\t' => width += config.tab_spaces(),
668             _ => return width,
669         }
670     }
671     width
672 }
673
674 pub(crate) trait NodeIdExt {
675     fn root() -> Self;
676 }
677
678 impl NodeIdExt for NodeId {
679     fn root() -> NodeId {
680         NodeId::placeholder_from_expn_id(LocalExpnId::ROOT)
681     }
682 }
683
684 pub(crate) fn unicode_str_width(s: &str) -> usize {
685     s.width()
686 }
687
688 #[cfg(test)]
689 mod test {
690     use super::*;
691
692     #[test]
693     fn test_remove_trailing_white_spaces() {
694         let s = "    r#\"\n        test\n    \"#";
695         assert_eq!(remove_trailing_white_spaces(s), s);
696     }
697
698     #[test]
699     fn test_trim_left_preserve_layout() {
700         let s = "aaa\n\tbbb\n    ccc";
701         let config = Config::default();
702         let indent = Indent::new(4, 0);
703         assert_eq!(
704             trim_left_preserve_layout(s, indent, &config),
705             Some("aaa\n    bbb\n    ccc".to_string())
706         );
707     }
708 }