|
| 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 | +} |
0 commit comments