]> git.lizzy.rs Git - rust.git/blob - src/utils.rs
Merge pull request #2874 from cavedweller/master
[rust.git] / src / utils.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use std::borrow::Cow;
12
13 use rustc_target::spec::abi;
14 use syntax::ast::{
15     self, Attribute, CrateSugar, MetaItem, MetaItemKind, NestedMetaItem, NestedMetaItemKind, Path,
16     Visibility, VisibilityKind,
17 };
18 use syntax::codemap::{BytePos, Span, NO_EXPANSION};
19 use syntax::ptr;
20
21 use rewrite::RewriteContext;
22 use shape::Shape;
23
24 pub const DEPR_SKIP_ANNOTATION: &str = "rustfmt_skip";
25 pub const SKIP_ANNOTATION: &str = "rustfmt::skip";
26
27 pub fn rewrite_ident<'a>(context: &'a RewriteContext, ident: ast::Ident) -> &'a str {
28     context.snippet(ident.span)
29 }
30
31 // Computes the length of a string's last line, minus offset.
32 pub 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 fn is_same_visibility(a: &Visibility, b: &Visibility) -> bool {
41     match (&a.node, &b.node) {
42         (
43             VisibilityKind::Restricted { path: p, .. },
44             VisibilityKind::Restricted { path: q, .. },
45         ) => format!("{}", p) == format!("{}", 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.
61 pub fn format_visibility(context: &RewriteContext, vis: &Visibility) -> Cow<'static, str> {
62     match vis.node {
63         VisibilityKind::Public => Cow::from("pub "),
64         VisibilityKind::Inherited => Cow::from(""),
65         VisibilityKind::Crate(CrateSugar::PubCrate) => Cow::from("pub(crate) "),
66         VisibilityKind::Crate(CrateSugar::JustCrate) => Cow::from("crate "),
67         VisibilityKind::Restricted { ref path, .. } => {
68             let Path { ref segments, .. } = **path;
69             let mut segments_iter = segments.iter().map(|seg| rewrite_ident(context, seg.ident));
70             if path.is_global() {
71                 segments_iter
72                     .next()
73                     .expect("Non-global path in pub(restricted)?");
74             }
75             let is_keyword = |s: &str| s == "self" || s == "super";
76             let path = segments_iter.collect::<Vec<_>>().join("::");
77             let in_str = if is_keyword(&path) { "" } else { "in " };
78
79             Cow::from(format!("pub({}{}) ", in_str, path))
80         }
81     }
82 }
83
84 #[inline]
85 pub fn format_async(is_async: ast::IsAsync) -> &'static str {
86     match is_async {
87         ast::IsAsync::Async { .. } => "async ",
88         ast::IsAsync::NotAsync => "",
89     }
90 }
91
92 #[inline]
93 pub fn format_constness(constness: ast::Constness) -> &'static str {
94     match constness {
95         ast::Constness::Const => "const ",
96         ast::Constness::NotConst => "",
97     }
98 }
99
100 #[inline]
101 pub fn format_defaultness(defaultness: ast::Defaultness) -> &'static str {
102     match defaultness {
103         ast::Defaultness::Default => "default ",
104         ast::Defaultness::Final => "",
105     }
106 }
107
108 #[inline]
109 pub fn format_unsafety(unsafety: ast::Unsafety) -> &'static str {
110     match unsafety {
111         ast::Unsafety::Unsafe => "unsafe ",
112         ast::Unsafety::Normal => "",
113     }
114 }
115
116 #[inline]
117 pub fn format_auto(is_auto: ast::IsAuto) -> &'static str {
118     match is_auto {
119         ast::IsAuto::Yes => "auto ",
120         ast::IsAuto::No => "",
121     }
122 }
123
124 #[inline]
125 pub fn format_mutability(mutability: ast::Mutability) -> &'static str {
126     match mutability {
127         ast::Mutability::Mutable => "mut ",
128         ast::Mutability::Immutable => "",
129     }
130 }
131
132 #[inline]
133 pub fn format_abi(abi: abi::Abi, explicit_abi: bool, is_mod: bool) -> Cow<'static, str> {
134     if abi == abi::Abi::Rust && !is_mod {
135         Cow::from("")
136     } else if abi == abi::Abi::C && !explicit_abi {
137         Cow::from("extern ")
138     } else {
139         Cow::from(format!("extern {} ", abi))
140     }
141 }
142
143 #[inline]
144 // Transform `Vec<syntax::ptr::P<T>>` into `Vec<&T>`
145 pub fn ptr_vec_to_ref_vec<T>(vec: &[ptr::P<T>]) -> Vec<&T> {
146     vec.iter().map(|x| &**x).collect::<Vec<_>>()
147 }
148
149 #[inline]
150 pub fn filter_attributes(attrs: &[ast::Attribute], style: ast::AttrStyle) -> Vec<ast::Attribute> {
151     attrs
152         .iter()
153         .filter(|a| a.style == style)
154         .cloned()
155         .collect::<Vec<_>>()
156 }
157
158 #[inline]
159 pub fn inner_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> {
160     filter_attributes(attrs, ast::AttrStyle::Inner)
161 }
162
163 #[inline]
164 pub fn outer_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> {
165     filter_attributes(attrs, ast::AttrStyle::Outer)
166 }
167
168 #[inline]
169 pub fn is_single_line(s: &str) -> bool {
170     s.chars().find(|&c| c == '\n').is_none()
171 }
172
173 #[inline]
174 pub fn first_line_contains_single_line_comment(s: &str) -> bool {
175     s.lines().next().map_or(false, |l| l.contains("//"))
176 }
177
178 #[inline]
179 pub fn last_line_contains_single_line_comment(s: &str) -> bool {
180     s.lines().last().map_or(false, |l| l.contains("//"))
181 }
182
183 #[inline]
184 pub fn is_attributes_extendable(attrs_str: &str) -> bool {
185     !attrs_str.contains('\n') && !last_line_contains_single_line_comment(attrs_str)
186 }
187
188 // The width of the first line in s.
189 #[inline]
190 pub fn first_line_width(s: &str) -> usize {
191     match s.find('\n') {
192         Some(n) => n,
193         None => s.len(),
194     }
195 }
196
197 // The width of the last line in s.
198 #[inline]
199 pub fn last_line_width(s: &str) -> usize {
200     match s.rfind('\n') {
201         Some(n) => s.len() - n - 1,
202         None => s.len(),
203     }
204 }
205
206 // The total used width of the last line.
207 #[inline]
208 pub fn last_line_used_width(s: &str, offset: usize) -> usize {
209     if s.contains('\n') {
210         last_line_width(s)
211     } else {
212         offset + s.len()
213     }
214 }
215
216 #[inline]
217 pub fn trimmed_last_line_width(s: &str) -> usize {
218     match s.rfind('\n') {
219         Some(n) => s[(n + 1)..].trim().len(),
220         None => s.trim().len(),
221     }
222 }
223
224 #[inline]
225 pub fn last_line_extendable(s: &str) -> bool {
226     if s.ends_with("\"#") {
227         return true;
228     }
229     for c in s.chars().rev() {
230         match c {
231             '(' | ')' | ']' | '}' | '?' | '>' => continue,
232             '\n' => break,
233             _ if c.is_whitespace() => continue,
234             _ => return false,
235         }
236     }
237     true
238 }
239
240 #[inline]
241 fn is_skip(meta_item: &MetaItem) -> bool {
242     match meta_item.node {
243         MetaItemKind::Word => {
244             let path_str = meta_item.ident.to_string();
245             path_str == SKIP_ANNOTATION || path_str == DEPR_SKIP_ANNOTATION
246         }
247         MetaItemKind::List(ref l) => {
248             meta_item.name() == "cfg_attr" && l.len() == 2 && is_skip_nested(&l[1])
249         }
250         _ => false,
251     }
252 }
253
254 #[inline]
255 fn is_skip_nested(meta_item: &NestedMetaItem) -> bool {
256     match meta_item.node {
257         NestedMetaItemKind::MetaItem(ref mi) => is_skip(mi),
258         NestedMetaItemKind::Literal(_) => false,
259     }
260 }
261
262 #[inline]
263 pub fn contains_skip(attrs: &[Attribute]) -> bool {
264     attrs
265         .iter()
266         .any(|a| a.meta().map_or(false, |a| is_skip(&a)))
267 }
268
269 #[inline]
270 pub fn semicolon_for_expr(context: &RewriteContext, expr: &ast::Expr) -> bool {
271     match expr.node {
272         ast::ExprKind::Ret(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Break(..) => {
273             context.config.trailing_semicolon()
274         }
275         _ => false,
276     }
277 }
278
279 #[inline]
280 pub fn semicolon_for_stmt(context: &RewriteContext, stmt: &ast::Stmt) -> bool {
281     match stmt.node {
282         ast::StmtKind::Semi(ref expr) => match expr.node {
283             ast::ExprKind::While(..)
284             | ast::ExprKind::WhileLet(..)
285             | ast::ExprKind::Loop(..)
286             | ast::ExprKind::ForLoop(..) => false,
287             ast::ExprKind::Break(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Ret(..) => {
288                 context.config.trailing_semicolon()
289             }
290             _ => true,
291         },
292         ast::StmtKind::Expr(..) => false,
293         _ => true,
294     }
295 }
296
297 #[inline]
298 pub fn stmt_expr(stmt: &ast::Stmt) -> Option<&ast::Expr> {
299     match stmt.node {
300         ast::StmtKind::Expr(ref expr) => Some(expr),
301         _ => None,
302     }
303 }
304
305 #[inline]
306 pub fn count_newlines(input: &str) -> usize {
307     // Using `as_bytes` to omit UTF-8 decoding
308     input.as_bytes().iter().filter(|&&c| c == b'\n').count()
309 }
310
311 // For format_missing and last_pos, need to use the source callsite (if applicable).
312 // Required as generated code spans aren't guaranteed to follow on from the last span.
313 macro_rules! source {
314     ($this:ident, $sp:expr) => {
315         $sp.source_callsite()
316     };
317 }
318
319 pub fn mk_sp(lo: BytePos, hi: BytePos) -> Span {
320     Span::new(lo, hi, NO_EXPANSION)
321 }
322
323 // Return true if the given span does not intersect with file lines.
324 macro_rules! out_of_file_lines_range {
325     ($self:ident, $span:expr) => {
326         !$self.config.file_lines().is_all() && !$self
327             .config
328             .file_lines()
329             .intersects(&$self.codemap.lookup_line_range($span))
330     };
331 }
332
333 macro_rules! skip_out_of_file_lines_range {
334     ($self:ident, $span:expr) => {
335         if out_of_file_lines_range!($self, $span) {
336             return None;
337         }
338     };
339 }
340
341 macro_rules! skip_out_of_file_lines_range_visitor {
342     ($self:ident, $span:expr) => {
343         if out_of_file_lines_range!($self, $span) {
344             $self.push_rewrite($span, None);
345             return;
346         }
347     };
348 }
349
350 // Wraps String in an Option. Returns Some when the string adheres to the
351 // Rewrite constraints defined for the Rewrite trait and None otherwise.
352 pub fn wrap_str(s: String, max_width: usize, shape: Shape) -> Option<String> {
353     if is_valid_str(&s, max_width, shape) {
354         Some(s)
355     } else {
356         None
357     }
358 }
359
360 fn is_valid_str(snippet: &str, max_width: usize, shape: Shape) -> bool {
361     if !snippet.is_empty() {
362         // First line must fits with `shape.width`.
363         if first_line_width(snippet) > shape.width {
364             return false;
365         }
366         // If the snippet does not include newline, we are done.
367         if first_line_width(snippet) == snippet.len() {
368             return true;
369         }
370         // The other lines must fit within the maximum width.
371         if snippet.lines().skip(1).any(|line| line.len() > max_width) {
372             return false;
373         }
374         // A special check for the last line, since the caller may
375         // place trailing characters on this line.
376         if last_line_width(snippet) > shape.used_width() + shape.width {
377             return false;
378         }
379     }
380     true
381 }
382
383 #[inline]
384 pub fn colon_spaces(before: bool, after: bool) -> &'static str {
385     match (before, after) {
386         (true, true) => " : ",
387         (true, false) => " :",
388         (false, true) => ": ",
389         (false, false) => ":",
390     }
391 }
392
393 #[inline]
394 pub fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr {
395     match e.node {
396         ast::ExprKind::Call(ref e, _)
397         | ast::ExprKind::Binary(_, ref e, _)
398         | ast::ExprKind::Cast(ref e, _)
399         | ast::ExprKind::Type(ref e, _)
400         | ast::ExprKind::Assign(ref e, _)
401         | ast::ExprKind::AssignOp(_, ref e, _)
402         | ast::ExprKind::Field(ref e, _)
403         | ast::ExprKind::Index(ref e, _)
404         | ast::ExprKind::Range(Some(ref e), _, _)
405         | ast::ExprKind::Try(ref e) => left_most_sub_expr(e),
406         _ => e,
407     }
408 }
409
410 #[inline]
411 pub fn starts_with_newline(s: &str) -> bool {
412     s.starts_with('\n') || s.starts_with("\r\n")
413 }
414
415 #[inline]
416 pub fn first_line_ends_with(s: &str, c: char) -> bool {
417     s.lines().next().map_or(false, |l| l.ends_with(c))
418 }