blob: 70da6ad58bdcfbcee5f6d5af1f2d7e16fc62e2a9 [file] [log] [blame]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{Msrv, MATCHES_MACRO};
use clippy_utils::source::snippet_opt;
use clippy_utils::{is_from_proc_macro, is_trait_method, path_to_local};
use itertools::Itertools;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, Param, PatKind};
use rustc_lint::LateContext;
use rustc_span::sym;
use super::STRING_LIT_CHARS_ANY;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
recv: &Expr<'_>,
param: &'tcx Param<'tcx>,
body: &Expr<'_>,
msrv: &Msrv,
) {
if msrv.meets(MATCHES_MACRO)
&& is_trait_method(cx, expr, sym::Iterator)
&& let PatKind::Binding(_, arg, _, _) = param.pat.kind
&& let ExprKind::Lit(lit_kind) = recv.kind
&& let LitKind::Str(val, _) = lit_kind.node
&& let ExprKind::Binary(kind, lhs, rhs) = body.kind
&& let BinOpKind::Eq = kind.node
&& let Some(lhs_path) = path_to_local(lhs)
&& let Some(rhs_path) = path_to_local(rhs)
&& let scrutinee = match (lhs_path == arg, rhs_path == arg) {
(true, false) => rhs,
(false, true) => lhs,
_ => return,
}
&& !is_from_proc_macro(cx, expr)
&& let Some(scrutinee_snip) = snippet_opt(cx, scrutinee.span)
{
// Normalize the char using `map` so `join` doesn't use `Display`, if we don't then
// something like `r"\"` will become `'\'`, which is of course invalid
let pat_snip = val.as_str().chars().map(|c| format!("{c:?}")).join(" | ");
span_lint_and_then(
cx,
STRING_LIT_CHARS_ANY,
expr.span,
"usage of `.chars().any(...)` to check if a char matches any from a string literal",
|diag| {
diag.span_suggestion_verbose(
expr.span,
"use `matches!(...)` instead",
format!("matches!({scrutinee_snip}, {pat_snip})"),
Applicability::MachineApplicable,
);
}
);
}
}