| 
 | 1 | +use clippy_utils::diagnostics::span_lint_and_sugg;  | 
 | 2 | +use clippy_utils::source::snippet;  | 
 | 3 | +use clippy_utils::ty::{get_type_diagnostic_name, is_type_diagnostic_item, is_type_lang_item};  | 
 | 4 | +use rustc_errors::Applicability;  | 
 | 5 | +use rustc_hir::ExprKind::{Binary, MethodCall};  | 
 | 6 | +use rustc_hir::{BinOpKind, Expr, LangItem};  | 
 | 7 | +use rustc_lint::{LateContext, LateLintPass};  | 
 | 8 | +use rustc_middle::ty;  | 
 | 9 | +use rustc_middle::ty::{Ty, UintTy};  | 
 | 10 | +use rustc_session::declare_lint_pass;  | 
 | 11 | +use rustc_span::sym;  | 
 | 12 | + | 
 | 13 | +declare_clippy_lint! {  | 
 | 14 | +    /// ### What it does  | 
 | 15 | +    /// Checks for manual case-insensitive ASCII comparison.  | 
 | 16 | +    ///  | 
 | 17 | +    /// ### Why is this bad?  | 
 | 18 | +    /// The `eq_ignore_ascii_case` method is faster because it does not allocate  | 
 | 19 | +    /// memory for the new strings, and it is more readable.  | 
 | 20 | +    ///  | 
 | 21 | +    /// ### Example  | 
 | 22 | +    /// ```no_run  | 
 | 23 | +    /// fn compare(a: &str, b: &str) -> bool {  | 
 | 24 | +    ///     a.to_ascii_lowercase() == b.to_ascii_lowercase() || a.to_ascii_lowercase() == "abc"  | 
 | 25 | +    /// }  | 
 | 26 | +    /// ```  | 
 | 27 | +    /// Use instead:  | 
 | 28 | +    /// ```no_run  | 
 | 29 | +    /// fn compare(a: &str, b: &str) -> bool {  | 
 | 30 | +    ///    a.eq_ignore_ascii_case(b) || a.eq_ignore_ascii_case("abc")  | 
 | 31 | +    /// }  | 
 | 32 | +    /// ```  | 
 | 33 | +    #[clippy::version = "1.82.0"]  | 
 | 34 | +    pub MANUAL_IGNORE_CASE_CMP,  | 
 | 35 | +    perf,  | 
 | 36 | +    "manual case-insensitive ASCII comparison"  | 
 | 37 | +}  | 
 | 38 | + | 
 | 39 | +declare_lint_pass!(ManualIgnoreCaseCmp => [MANUAL_IGNORE_CASE_CMP]);  | 
 | 40 | + | 
 | 41 | +fn get_ascii_type<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<Ty<'tcx>> {  | 
 | 42 | +    let ty_raw = cx.typeck_results().expr_ty(expr);  | 
 | 43 | +    let ty = ty_raw.peel_refs();  | 
 | 44 | +    if needs_ref_to_cmp(cx, ty)  | 
 | 45 | +        || ty.is_str()  | 
 | 46 | +        || ty.is_slice()  | 
 | 47 | +        || matches!(get_type_diagnostic_name(cx, ty), Some(sym::OsStr | sym::OsString))  | 
 | 48 | +    {  | 
 | 49 | +        Some(ty_raw)  | 
 | 50 | +    } else {  | 
 | 51 | +        None  | 
 | 52 | +    }  | 
 | 53 | +}  | 
 | 54 | + | 
 | 55 | +/// Returns true if the type needs to be dereferenced to be compared  | 
 | 56 | +fn needs_ref_to_cmp(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {  | 
 | 57 | +    ty.is_char()  | 
 | 58 | +        || *ty.kind() == ty::Uint(UintTy::U8)  | 
 | 59 | +        || is_type_diagnostic_item(cx, ty, sym::Vec)  | 
 | 60 | +        || is_type_lang_item(cx, ty, LangItem::String)  | 
 | 61 | +}  | 
 | 62 | + | 
 | 63 | +impl LateLintPass<'_> for ManualIgnoreCaseCmp {  | 
 | 64 | +    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {  | 
 | 65 | +        // check if expression represents a comparison of two strings  | 
 | 66 | +        // using .to_ascii_lowercase() or .to_ascii_uppercase() methods  | 
 | 67 | +        // Offer to replace it with .eq_ignore_ascii_case() method  | 
 | 68 | +        if let Binary(op, left, right) = &expr.kind  | 
 | 69 | +            && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne)  | 
 | 70 | +            && let MethodCall(left_path, left_val, _, _) = left.kind  | 
 | 71 | +            && let MethodCall(right_path, right_val, _, _) = right.kind  | 
 | 72 | +            && left_path.ident == right_path.ident  | 
 | 73 | +            && matches!(  | 
 | 74 | +                left_path.ident.name.as_str(),  | 
 | 75 | +                "to_ascii_lowercase" | "to_ascii_uppercase"  | 
 | 76 | +            )  | 
 | 77 | +            && get_ascii_type(cx, left_val).is_some()  | 
 | 78 | +            && let Some(rtype) = get_ascii_type(cx, right_val)  | 
 | 79 | +        {  | 
 | 80 | +            // FIXME: there must be a better way to add dereference operator  | 
 | 81 | +            let deref = if needs_ref_to_cmp(cx, rtype) { "&" } else { "" };  | 
 | 82 | +            let neg = if op.node == BinOpKind::Ne { "!" } else { "" };  | 
 | 83 | +            span_lint_and_sugg(  | 
 | 84 | +                cx,  | 
 | 85 | +                MANUAL_IGNORE_CASE_CMP,  | 
 | 86 | +                expr.span,  | 
 | 87 | +                "manual case-insensitive ASCII comparison",  | 
 | 88 | +                "consider using `.eq_ignore_ascii_case()` instead",  | 
 | 89 | +                format!(  | 
 | 90 | +                    "{neg}{}.eq_ignore_ascii_case({deref}{})",  | 
 | 91 | +                    snippet(cx, left_val.span, "_"),  | 
 | 92 | +                    snippet(cx, right_val.span, "_")  | 
 | 93 | +                ),  | 
 | 94 | +                Applicability::MachineApplicable,  | 
 | 95 | +            );  | 
 | 96 | +        }  | 
 | 97 | +    }  | 
 | 98 | +}  | 
0 commit comments