summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/methods/collapsible_str_replace.rs
blob: 5e01ed90ff0997402337ae9cba753f55495c2fb4 (plain)
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{eq_expr_value, get_parent_expr};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use std::collections::VecDeque;

use super::method_call;
use super::COLLAPSIBLE_STR_REPLACE;

pub(super) fn check<'tcx>(
    cx: &LateContext<'tcx>,
    expr: &'tcx hir::Expr<'tcx>,
    from: &'tcx hir::Expr<'tcx>,
    to: &'tcx hir::Expr<'tcx>,
) {
    let replace_methods = collect_replace_calls(cx, expr, to);
    if replace_methods.methods.len() > 1 {
        let from_kind = cx.typeck_results().expr_ty(from).peel_refs().kind();
        // If the parent node's `to` argument is the same as the `to` argument
        // of the last replace call in the current chain, don't lint as it was already linted
        if let Some(parent) = get_parent_expr(cx, expr)
            && let Some(("replace", _, [current_from, current_to], _, _)) = method_call(parent)
            && eq_expr_value(cx, to, current_to)
            && from_kind == cx.typeck_results().expr_ty(current_from).peel_refs().kind()
        {
            return;
        }

        check_consecutive_replace_calls(cx, expr, &replace_methods, to);
    }
}

struct ReplaceMethods<'tcx> {
    methods: VecDeque<&'tcx hir::Expr<'tcx>>,
    from_args: VecDeque<&'tcx hir::Expr<'tcx>>,
}

fn collect_replace_calls<'tcx>(
    cx: &LateContext<'tcx>,
    expr: &'tcx hir::Expr<'tcx>,
    to_arg: &'tcx hir::Expr<'tcx>,
) -> ReplaceMethods<'tcx> {
    let mut methods = VecDeque::new();
    let mut from_args = VecDeque::new();

    let _: Option<()> = for_each_expr(expr, |e| {
        if let Some(("replace", _, [from, to], _, _)) = method_call(e) {
            if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() {
                methods.push_front(e);
                from_args.push_front(from);
                ControlFlow::Continue(())
            } else {
                ControlFlow::Break(())
            }
        } else {
            ControlFlow::Continue(())
        }
    });

    ReplaceMethods { methods, from_args }
}

/// Check a chain of `str::replace` calls for `collapsible_str_replace` lint.
fn check_consecutive_replace_calls<'tcx>(
    cx: &LateContext<'tcx>,
    expr: &'tcx hir::Expr<'tcx>,
    replace_methods: &ReplaceMethods<'tcx>,
    to_arg: &'tcx hir::Expr<'tcx>,
) {
    let from_args = &replace_methods.from_args;
    let from_arg_reprs: Vec<String> = from_args
        .iter()
        .map(|from_arg| snippet(cx, from_arg.span, "..").to_string())
        .collect();
    let app = Applicability::MachineApplicable;
    let earliest_replace_call = replace_methods.methods.front().unwrap();
    if let Some((_, _, [..], span_lo, _)) = method_call(earliest_replace_call) {
        span_lint_and_sugg(
            cx,
            COLLAPSIBLE_STR_REPLACE,
            expr.span.with_lo(span_lo.lo()),
            "used consecutive `str::replace` call",
            "replace with",
            format!(
                "replace([{}], {})",
                from_arg_reprs.join(", "),
                snippet(cx, to_arg.span, ".."),
            ),
            app,
        );
    }
}