]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_utils/src/source.rs
turn `append_place_to_string` from recursion into iteration
[rust.git] / src / tools / clippy / clippy_utils / src / source.rs
1 //! Utils for extracting, inspecting or transforming source code
2
3 #![allow(clippy::module_name_repetitions)]
4
5 use crate::line_span;
6 use rustc_errors::Applicability;
7 use rustc_hir::{Expr, ExprKind};
8 use rustc_lint::{LateContext, LintContext};
9 use rustc_span::hygiene;
10 use rustc_span::{BytePos, Pos, Span, SyntaxContext};
11 use std::borrow::Cow;
12
13 /// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
14 /// Also takes an `Option<String>` which can be put inside the braces.
15 pub fn expr_block<'a, T: LintContext>(
16     cx: &T,
17     expr: &Expr<'_>,
18     option: Option<String>,
19     default: &'a str,
20     indent_relative_to: Option<Span>,
21 ) -> Cow<'a, str> {
22     let code = snippet_block(cx, expr.span, default, indent_relative_to);
23     let string = option.unwrap_or_default();
24     if expr.span.from_expansion() {
25         Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default)))
26     } else if let ExprKind::Block(_, _) = expr.kind {
27         Cow::Owned(format!("{}{}", code, string))
28     } else if string.is_empty() {
29         Cow::Owned(format!("{{ {} }}", code))
30     } else {
31         Cow::Owned(format!("{{\n{};\n{}\n}}", code, string))
32     }
33 }
34
35 /// Returns a new Span that extends the original Span to the first non-whitespace char of the first
36 /// line.
37 ///
38 /// ```rust,ignore
39 ///     let x = ();
40 /// //          ^^
41 /// // will be converted to
42 ///     let x = ();
43 /// //  ^^^^^^^^^^
44 /// ```
45 pub fn first_line_of_span<T: LintContext>(cx: &T, span: Span) -> Span {
46     first_char_in_first_line(cx, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos))
47 }
48
49 fn first_char_in_first_line<T: LintContext>(cx: &T, span: Span) -> Option<BytePos> {
50     let line_span = line_span(cx, span);
51     snippet_opt(cx, line_span).and_then(|snip| {
52         snip.find(|c: char| !c.is_whitespace())
53             .map(|pos| line_span.lo() + BytePos::from_usize(pos))
54     })
55 }
56
57 /// Returns the indentation of the line of a span
58 ///
59 /// ```rust,ignore
60 /// let x = ();
61 /// //      ^^ -- will return 0
62 ///     let x = ();
63 /// //          ^^ -- will return 4
64 /// ```
65 pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> {
66     snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
67 }
68
69 /// Gets a snippet of the indentation of the line of a span
70 pub fn snippet_indent<T: LintContext>(cx: &T, span: Span) -> Option<String> {
71     snippet_opt(cx, line_span(cx, span)).map(|mut s| {
72         let len = s.len() - s.trim_start().len();
73         s.truncate(len);
74         s
75     })
76 }
77
78 // If the snippet is empty, it's an attribute that was inserted during macro
79 // expansion and we want to ignore those, because they could come from external
80 // sources that the user has no control over.
81 // For some reason these attributes don't have any expansion info on them, so
82 // we have to check it this way until there is a better way.
83 pub fn is_present_in_source<T: LintContext>(cx: &T, span: Span) -> bool {
84     if let Some(snippet) = snippet_opt(cx, span) {
85         if snippet.is_empty() {
86             return false;
87         }
88     }
89     true
90 }
91
92 /// Returns the positon just before rarrow
93 ///
94 /// ```rust,ignore
95 /// fn into(self) -> () {}
96 ///              ^
97 /// // in case of unformatted code
98 /// fn into2(self)-> () {}
99 ///               ^
100 /// fn into3(self)   -> () {}
101 ///               ^
102 /// ```
103 pub fn position_before_rarrow(s: &str) -> Option<usize> {
104     s.rfind("->").map(|rpos| {
105         let mut rpos = rpos;
106         let chars: Vec<char> = s.chars().collect();
107         while rpos > 1 {
108             if let Some(c) = chars.get(rpos - 1) {
109                 if c.is_whitespace() {
110                     rpos -= 1;
111                     continue;
112                 }
113             }
114             break;
115         }
116         rpos
117     })
118 }
119
120 /// Reindent a multiline string with possibility of ignoring the first line.
121 #[allow(clippy::needless_pass_by_value)]
122 pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> {
123     let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' ');
124     let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t');
125     reindent_multiline_inner(&s_tab, ignore_first, indent, ' ').into()
126 }
127
128 fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
129     let x = s
130         .lines()
131         .skip(usize::from(ignore_first))
132         .filter_map(|l| {
133             if l.is_empty() {
134                 None
135             } else {
136                 // ignore empty lines
137                 Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0)
138             }
139         })
140         .min()
141         .unwrap_or(0);
142     let indent = indent.unwrap_or(0);
143     s.lines()
144         .enumerate()
145         .map(|(i, l)| {
146             if (ignore_first && i == 0) || l.is_empty() {
147                 l.to_owned()
148             } else if x > indent {
149                 l.split_at(x - indent).1.to_owned()
150             } else {
151                 " ".repeat(indent - x) + l
152             }
153         })
154         .collect::<Vec<String>>()
155         .join("\n")
156 }
157
158 /// Converts a span to a code snippet if available, otherwise returns the default.
159 ///
160 /// This is useful if you want to provide suggestions for your lint or more generally, if you want
161 /// to convert a given `Span` to a `str`. To create suggestions consider using
162 /// [`snippet_with_applicability`] to ensure that the applicability stays correct.
163 ///
164 /// # Example
165 /// ```rust,ignore
166 /// // Given two spans one for `value` and one for the `init` expression.
167 /// let value = Vec::new();
168 /// //  ^^^^^   ^^^^^^^^^^
169 /// //  span1   span2
170 ///
171 /// // The snipped call would return the corresponding code snippet
172 /// snippet(cx, span1, "..") // -> "value"
173 /// snippet(cx, span2, "..") // -> "Vec::new()"
174 /// ```
175 pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
176     snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from)
177 }
178
179 /// Same as [`snippet`], but it adapts the applicability level by following rules:
180 ///
181 /// - Applicability level `Unspecified` will never be changed.
182 /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
183 /// - If the default value is used and the applicability level is `MachineApplicable`, change it to
184 /// `HasPlaceholders`
185 pub fn snippet_with_applicability<'a, T: LintContext>(
186     cx: &T,
187     span: Span,
188     default: &'a str,
189     applicability: &mut Applicability,
190 ) -> Cow<'a, str> {
191     if *applicability != Applicability::Unspecified && span.from_expansion() {
192         *applicability = Applicability::MaybeIncorrect;
193     }
194     snippet_opt(cx, span).map_or_else(
195         || {
196             if *applicability == Applicability::MachineApplicable {
197                 *applicability = Applicability::HasPlaceholders;
198             }
199             Cow::Borrowed(default)
200         },
201         From::from,
202     )
203 }
204
205 /// Same as `snippet`, but should only be used when it's clear that the input span is
206 /// not a macro argument.
207 pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
208     snippet(cx, span.source_callsite(), default)
209 }
210
211 /// Converts a span to a code snippet. Returns `None` if not available.
212 pub fn snippet_opt<T: LintContext>(cx: &T, span: Span) -> Option<String> {
213     cx.sess().source_map().span_to_snippet(span).ok()
214 }
215
216 /// Converts a span (from a block) to a code snippet if available, otherwise use default.
217 ///
218 /// This trims the code of indentation, except for the first line. Use it for blocks or block-like
219 /// things which need to be printed as such.
220 ///
221 /// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the
222 /// resulting snippet of the given span.
223 ///
224 /// # Example
225 ///
226 /// ```rust,ignore
227 /// snippet_block(cx, block.span, "..", None)
228 /// // where, `block` is the block of the if expr
229 ///     if x {
230 ///         y;
231 ///     }
232 /// // will return the snippet
233 /// {
234 ///     y;
235 /// }
236 /// ```
237 ///
238 /// ```rust,ignore
239 /// snippet_block(cx, block.span, "..", Some(if_expr.span))
240 /// // where, `block` is the block of the if expr
241 ///     if x {
242 ///         y;
243 ///     }
244 /// // will return the snippet
245 /// {
246 ///         y;
247 ///     } // aligned with `if`
248 /// ```
249 /// Note that the first line of the snippet always has 0 indentation.
250 pub fn snippet_block<'a, T: LintContext>(
251     cx: &T,
252     span: Span,
253     default: &'a str,
254     indent_relative_to: Option<Span>,
255 ) -> Cow<'a, str> {
256     let snip = snippet(cx, span, default);
257     let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
258     reindent_multiline(snip, true, indent)
259 }
260
261 /// Same as `snippet_block`, but adapts the applicability level by the rules of
262 /// `snippet_with_applicability`.
263 pub fn snippet_block_with_applicability<'a, T: LintContext>(
264     cx: &T,
265     span: Span,
266     default: &'a str,
267     indent_relative_to: Option<Span>,
268     applicability: &mut Applicability,
269 ) -> Cow<'a, str> {
270     let snip = snippet_with_applicability(cx, span, default, applicability);
271     let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
272     reindent_multiline(snip, true, indent)
273 }
274
275 /// Same as `snippet_with_applicability`, but first walks the span up to the given context. This
276 /// will result in the macro call, rather then the expansion, if the span is from a child context.
277 /// If the span is not from a child context, it will be used directly instead.
278 ///
279 /// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node
280 /// would result in `box []`. If given the context of the address of expression, this function will
281 /// correctly get a snippet of `vec![]`.
282 ///
283 /// This will also return whether or not the snippet is a macro call.
284 pub fn snippet_with_context<'a>(
285     cx: &LateContext<'_>,
286     span: Span,
287     outer: SyntaxContext,
288     default: &'a str,
289     applicability: &mut Applicability,
290 ) -> (Cow<'a, str>, bool) {
291     let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else(
292         || {
293             // The span is from a macro argument, and the outer context is the macro using the argument
294             if *applicability != Applicability::Unspecified {
295                 *applicability = Applicability::MaybeIncorrect;
296             }
297             // TODO: get the argument span.
298             (span, false)
299         },
300         |outer_span| (outer_span, span.ctxt() != outer),
301     );
302
303     (
304         snippet_with_applicability(cx, span, default, applicability),
305         is_macro_call,
306     )
307 }
308
309 /// Walks the span up to the target context, thereby returning the macro call site if the span is
310 /// inside a macro expansion, or the original span if it is not. Note this will return `None` in the
311 /// case of the span being in a macro expansion, but the target context is from expanding a macro
312 /// argument.
313 ///
314 /// Given the following
315 ///
316 /// ```rust,ignore
317 /// macro_rules! m { ($e:expr) => { f($e) }; }
318 /// g(m!(0))
319 /// ```
320 ///
321 /// If called with a span of the call to `f` and a context of the call to `g` this will return a
322 /// span containing `m!(0)`. However, if called with a span of the literal `0` this will give a span
323 /// containing `0` as the context is the same as the outer context.
324 ///
325 /// This will traverse through multiple macro calls. Given the following:
326 ///
327 /// ```rust,ignore
328 /// macro_rules! m { ($e:expr) => { n!($e, 0) }; }
329 /// macro_rules! n { ($e:expr, $f:expr) => { f($e, $f) }; }
330 /// g(m!(0))
331 /// ```
332 ///
333 /// If called with a span of the call to `f` and a context of the call to `g` this will return a
334 /// span containing `m!(0)`.
335 pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option<Span> {
336     let outer_span = hygiene::walk_chain(span, outer);
337     (outer_span.ctxt() == outer).then(|| outer_span)
338 }
339
340 /// Removes block comments from the given `Vec` of lines.
341 ///
342 /// # Examples
343 ///
344 /// ```rust,ignore
345 /// without_block_comments(vec!["/*", "foo", "*/"]);
346 /// // => vec![]
347 ///
348 /// without_block_comments(vec!["bar", "/*", "foo", "*/"]);
349 /// // => vec!["bar"]
350 /// ```
351 pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> {
352     let mut without = vec![];
353
354     let mut nest_level = 0;
355
356     for line in lines {
357         if line.contains("/*") {
358             nest_level += 1;
359             continue;
360         } else if line.contains("*/") {
361             nest_level -= 1;
362             continue;
363         }
364
365         if nest_level == 0 {
366             without.push(line);
367         }
368     }
369
370     without
371 }
372
373 #[cfg(test)]
374 mod test {
375     use super::{reindent_multiline, without_block_comments};
376
377     #[test]
378     fn test_reindent_multiline_single_line() {
379         assert_eq!("", reindent_multiline("".into(), false, None));
380         assert_eq!("...", reindent_multiline("...".into(), false, None));
381         assert_eq!("...", reindent_multiline("    ...".into(), false, None));
382         assert_eq!("...", reindent_multiline("\t...".into(), false, None));
383         assert_eq!("...", reindent_multiline("\t\t...".into(), false, None));
384     }
385
386     #[test]
387     #[rustfmt::skip]
388     fn test_reindent_multiline_block() {
389         assert_eq!("\
390     if x {
391         y
392     } else {
393         z
394     }", reindent_multiline("    if x {
395             y
396         } else {
397             z
398         }".into(), false, None));
399         assert_eq!("\
400     if x {
401     \ty
402     } else {
403     \tz
404     }", reindent_multiline("    if x {
405         \ty
406         } else {
407         \tz
408         }".into(), false, None));
409     }
410
411     #[test]
412     #[rustfmt::skip]
413     fn test_reindent_multiline_empty_line() {
414         assert_eq!("\
415     if x {
416         y
417
418     } else {
419         z
420     }", reindent_multiline("    if x {
421             y
422
423         } else {
424             z
425         }".into(), false, None));
426     }
427
428     #[test]
429     #[rustfmt::skip]
430     fn test_reindent_multiline_lines_deeper() {
431         assert_eq!("\
432         if x {
433             y
434         } else {
435             z
436         }", reindent_multiline("\
437     if x {
438         y
439     } else {
440         z
441     }".into(), true, Some(8)));
442     }
443
444     #[test]
445     fn test_without_block_comments_lines_without_block_comments() {
446         let result = without_block_comments(vec!["/*", "", "*/"]);
447         println!("result: {:?}", result);
448         assert!(result.is_empty());
449
450         let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]);
451         assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]);
452
453         let result = without_block_comments(vec!["/* rust", "", "*/"]);
454         assert!(result.is_empty());
455
456         let result = without_block_comments(vec!["/* one-line comment */"]);
457         assert!(result.is_empty());
458
459         let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]);
460         assert!(result.is_empty());
461
462         let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]);
463         assert!(result.is_empty());
464
465         let result = without_block_comments(vec!["foo", "bar", "baz"]);
466         assert_eq!(result, vec!["foo", "bar", "baz"]);
467     }
468 }