]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/methods/collapsible_str_replace.rs
Merge commit 'f4850f7292efa33759b4f7f9b7621268979e9914' into clippyup
[rust.git] / src / tools / clippy / clippy_lints / src / methods / collapsible_str_replace.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::source::snippet;
3 use clippy_utils::visitors::for_each_expr;
4 use clippy_utils::{eq_expr_value, get_parent_expr};
5 use core::ops::ControlFlow;
6 use rustc_errors::Applicability;
7 use rustc_hir as hir;
8 use rustc_lint::LateContext;
9 use std::collections::VecDeque;
10
11 use super::method_call;
12 use super::COLLAPSIBLE_STR_REPLACE;
13
14 pub(super) fn check<'tcx>(
15     cx: &LateContext<'tcx>,
16     expr: &'tcx hir::Expr<'tcx>,
17     from: &'tcx hir::Expr<'tcx>,
18     to: &'tcx hir::Expr<'tcx>,
19 ) {
20     let replace_methods = collect_replace_calls(cx, expr, to);
21     if replace_methods.methods.len() > 1 {
22         let from_kind = cx.typeck_results().expr_ty(from).peel_refs().kind();
23         // If the parent node's `to` argument is the same as the `to` argument
24         // of the last replace call in the current chain, don't lint as it was already linted
25         if let Some(parent) = get_parent_expr(cx, expr)
26             && let Some(("replace", _, [current_from, current_to], _, _)) = method_call(parent)
27             && eq_expr_value(cx, to, current_to)
28             && from_kind == cx.typeck_results().expr_ty(current_from).peel_refs().kind()
29         {
30             return;
31         }
32
33         check_consecutive_replace_calls(cx, expr, &replace_methods, to);
34     }
35 }
36
37 struct ReplaceMethods<'tcx> {
38     methods: VecDeque<&'tcx hir::Expr<'tcx>>,
39     from_args: VecDeque<&'tcx hir::Expr<'tcx>>,
40 }
41
42 fn collect_replace_calls<'tcx>(
43     cx: &LateContext<'tcx>,
44     expr: &'tcx hir::Expr<'tcx>,
45     to_arg: &'tcx hir::Expr<'tcx>,
46 ) -> ReplaceMethods<'tcx> {
47     let mut methods = VecDeque::new();
48     let mut from_args = VecDeque::new();
49
50     let _: Option<()> = for_each_expr(expr, |e| {
51         if let Some(("replace", _, [from, to], _, _)) = method_call(e) {
52             if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() {
53                 methods.push_front(e);
54                 from_args.push_front(from);
55                 ControlFlow::Continue(())
56             } else {
57                 ControlFlow::BREAK
58             }
59         } else {
60             ControlFlow::Continue(())
61         }
62     });
63
64     ReplaceMethods { methods, from_args }
65 }
66
67 /// Check a chain of `str::replace` calls for `collapsible_str_replace` lint.
68 fn check_consecutive_replace_calls<'tcx>(
69     cx: &LateContext<'tcx>,
70     expr: &'tcx hir::Expr<'tcx>,
71     replace_methods: &ReplaceMethods<'tcx>,
72     to_arg: &'tcx hir::Expr<'tcx>,
73 ) {
74     let from_args = &replace_methods.from_args;
75     let from_arg_reprs: Vec<String> = from_args
76         .iter()
77         .map(|from_arg| snippet(cx, from_arg.span, "..").to_string())
78         .collect();
79     let app = Applicability::MachineApplicable;
80     let earliest_replace_call = replace_methods.methods.front().unwrap();
81     if let Some((_, _, [..], span_lo, _)) = method_call(earliest_replace_call) {
82         span_lint_and_sugg(
83             cx,
84             COLLAPSIBLE_STR_REPLACE,
85             expr.span.with_lo(span_lo.lo()),
86             "used consecutive `str::replace` call",
87             "replace with",
88             format!(
89                 "replace([{}], {})",
90                 from_arg_reprs.join(", "),
91                 snippet(cx, to_arg.span, ".."),
92             ),
93             app,
94         );
95     }
96 }