Skip to main content

clippy_utils/
source.rs

1//! Utils for extracting, inspecting or transforming source code
2
3#![allow(clippy::module_name_repetitions)]
4
5use std::sync::Arc;
6
7use rustc_ast::{LitKind, StrStyle};
8use rustc_errors::Applicability;
9use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource};
10use rustc_lexer::{FrontmatterAllowed, LiteralKind, TokenKind, tokenize};
11use rustc_lint::{EarlyContext, LateContext};
12use rustc_middle::ty::TyCtxt;
13use rustc_session::Session;
14use rustc_span::source_map::{SourceMap, original_sp};
15use rustc_span::{
16    BytePos, DUMMY_SP, DesugaringKind, Pos, RelativeBytePos, SourceFile, SourceFileAndLine, Span, SpanData,
17    SyntaxContext, hygiene,
18};
19use std::borrow::Cow;
20use std::fmt;
21use std::ops::{Deref, Index, Range};
22
23pub trait HasSourceMap<'sm>: Copy {
24    #[must_use]
25    fn source_map(self) -> &'sm SourceMap;
26}
27impl<'sm> HasSourceMap<'sm> for &'sm SourceMap {
28    #[inline]
29    fn source_map(self) -> &'sm SourceMap {
30        self
31    }
32}
33impl<'sm> HasSourceMap<'sm> for &'sm Session {
34    #[inline]
35    fn source_map(self) -> &'sm SourceMap {
36        self.source_map()
37    }
38}
39impl<'sm> HasSourceMap<'sm> for TyCtxt<'sm> {
40    #[inline]
41    fn source_map(self) -> &'sm SourceMap {
42        self.sess.source_map()
43    }
44}
45impl<'sm> HasSourceMap<'sm> for &'sm EarlyContext<'_> {
46    #[inline]
47    fn source_map(self) -> &'sm SourceMap {
48        ::rustc_lint::LintContext::sess(self).source_map()
49    }
50}
51impl<'sm> HasSourceMap<'sm> for &LateContext<'sm> {
52    #[inline]
53    fn source_map(self) -> &'sm SourceMap {
54        self.tcx.sess.source_map()
55    }
56}
57
58/// Conversion of a value into the range portion of a `Span`.
59pub trait SpanRange: Sized {
60    fn into_range(self) -> Range<BytePos>;
61}
62impl SpanRange for Span {
63    fn into_range(self) -> Range<BytePos> {
64        let data = self.data();
65        data.lo..data.hi
66    }
67}
68impl SpanRange for SpanData {
69    fn into_range(self) -> Range<BytePos> {
70        self.lo..self.hi
71    }
72}
73impl SpanRange for Range<BytePos> {
74    fn into_range(self) -> Range<BytePos> {
75        self
76    }
77}
78
79/// Conversion of a value into a `Span`
80pub trait IntoSpan: Sized {
81    fn into_span(self) -> Span;
82    fn with_ctxt(self, ctxt: SyntaxContext) -> Span;
83}
84impl IntoSpan for Span {
85    fn into_span(self) -> Span {
86        self
87    }
88    fn with_ctxt(self, ctxt: SyntaxContext) -> Span {
89        self.with_ctxt(ctxt)
90    }
91}
92impl IntoSpan for SpanData {
93    fn into_span(self) -> Span {
94        self.span()
95    }
96    fn with_ctxt(self, ctxt: SyntaxContext) -> Span {
97        Span::new(self.lo, self.hi, ctxt, self.parent)
98    }
99}
100impl IntoSpan for Range<BytePos> {
101    fn into_span(self) -> Span {
102        Span::with_root_ctxt(self.start, self.end)
103    }
104    fn with_ctxt(self, ctxt: SyntaxContext) -> Span {
105        Span::new(self.start, self.end, ctxt, None)
106    }
107}
108
109pub trait SpanExt: SpanRange {
110    /// Attempts to get a handle to the source text. Returns `None` if either the span is malformed,
111    /// or the source text is not accessible.
112    fn get_text<'sm>(self, sm: impl HasSourceMap<'sm>) -> Option<SourceText> {
113        get_source_range(sm.source_map(), self.into_range()).and_then(SourceText::new)
114    }
115
116    /// Gets the source file, and range in the file, of the given span. Returns `None` if the span
117    /// extends through multiple files, or is malformed.
118    fn get_source_range<'sm>(self, sm: impl HasSourceMap<'sm>) -> Option<SourceFileRange> {
119        get_source_range(sm.source_map(), self.into_range())
120    }
121
122    /// Calls the given function with the source text referenced and returns the value. Returns
123    /// `None` if the source text cannot be retrieved.
124    fn with_source_text<'sm, T>(self, sm: impl HasSourceMap<'sm>, f: impl for<'a> FnOnce(&'a str) -> T) -> Option<T> {
125        with_source_text(sm.source_map(), self.into_range(), f)
126    }
127
128    /// Checks if the referenced source text satisfies the given predicate. Returns `false` if the
129    /// source text cannot be retrieved.
130    fn check_text<'sm>(self, sm: impl HasSourceMap<'sm>, pred: impl for<'a> FnOnce(&'a str) -> bool) -> bool {
131        self.with_source_text(sm, pred).unwrap_or(false)
132    }
133
134    /// Calls the given function with the both the text of the source file and the referenced range,
135    /// and returns the value. Returns `None` if the source text cannot be retrieved.
136    fn with_source_text_and_range<'sm, T>(
137        self,
138        sm: impl HasSourceMap<'sm>,
139        f: impl for<'a> FnOnce(&'a str, Range<usize>) -> T,
140    ) -> Option<T> {
141        with_source_text_and_range(sm.source_map(), self.into_range(), f)
142    }
143
144    /// Calls the given function with the both the text of the source file and the referenced range,
145    /// and creates a new span with the returned range. Returns `None` if the source text cannot be
146    /// retrieved, or no result is returned.
147    ///
148    /// The new range must reside within the same source file.
149    fn map_range<'sm>(
150        self,
151        sm: impl HasSourceMap<'sm>,
152        f: impl for<'a> FnOnce(&'a SourceFile, &'a str, Range<usize>) -> Option<Range<usize>>,
153    ) -> Option<Range<BytePos>> {
154        map_range(sm.source_map(), self.into_range(), f)
155    }
156
157    /// Extends the range to include all preceding whitespace characters.
158    ///
159    /// The range will not be expanded if it would cross a line boundary, the line the range would
160    /// be extended to ends with a line comment and the text after the range contains a
161    /// non-whitespace character on the same line. e.g.
162    ///
163    /// ```ignore
164    /// ( // Some comment
165    /// foo)
166    /// ```
167    ///
168    /// When the range points to `foo`, suggesting to remove the range after it's been extended will
169    /// cause the `)` to be placed inside the line comment as `( // Some comment)`.
170    fn with_leading_whitespace<'sm>(self, sm: impl HasSourceMap<'sm>) -> Range<BytePos> {
171        with_leading_whitespace(sm.source_map(), self.into_range())
172    }
173
174    /// Trims the leading whitespace from the range.
175    fn trim_start<'sm>(self, sm: impl HasSourceMap<'sm>) -> Range<BytePos> {
176        trim_start(sm.source_map(), self.into_range())
177    }
178}
179impl<T: SpanRange> SpanExt for T {}
180
181/// Handle to a range of text in a source file.
182pub struct SourceText(SourceFileRange);
183impl SourceText {
184    /// Takes ownership of the source file handle if the source text is accessible.
185    pub fn new(text: SourceFileRange) -> Option<Self> {
186        if text.as_str().is_some() {
187            Some(Self(text))
188        } else {
189            None
190        }
191    }
192
193    /// Gets the source text.
194    pub fn as_str(&self) -> &str {
195        self.0.as_str().unwrap()
196    }
197
198    /// Converts this into an owned string.
199    pub fn to_owned(&self) -> String {
200        self.as_str().to_owned()
201    }
202}
203impl Deref for SourceText {
204    type Target = str;
205    fn deref(&self) -> &Self::Target {
206        self.as_str()
207    }
208}
209impl AsRef<str> for SourceText {
210    fn as_ref(&self) -> &str {
211        self.as_str()
212    }
213}
214impl<T> Index<T> for SourceText
215where
216    str: Index<T>,
217{
218    type Output = <str as Index<T>>::Output;
219    fn index(&self, idx: T) -> &Self::Output {
220        &self.as_str()[idx]
221    }
222}
223impl fmt::Display for SourceText {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        self.as_str().fmt(f)
226    }
227}
228impl fmt::Debug for SourceText {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        self.as_str().fmt(f)
231    }
232}
233
234fn get_source_range(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange> {
235    let start = sm.lookup_byte_offset(sp.start);
236    let end = sm.lookup_byte_offset(sp.end);
237    if !Arc::ptr_eq(&start.sf, &end.sf) || start.pos > end.pos {
238        return None;
239    }
240    sm.ensure_source_file_source_present(&start.sf);
241    let range = start.pos.to_usize()..end.pos.to_usize();
242    Some(SourceFileRange { sf: start.sf, range })
243}
244
245fn with_source_text<T>(sm: &SourceMap, sp: Range<BytePos>, f: impl for<'a> FnOnce(&'a str) -> T) -> Option<T> {
246    if let Some(src) = get_source_range(sm, sp)
247        && let Some(src) = src.as_str()
248    {
249        Some(f(src))
250    } else {
251        None
252    }
253}
254
255fn with_source_text_and_range<T>(
256    sm: &SourceMap,
257    sp: Range<BytePos>,
258    f: impl for<'a> FnOnce(&'a str, Range<usize>) -> T,
259) -> Option<T> {
260    if let Some(src) = get_source_range(sm, sp)
261        && let Some(text) = &src.sf.src
262    {
263        Some(f(text, src.range))
264    } else {
265        None
266    }
267}
268
269#[expect(clippy::cast_possible_truncation)]
270fn map_range(
271    sm: &SourceMap,
272    sp: Range<BytePos>,
273    f: impl for<'a> FnOnce(&'a SourceFile, &'a str, Range<usize>) -> Option<Range<usize>>,
274) -> Option<Range<BytePos>> {
275    if let Some(src) = get_source_range(sm, sp.clone())
276        && let Some(text) = &src.sf.src
277        && let Some(range) = f(&src.sf, text, src.range.clone())
278    {
279        debug_assert!(
280            range.start <= text.len() && range.end <= text.len(),
281            "Range `{range:?}` is outside the source file (file `{}`, length `{}`)",
282            src.sf.name.prefer_local_unconditionally(),
283            text.len(),
284        );
285        debug_assert!(range.start <= range.end, "Range `{range:?}` has overlapping bounds");
286        let dstart = (range.start as u32).wrapping_sub(src.range.start as u32);
287        let dend = (range.end as u32).wrapping_sub(src.range.start as u32);
288        Some(BytePos(sp.start.0.wrapping_add(dstart))..BytePos(sp.start.0.wrapping_add(dend)))
289    } else {
290        None
291    }
292}
293
294fn ends_with_line_comment_or_broken(text: &str) -> bool {
295    let Some(last) = tokenize(text, FrontmatterAllowed::No).last() else {
296        return false;
297    };
298    match last.kind {
299        // Will give the wrong result on text like `" // "` where the first quote ends a string
300        // started earlier. The only workaround is to lex the whole file which we don't really want
301        // to do.
302        TokenKind::LineComment { .. } | TokenKind::BlockComment { terminated: false, .. } => true,
303        TokenKind::Literal { kind, .. } => matches!(
304            kind,
305            LiteralKind::Byte { terminated: false }
306                | LiteralKind::ByteStr { terminated: false }
307                | LiteralKind::CStr { terminated: false }
308                | LiteralKind::Char { terminated: false }
309                | LiteralKind::RawByteStr { n_hashes: None }
310                | LiteralKind::RawCStr { n_hashes: None }
311                | LiteralKind::RawStr { n_hashes: None }
312        ),
313        _ => false,
314    }
315}
316
317fn with_leading_whitespace_inner(lines: &[RelativeBytePos], src: &str, range: Range<usize>) -> Option<usize> {
318    debug_assert!(lines.is_empty() || lines[0].to_u32() == 0);
319
320    let start = src.get(..range.start)?.trim_end();
321    let next_line = lines.partition_point(|&pos| pos.to_usize() <= start.len());
322    if let Some(line_end) = lines.get(next_line)
323        && line_end.to_usize() <= range.start
324        && let prev_start = lines.get(next_line - 1).map_or(0, |&x| x.to_usize())
325        && ends_with_line_comment_or_broken(&start[prev_start..])
326        && let next_line = lines.partition_point(|&pos| pos.to_usize() < range.end)
327        && let next_start = lines.get(next_line).map_or(src.len(), |&x| x.to_usize())
328        && tokenize(src.get(range.end..next_start)?, FrontmatterAllowed::No)
329            .any(|t| !matches!(t.kind, TokenKind::Whitespace))
330    {
331        Some(range.start)
332    } else {
333        Some(start.len())
334    }
335}
336
337fn with_leading_whitespace(sm: &SourceMap, sp: Range<BytePos>) -> Range<BytePos> {
338    map_range(sm, sp.clone(), |sf, src, range| {
339        Some(with_leading_whitespace_inner(sf.lines(), src, range.clone())?..range.end)
340    })
341    .unwrap_or(sp)
342}
343
344fn trim_start(sm: &SourceMap, sp: Range<BytePos>) -> Range<BytePos> {
345    map_range(sm, sp.clone(), |_, src, range| {
346        let src = src.get(range.clone())?;
347        Some(range.start + (src.len() - src.trim_start().len())..range.end)
348    })
349    .unwrap_or(sp)
350}
351
352pub struct SourceFileRange {
353    pub sf: Arc<SourceFile>,
354    pub range: Range<usize>,
355}
356impl SourceFileRange {
357    /// Attempts to get the text from the source file. This can fail if the source text isn't
358    /// loaded.
359    pub fn as_str(&self) -> Option<&str> {
360        (self.sf.src.as_ref().map(|src| src.as_str()))
361            .or_else(|| self.sf.external_src.get()?.get_source())
362            .and_then(|x| x.get(self.range.clone()))
363    }
364}
365
366/// Like [`snippet_block`], but add braces if the expr is not an `ExprKind::Block` with no label.
367pub fn expr_block<'sm>(
368    sm: impl HasSourceMap<'sm>,
369    expr: &Expr<'_>,
370    outer: SyntaxContext,
371    default: &str,
372    indent_relative_to: Option<Span>,
373    app: &mut Applicability,
374) -> String {
375    let (code, from_macro) = snippet_block_with_context(sm, expr.span, outer, default, indent_relative_to, app);
376    if !from_macro
377        && let ExprKind::Block(block, None) = expr.kind
378        && block.rules != BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
379    {
380        code
381    } else {
382        // FIXME: add extra indent for the unsafe blocks:
383        //     original code:   unsafe { ... }
384        //     result code:     { unsafe { ... } }
385        //     desired code:    {\n  unsafe { ... }\n}
386        format!("{{ {code} }}")
387    }
388}
389
390/// Returns a new Span that extends the original Span to the first non-whitespace char of the first
391/// line.
392///
393/// ```rust,ignore
394///     let x = ();
395/// //          ^^
396/// // will be converted to
397///     let x = ();
398/// //  ^^^^^^^^^^
399/// ```
400pub fn first_line_of_span<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> Span {
401    first_char_in_first_line(sm, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos))
402}
403
404fn first_char_in_first_line<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> Option<BytePos> {
405    let line_span = line_span(sm, span);
406    snippet_opt(sm, line_span).and_then(|snip| {
407        snip.find(|c: char| !c.is_whitespace())
408            .map(|pos| line_span.lo() + BytePos::from_usize(pos))
409    })
410}
411
412/// Extends the span to the beginning of the spans line, incl. whitespaces.
413///
414/// ```no_run
415///        let x = ();
416/// //             ^^
417/// // will be converted to
418///        let x = ();
419/// // ^^^^^^^^^^^^^^
420/// ```
421fn line_span<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> Span {
422    let span = original_sp(span, DUMMY_SP);
423    let SourceFileAndLine { sf, line } = sm.source_map().lookup_line(span.lo()).unwrap();
424    let line_start = sf.lines()[line];
425    let line_start = sf.absolute_position(line_start);
426    span.with_lo(line_start)
427}
428
429/// Returns the indentation of the line of a span
430///
431/// ```rust,ignore
432/// let x = ();
433/// //      ^^ -- will return 0
434///     let x = ();
435/// //          ^^ -- will return 4
436/// ```
437pub fn indent_of<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> Option<usize> {
438    snippet_opt(sm, line_span(sm, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
439}
440
441/// Gets a snippet of the indentation of the line of a span
442pub fn snippet_indent<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> Option<String> {
443    snippet_opt(sm, line_span(sm, span)).map(|mut s| {
444        let len = s.len() - s.trim_start().len();
445        s.truncate(len);
446        s
447    })
448}
449
450// If the snippet is empty, it's an attribute that was inserted during macro
451// expansion and we want to ignore those, because they could come from external
452// sources that the user has no control over.
453// For some reason these attributes don't have any expansion info on them, so
454// we have to check it this way until there is a better way.
455pub fn is_present_in_source<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> bool {
456    if let Some(snippet) = snippet_opt(sm, span)
457        && snippet.is_empty()
458    {
459        return false;
460    }
461    true
462}
463
464/// Returns the position just before rarrow
465///
466/// ```rust,ignore
467/// fn into(self) -> () {}
468///              ^
469/// // in case of unformatted code
470/// fn into2(self)-> () {}
471///               ^
472/// fn into3(self)   -> () {}
473///               ^
474/// ```
475pub fn position_before_rarrow(s: &str) -> Option<usize> {
476    s.rfind("->").map(|rpos| {
477        let mut rpos = rpos;
478        let chars: Vec<char> = s.chars().collect();
479        while rpos > 1 {
480            if let Some(c) = chars.get(rpos - 1)
481                && c.is_whitespace()
482            {
483                rpos -= 1;
484                continue;
485            }
486            break;
487        }
488        rpos
489    })
490}
491
492/// Reindent a multiline string with possibility of ignoring the first line.
493pub fn reindent_multiline(s: &str, ignore_first: bool, indent: Option<usize>) -> String {
494    let s_space = reindent_multiline_inner(s, ignore_first, indent, ' ');
495    let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t');
496    reindent_multiline_inner(&s_tab, ignore_first, indent, ' ')
497}
498
499fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
500    let x = s
501        .lines()
502        .skip(usize::from(ignore_first))
503        .filter_map(|l| {
504            if l.is_empty() {
505                None
506            } else {
507                // ignore empty lines
508                Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0)
509            }
510        })
511        .min()
512        .unwrap_or(0);
513    let indent = indent.unwrap_or(0);
514    s.lines()
515        .enumerate()
516        .map(|(i, l)| {
517            if (ignore_first && i == 0) || l.is_empty() {
518                l.to_owned()
519            } else if x > indent {
520                l.split_at(x - indent).1.to_owned()
521            } else {
522                " ".repeat(indent - x) + l
523            }
524        })
525        .collect::<Vec<String>>()
526        .join("\n")
527}
528
529/// Converts a span to a code snippet if available, otherwise returns the default.
530///
531/// This is useful if you want to provide suggestions for your lint or more generally, if you want
532/// to convert a given `Span` to a `str`.
533///
534/// To create suggestions consider using [`snippet_with_applicability`] to ensure that the
535/// applicability stays correct.
536///
537/// # Example
538/// ```rust,ignore
539/// // Given two spans one for `value` and one for the `init` expression.
540/// let value = Vec::new();
541/// //  ^^^^^   ^^^^^^^^^^
542/// //  span1   span2
543///
544/// // The snippet call would return the corresponding code snippet
545/// snippet(cx, span1, "..") // -> "value"
546/// snippet(cx, span2, "..") // -> "Vec::new()"
547/// ```
548pub fn snippet<'a, 'sm>(sm: impl HasSourceMap<'sm>, span: Span, default: &'a str) -> Cow<'a, str> {
549    snippet_opt(sm, span).map_or_else(|| Cow::Borrowed(default), From::from)
550}
551
552/// Same as [`snippet`], but it adapts the applicability level by following rules:
553///
554/// - Applicability level `Unspecified` will never be changed.
555/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
556/// - If the default value is used and the applicability level is `MachineApplicable`, change it to
557///   `HasPlaceholders`
558///
559/// If the span might realistically contain a macro call (e.g. `vec![]`), consider using
560/// [`snippet_with_context`] instead.
561pub fn snippet_with_applicability<'a, 'sm>(
562    sm: impl HasSourceMap<'sm>,
563    span: Span,
564    default: &'a str,
565    applicability: &mut Applicability,
566) -> Cow<'a, str> {
567    snippet_with_applicability_sm(sm.source_map(), span, default, applicability)
568}
569
570fn snippet_with_applicability_sm<'a>(
571    sm: &SourceMap,
572    span: Span,
573    default: &'a str,
574    applicability: &mut Applicability,
575) -> Cow<'a, str> {
576    if *applicability != Applicability::Unspecified && span.from_expansion() {
577        *applicability = Applicability::MaybeIncorrect;
578    }
579    if let Some(t) = snippet_opt(sm, span) {
580        Cow::Owned(t)
581    } else {
582        if *applicability == Applicability::MachineApplicable {
583            *applicability = Applicability::HasPlaceholders;
584        }
585        Cow::Borrowed(default)
586    }
587}
588
589/// Converts a span to a code snippet. Returns `None` if not available.
590pub fn snippet_opt<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> Option<String> {
591    sm.source_map().span_to_snippet(span).ok()
592}
593
594/// Converts a span (from a block) to a code snippet if available, otherwise use default.
595///
596/// This trims the code of indentation, except for the first line. Use it for blocks or block-like
597/// things which need to be printed as such.
598///
599/// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the
600/// resulting snippet of the given span.
601///
602/// # Example
603///
604/// ```rust,ignore
605/// snippet_block(cx, block.span, "..", None)
606/// // where, `block` is the block of the if expr
607///     if x {
608///         y;
609///     }
610/// // will return the snippet
611/// {
612///     y;
613/// }
614/// ```
615///
616/// ```rust,ignore
617/// snippet_block(cx, block.span, "..", Some(if_expr.span))
618/// // where, `block` is the block of the if expr
619///     if x {
620///         y;
621///     }
622/// // will return the snippet
623/// {
624///         y;
625///     } // aligned with `if`
626/// ```
627/// Note that the first line of the snippet always has 0 indentation.
628pub fn snippet_block<'sm>(
629    sm: impl HasSourceMap<'sm>,
630    span: Span,
631    default: &str,
632    indent_relative_to: Option<Span>,
633) -> String {
634    let snip = snippet(sm, span, default);
635    let indent = indent_relative_to.and_then(|s| indent_of(sm, s));
636    reindent_multiline(&snip, true, indent)
637}
638
639/// Same as [`snippet_block`], but adapts the applicability level by the rules of
640/// [`snippet_with_applicability`].
641pub fn snippet_block_with_applicability<'sm>(
642    sm: impl HasSourceMap<'sm>,
643    span: Span,
644    default: &str,
645    indent_relative_to: Option<Span>,
646    applicability: &mut Applicability,
647) -> String {
648    let snip = snippet_with_applicability(sm, span, default, applicability);
649    let indent = indent_relative_to.and_then(|s| indent_of(sm, s));
650    reindent_multiline(&snip, true, indent)
651}
652
653/// Combination of [`snippet_block`] and [`snippet_with_context`].
654pub fn snippet_block_with_context<'sm>(
655    sm: impl HasSourceMap<'sm>,
656    span: Span,
657    outer: SyntaxContext,
658    default: &str,
659    indent_relative_to: Option<Span>,
660    app: &mut Applicability,
661) -> (String, bool) {
662    let (snip, from_macro) = snippet_with_context(sm, span, outer, default, app);
663    let indent = indent_relative_to.and_then(|s| indent_of(sm, s));
664    (reindent_multiline(&snip, true, indent), from_macro)
665}
666
667/// Same as [`snippet_with_applicability`], but first walks the span up to the given context.
668///
669/// This will result in the macro call, rather than the expansion, if the span is from a child
670/// context. If the span is not from a child context, it will be used directly instead.
671///
672/// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node
673/// would result in `box []`. If given the context of the address of expression, this function will
674/// correctly get a snippet of `vec![]`.
675///
676/// This will also return whether or not the snippet is a macro call.
677pub fn snippet_with_context<'a, 'sm>(
678    sm: impl HasSourceMap<'sm>,
679    span: Span,
680    outer: SyntaxContext,
681    default: &'a str,
682    applicability: &mut Applicability,
683) -> (Cow<'a, str>, bool) {
684    snippet_with_context_sm(sm.source_map(), span, outer, default, applicability)
685}
686
687fn snippet_with_context_sm<'a>(
688    sm: &SourceMap,
689    span: Span,
690    outer: SyntaxContext,
691    default: &'a str,
692    applicability: &mut Applicability,
693) -> (Cow<'a, str>, bool) {
694    // If it is just range desugaring, use the desugaring span since it may include parenthesis.
695    if span.desugaring_kind() == Some(DesugaringKind::RangeExpr) && span.parent_callsite().unwrap().ctxt() == outer {
696        return (snippet_with_applicability_sm(sm, span, default, applicability), false);
697    }
698
699    let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else(
700        || {
701            // The span is from a macro argument, and the outer context is the macro using the argument
702            if *applicability != Applicability::Unspecified {
703                *applicability = Applicability::MaybeIncorrect;
704            }
705            // TODO: get the argument span.
706            (span, false)
707        },
708        |outer_span| (outer_span, span.ctxt() != outer),
709    );
710
711    (
712        snippet_with_applicability_sm(sm, span, default, applicability),
713        is_macro_call,
714    )
715}
716
717/// Walks the span up to the target context, thereby returning the macro call site if the span is
718/// inside a macro expansion, or the original span if it is not.
719///
720/// Note this will return `None` in the case of the span being in a macro expansion, but the target
721/// context is from expanding a macro argument.
722///
723/// Given the following
724///
725/// ```rust,ignore
726/// macro_rules! m { ($e:expr) => { f($e) }; }
727/// g(m!(0))
728/// ```
729///
730/// If called with a span of the call to `f` and a context of the call to `g` this will return a
731/// span containing `m!(0)`. However, if called with a span of the literal `0` this will give a span
732/// containing `0` as the context is the same as the outer context.
733///
734/// This will traverse through multiple macro calls. Given the following:
735///
736/// ```rust,ignore
737/// macro_rules! m { ($e:expr) => { n!($e, 0) }; }
738/// macro_rules! n { ($e:expr, $f:expr) => { f($e, $f) }; }
739/// g(m!(0))
740/// ```
741///
742/// If called with a span of the call to `f` and a context of the call to `g` this will return a
743/// span containing `m!(0)`.
744pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option<Span> {
745    let outer_span = hygiene::walk_chain(span, outer);
746    (outer_span.ctxt() == outer).then_some(outer_span)
747}
748
749/// Trims the whitespace from the start and the end of the span.
750pub fn trim_span(sm: &SourceMap, span: Span) -> Span {
751    let data = span.data();
752    let sf: &_ = &sm.lookup_source_file(data.lo);
753    let Some(src) = sf.src.as_deref() else {
754        return span;
755    };
756    let Some(snip) = &src.get((data.lo - sf.start_pos).to_usize()..(data.hi - sf.start_pos).to_usize()) else {
757        return span;
758    };
759    let trim_start = snip.len() - snip.trim_start().len();
760    let trim_end = snip.len() - snip.trim_end().len();
761    SpanData {
762        lo: data.lo + BytePos::from_usize(trim_start),
763        hi: data.hi - BytePos::from_usize(trim_end),
764        ctxt: data.ctxt,
765        parent: data.parent,
766    }
767    .span()
768}
769
770/// Expand a span to include a preceding comma
771/// ```rust,ignore
772/// writeln!(o, "")   ->   writeln!(o, "")
773///             ^^                   ^^^^
774/// ```
775pub fn expand_past_previous_comma<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> Span {
776    let extended = sm.source_map().span_extend_to_prev_char(span, ',', true);
777    extended.with_lo(extended.lo() - BytePos(1))
778}
779
780/// Converts `expr` to a `char` literal if it's a `str` literal containing a single
781/// character (or a single byte with `ascii_only`)
782pub fn str_literal_to_char_literal<'sm>(
783    sm: impl HasSourceMap<'sm>,
784    expr: &Expr<'_>,
785    applicability: &mut Applicability,
786    ascii_only: bool,
787) -> Option<String> {
788    if let ExprKind::Lit(lit) = &expr.kind
789        && let LitKind::Str(r, style) = lit.node
790        && let string = r.as_str()
791        && let len = if ascii_only {
792            string.len()
793        } else {
794            string.chars().count()
795        }
796        && len == 1
797    {
798        let snip = snippet_with_applicability(sm, expr.span, string, applicability);
799        let ch = if let StrStyle::Raw(nhash) = style {
800            let nhash = nhash as usize;
801            // for raw string: r##"a"##
802            &snip[(nhash + 2)..(snip.len() - 1 - nhash)]
803        } else {
804            // for regular string: "a"
805            &snip[1..(snip.len() - 1)]
806        };
807
808        let hint = format!(
809            "'{}'",
810            match ch {
811                "'" => "\\'",
812                r"\" => "\\\\",
813                "\\\"" => "\"", // no need to escape `"` in `'"'`
814                _ => ch,
815            }
816        );
817
818        Some(hint)
819    } else {
820        None
821    }
822}
823
824#[cfg(test)]
825mod test {
826    use super::reindent_multiline;
827
828    #[test]
829    fn test_reindent_multiline_single_line() {
830        assert_eq!("", reindent_multiline("", false, None));
831        assert_eq!("...", reindent_multiline("...", false, None));
832        assert_eq!("...", reindent_multiline("    ...", false, None));
833        assert_eq!("...", reindent_multiline("\t...", false, None));
834        assert_eq!("...", reindent_multiline("\t\t...", false, None));
835    }
836
837    #[test]
838    #[rustfmt::skip]
839    fn test_reindent_multiline_block() {
840        assert_eq!("\
841    if x {
842        y
843    } else {
844        z
845    }", reindent_multiline("    if x {
846            y
847        } else {
848            z
849        }", false, None));
850        assert_eq!("\
851    if x {
852    \ty
853    } else {
854    \tz
855    }", reindent_multiline("    if x {
856        \ty
857        } else {
858        \tz
859        }", false, None));
860    }
861
862    #[test]
863    #[rustfmt::skip]
864    fn test_reindent_multiline_empty_line() {
865        assert_eq!("\
866    if x {
867        y
868
869    } else {
870        z
871    }", reindent_multiline("    if x {
872            y
873
874        } else {
875            z
876        }", false, None));
877    }
878
879    #[test]
880    #[rustfmt::skip]
881    fn test_reindent_multiline_lines_deeper() {
882        assert_eq!("\
883        if x {
884            y
885        } else {
886            z
887        }", reindent_multiline("\
888    if x {
889        y
890    } else {
891        z
892    }", true, Some(8)));
893    }
894}