1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
use crate::source::snippet;
use crate::visitors::{for_each_expr, Descend};
use crate::{path_to_local_id, strip_pat_refs};
use core::ops::ControlFlow;
use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind};
use rustc_lint::LateContext;
use rustc_span::Span;
use std::borrow::Cow;

pub fn get_spans(
    cx: &LateContext<'_>,
    opt_body_id: Option<BodyId>,
    idx: usize,
    replacements: &[(&'static str, &'static str)],
) -> Option<Vec<(Span, Cow<'static, str>)>> {
    if let Some(body) = opt_body_id.map(|id| cx.tcx.hir().body(id)) {
        if let PatKind::Binding(_, binding_id, _, _) = strip_pat_refs(body.params[idx].pat).kind {
            extract_clone_suggestions(cx, binding_id, replacements, body)
        } else {
            Some(vec![])
        }
    } else {
        Some(vec![])
    }
}

fn extract_clone_suggestions<'tcx>(
    cx: &LateContext<'tcx>,
    id: HirId,
    replace: &[(&'static str, &'static str)],
    body: &'tcx Body<'_>,
) -> Option<Vec<(Span, Cow<'static, str>)>> {
    let mut spans = Vec::new();
    for_each_expr(body, |e| {
        if let ExprKind::MethodCall(seg, recv, [], _) = e.kind
            && path_to_local_id(recv, id)
        {
            if seg.ident.as_str() == "capacity" {
                return ControlFlow::Break(());
            }
            for &(fn_name, suffix) in replace {
                if seg.ident.as_str() == fn_name {
                    spans.push((e.span, snippet(cx, recv.span, "_") + suffix));
                    return ControlFlow::Continue(Descend::No);
                }
            }
        }
        ControlFlow::Continue(Descend::Yes)
    })
    .is_none()
    .then_some(spans)
}