X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=clippy_utils%2Fsrc%2Fsource.rs;h=f88a92fb11c111146851e4213a6508c0ec26b2ac;hb=490c773e66d28a3b07a87af8d27d844ed6ed6efc;hp=2d794d48dc5ff63dba4f0a5ba7901f7e5f7b06de;hpb=9f6b5de7deaf4dc9e7917370ad09ab85dc23997c;p=rust.git diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 2d794d48dc5..f88a92fb11c 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -7,9 +7,28 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LintContext}; use rustc_span::hygiene; -use rustc_span::{BytePos, Pos, Span, SyntaxContext}; +use rustc_span::source_map::SourceMap; +use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext}; use std::borrow::Cow; +/// Checks if the span starts with the given text. This will return false if the span crosses +/// multiple files or if source is not available. +/// +/// This is used to check for proc macros giving unhelpful spans to things. +pub fn span_starts_with(cx: &T, span: Span, text: &str) -> bool { + fn helper(sm: &SourceMap, span: Span, text: &str) -> bool { + let pos = sm.lookup_byte_offset(span.lo()); + let Some(ref src) = pos.sf.src else { + return false; + }; + let end = span.hi() - pos.sf.start_pos; + src.get(pos.pos.0 as usize..end.0 as usize) + // Expression spans can include wrapping parenthesis. Remove them first. + .map_or(false, |s| s.trim_start_matches('(').starts_with(text)) + } + helper(cx.sess().source_map(), span, text) +} + /// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`. /// Also takes an `Option` which can be put inside the braces. pub fn expr_block<'a, T: LintContext>( @@ -66,6 +85,15 @@ pub fn indent_of(cx: &T, span: Span) -> Option { snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace())) } +/// Gets a snippet of the indentation of the line of a span +pub fn snippet_indent(cx: &T, span: Span) -> Option { + snippet_opt(cx, line_span(cx, span)).map(|mut s| { + let len = s.len() - s.trim_start().len(); + s.truncate(len); + s + }) +} + // If the snippet is empty, it's an attribute that was inserted during macro // expansion and we want to ignore those, because they could come from external // sources that the user has no control over. @@ -80,7 +108,7 @@ pub fn is_present_in_source(cx: &T, span: Span) -> bool { true } -/// Returns the positon just before rarrow +/// Returns the position just before rarrow /// /// ```rust,ignore /// fn into(self) -> () {} @@ -109,7 +137,7 @@ pub fn position_before_rarrow(s: &str) -> Option { } /// Reindent a multiline string with possibility of ignoring the first line. -#[allow(clippy::needless_pass_by_value)] +#[expect(clippy::needless_pass_by_value)] pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option) -> Cow<'_, str> { let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' '); let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t'); @@ -119,7 +147,7 @@ pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option, ch: char) -> String { let x = s .lines() - .skip(ignore_first as usize) + .skip(usize::from(ignore_first)) .filter_map(|l| { if l.is_empty() { None @@ -146,20 +174,28 @@ fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option, .join("\n") } -/// Converts a span to a code snippet if available, otherwise use default. +/// Converts a span to a code snippet if available, otherwise returns the default. /// /// This is useful if you want to provide suggestions for your lint or more generally, if you want -/// to convert a given `Span` to a `str`. +/// to convert a given `Span` to a `str`. To create suggestions consider using +/// [`snippet_with_applicability`] to ensure that the applicability stays correct. /// /// # Example /// ```rust,ignore -/// snippet(cx, expr.span, "..") +/// // Given two spans one for `value` and one for the `init` expression. +/// let value = Vec::new(); +/// // ^^^^^ ^^^^^^^^^^ +/// // span1 span2 +/// +/// // The snipped call would return the corresponding code snippet +/// snippet(cx, span1, "..") // -> "value" +/// snippet(cx, span2, "..") // -> "Vec::new()" /// ``` pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from) } -/// Same as `snippet`, but it adapts the applicability level by following rules: +/// Same as [`snippet`], but it adapts the applicability level by following rules: /// /// - Applicability level `Unspecified` will never be changed. /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`. @@ -264,24 +300,24 @@ pub fn snippet_block_with_applicability<'a, T: LintContext>( /// correctly get a snippet of `vec![]`. /// /// This will also return whether or not the snippet is a macro call. -pub fn snippet_with_context( +pub fn snippet_with_context<'a>( cx: &LateContext<'_>, span: Span, outer: SyntaxContext, default: &'a str, applicability: &mut Applicability, ) -> (Cow<'a, str>, bool) { - let outer_span = hygiene::walk_chain(span, outer); - let (span, is_macro_call) = if outer_span.ctxt() == outer { - (outer_span, span.ctxt() != outer) - } else { - // The span is from a macro argument, and the outer context is the macro using the argument - if *applicability != Applicability::Unspecified { - *applicability = Applicability::MaybeIncorrect; - } - // TODO: get the argument span. - (span, false) - }; + let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else( + || { + // The span is from a macro argument, and the outer context is the macro using the argument + if *applicability != Applicability::Unspecified { + *applicability = Applicability::MaybeIncorrect; + } + // TODO: get the argument span. + (span, false) + }, + |outer_span| (outer_span, span.ctxt() != outer), + ); ( snippet_with_applicability(cx, span, default, applicability), @@ -289,6 +325,37 @@ pub fn snippet_with_context( ) } +/// Walks the span up to the target context, thereby returning the macro call site if the span is +/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the +/// case of the span being in a macro expansion, but the target context is from expanding a macro +/// argument. +/// +/// Given the following +/// +/// ```rust,ignore +/// macro_rules! m { ($e:expr) => { f($e) }; } +/// g(m!(0)) +/// ``` +/// +/// If called with a span of the call to `f` and a context of the call to `g` this will return a +/// span containing `m!(0)`. However, if called with a span of the literal `0` this will give a span +/// containing `0` as the context is the same as the outer context. +/// +/// This will traverse through multiple macro calls. Given the following: +/// +/// ```rust,ignore +/// macro_rules! m { ($e:expr) => { n!($e, 0) }; } +/// macro_rules! n { ($e:expr, $f:expr) => { f($e, $f) }; } +/// g(m!(0)) +/// ``` +/// +/// If called with a span of the call to `f` and a context of the call to `g` this will return a +/// span containing `m!(0)`. +pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option { + let outer_span = hygiene::walk_chain(span, outer); + (outer_span.ctxt() == outer).then(|| outer_span) +} + /// Removes block comments from the given `Vec` of lines. /// /// # Examples @@ -322,6 +389,27 @@ pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> { without } +/// Trims the whitespace from the start and the end of the span. +pub fn trim_span(sm: &SourceMap, span: Span) -> Span { + let data = span.data(); + let sf: &_ = &sm.lookup_source_file(data.lo); + let Some(src) = sf.src.as_deref() else { + return span; + }; + let Some(snip) = &src.get((data.lo - sf.start_pos).to_usize()..(data.hi - sf.start_pos).to_usize()) else { + return span; + }; + let trim_start = snip.len() - snip.trim_start().len(); + let trim_end = snip.len() - snip.trim_end().len(); + SpanData { + lo: data.lo + BytePos::from_usize(trim_start), + hi: data.hi - BytePos::from_usize(trim_end), + ctxt: data.ctxt, + parent: data.parent, + } + .span() +} + #[cfg(test)] mod test { use super::{reindent_multiline, without_block_comments};