1 //! Utils for extracting, inspecting or transforming source code
3 #![allow(clippy::module_name_repetitions)]
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};
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>(
18 option: Option<String>,
20 indent_relative_to: Option<Span>,
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))
31 Cow::Owned(format!("{{\n{};\n{}\n}}", code, string))
35 /// Returns a new Span that extends the original Span to the first non-whitespace char of the first
41 /// // will be converted to
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))
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))
57 /// Returns the indentation of the line of a span
61 /// // ^^ -- will return 0
63 /// // ^^ -- will return 4
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()))
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();
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() {
92 /// Returns the positon just before rarrow
95 /// fn into(self) -> () {}
97 /// // in case of unformatted code
98 /// fn into2(self)-> () {}
100 /// fn into3(self) -> () {}
103 pub fn position_before_rarrow(s: &str) -> Option<usize> {
104 s.rfind("->").map(|rpos| {
106 let chars: Vec<char> = s.chars().collect();
108 if let Some(c) = chars.get(rpos - 1) {
109 if c.is_whitespace() {
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()
128 fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
131 .skip(ignore_first as usize)
136 // ignore empty lines
137 Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0)
142 let indent = indent.unwrap_or(0);
146 if (ignore_first && i == 0) || l.is_empty() {
148 } else if x > indent {
149 l.split_at(x - indent).1.to_owned()
151 " ".repeat(indent - x) + l
154 .collect::<Vec<String>>()
158 /// Converts a span to a code snippet if available, otherwise use default.
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`.
165 /// snippet(cx, expr.span, "..")
167 pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
168 snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from)
171 /// Same as `snippet`, but it adapts the applicability level by following rules:
173 /// - Applicability level `Unspecified` will never be changed.
174 /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
175 /// - If the default value is used and the applicability level is `MachineApplicable`, change it to
176 /// `HasPlaceholders`
177 pub fn snippet_with_applicability<'a, T: LintContext>(
181 applicability: &mut Applicability,
183 if *applicability != Applicability::Unspecified && span.from_expansion() {
184 *applicability = Applicability::MaybeIncorrect;
186 snippet_opt(cx, span).map_or_else(
188 if *applicability == Applicability::MachineApplicable {
189 *applicability = Applicability::HasPlaceholders;
191 Cow::Borrowed(default)
197 /// Same as `snippet`, but should only be used when it's clear that the input span is
198 /// not a macro argument.
199 pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
200 snippet(cx, span.source_callsite(), default)
203 /// Converts a span to a code snippet. Returns `None` if not available.
204 pub fn snippet_opt<T: LintContext>(cx: &T, span: Span) -> Option<String> {
205 cx.sess().source_map().span_to_snippet(span).ok()
208 /// Converts a span (from a block) to a code snippet if available, otherwise use default.
210 /// This trims the code of indentation, except for the first line. Use it for blocks or block-like
211 /// things which need to be printed as such.
213 /// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the
214 /// resulting snippet of the given span.
219 /// snippet_block(cx, block.span, "..", None)
220 /// // where, `block` is the block of the if expr
224 /// // will return the snippet
231 /// snippet_block(cx, block.span, "..", Some(if_expr.span))
232 /// // where, `block` is the block of the if expr
236 /// // will return the snippet
239 /// } // aligned with `if`
241 /// Note that the first line of the snippet always has 0 indentation.
242 pub fn snippet_block<'a, T: LintContext>(
246 indent_relative_to: Option<Span>,
248 let snip = snippet(cx, span, default);
249 let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
250 reindent_multiline(snip, true, indent)
253 /// Same as `snippet_block`, but adapts the applicability level by the rules of
254 /// `snippet_with_applicability`.
255 pub fn snippet_block_with_applicability<'a, T: LintContext>(
259 indent_relative_to: Option<Span>,
260 applicability: &mut Applicability,
262 let snip = snippet_with_applicability(cx, span, default, applicability);
263 let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
264 reindent_multiline(snip, true, indent)
267 /// Same as `snippet_with_applicability`, but first walks the span up to the given context. This
268 /// will result in the macro call, rather then the expansion, if the span is from a child context.
269 /// If the span is not from a child context, it will be used directly instead.
271 /// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node
272 /// would result in `box []`. If given the context of the address of expression, this function will
273 /// correctly get a snippet of `vec![]`.
275 /// This will also return whether or not the snippet is a macro call.
276 pub fn snippet_with_context(
277 cx: &LateContext<'_>,
279 outer: SyntaxContext,
281 applicability: &mut Applicability,
282 ) -> (Cow<'a, str>, bool) {
283 let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else(
285 // The span is from a macro argument, and the outer context is the macro using the argument
286 if *applicability != Applicability::Unspecified {
287 *applicability = Applicability::MaybeIncorrect;
289 // TODO: get the argument span.
292 |outer_span| (outer_span, span.ctxt() != outer),
296 snippet_with_applicability(cx, span, default, applicability),
301 /// Walks the span up to the target context, thereby returning the macro call site if the span is
302 /// inside a macro expansion, or the original span if it is not. Note this will return `None` in the
303 /// case of the span being in a macro expansion, but the target context is from expanding a macro
306 /// Given the following
309 /// macro_rules! m { ($e:expr) => { f($e) }; }
313 /// If called with a span of the call to `f` and a context of the call to `g` this will return a
314 /// span containing `m!(0)`. However, if called with a span of the literal `0` this will give a span
315 /// containing `0` as the context is the same as the outer context.
317 /// This will traverse through multiple macro calls. Given the following:
320 /// macro_rules! m { ($e:expr) => { n!($e, 0) }; }
321 /// macro_rules! n { ($e:expr, $f:expr) => { f($e, $f) }; }
325 /// If called with a span of the call to `f` and a context of the call to `g` this will return a
326 /// span containing `m!(0)`.
327 pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option<Span> {
328 let outer_span = hygiene::walk_chain(span, outer);
329 (outer_span.ctxt() == outer).then(|| outer_span)
332 /// Removes block comments from the given `Vec` of lines.
337 /// without_block_comments(vec!["/*", "foo", "*/"]);
340 /// without_block_comments(vec!["bar", "/*", "foo", "*/"]);
341 /// // => vec!["bar"]
343 pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> {
344 let mut without = vec![];
346 let mut nest_level = 0;
349 if line.contains("/*") {
352 } else if line.contains("*/") {
367 use super::{reindent_multiline, without_block_comments};
370 fn test_reindent_multiline_single_line() {
371 assert_eq!("", reindent_multiline("".into(), false, None));
372 assert_eq!("...", reindent_multiline("...".into(), false, None));
373 assert_eq!("...", reindent_multiline(" ...".into(), false, None));
374 assert_eq!("...", reindent_multiline("\t...".into(), false, None));
375 assert_eq!("...", reindent_multiline("\t\t...".into(), false, None));
380 fn test_reindent_multiline_block() {
386 }", reindent_multiline(" if x {
390 }".into(), false, None));
396 }", reindent_multiline(" if x {
400 }".into(), false, None));
405 fn test_reindent_multiline_empty_line() {
412 }", reindent_multiline(" if x {
417 }".into(), false, None));
422 fn test_reindent_multiline_lines_deeper() {
428 }", reindent_multiline("\
433 }".into(), true, Some(8)));
437 fn test_without_block_comments_lines_without_block_comments() {
438 let result = without_block_comments(vec!["/*", "", "*/"]);
439 println!("result: {:?}", result);
440 assert!(result.is_empty());
442 let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]);
443 assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]);
445 let result = without_block_comments(vec!["/* rust", "", "*/"]);
446 assert!(result.is_empty());
448 let result = without_block_comments(vec!["/* one-line comment */"]);
449 assert!(result.is_empty());
451 let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]);
452 assert!(result.is_empty());
454 let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]);
455 assert!(result.is_empty());
457 let result = without_block_comments(vec!["foo", "bar", "baz"]);
458 assert_eq!(result, vec!["foo", "bar", "baz"]);