@@ -5,6 +5,7 @@ use clippy_utils::{get_parent_expr, is_lint_allowed, match_function_call, method
55use clippy_utils:: { peel_blocks, SpanlessEq } ;
66use if_chain:: if_chain;
77use rustc_errors:: Applicability ;
8+ use rustc_hir:: def_id:: DefId ;
89use rustc_hir:: { BinOpKind , BorrowKind , Expr , ExprKind , LangItem , QPath } ;
910use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
1011use rustc_middle:: lint:: in_external_macro;
@@ -451,3 +452,58 @@ impl<'tcx> LateLintPass<'tcx> for StringToString {
451452 }
452453 }
453454}
455+
456+ declare_clippy_lint ! {
457+ /// ### What it does
458+ /// Warns about calling `str::trim` (or variants) before `str::split_whitespace`.
459+ ///
460+ /// ### Why is this bad?
461+ /// `split_whitespace` already ignores leading and trailing whitespace.
462+ ///
463+ /// ### Example
464+ /// ```rust
465+ /// " A B C ".trim().split_whitespace();
466+ /// ```
467+ /// Use instead:
468+ /// ```rust
469+ /// " A B C ".split_whitespace();
470+ /// ```
471+ #[ clippy:: version = "1.62.0" ]
472+ pub TRIM_SPLIT_WHITESPACE ,
473+ style,
474+ "using `str::trim()` or alike before `str::split_whitespace`"
475+ }
476+ declare_lint_pass ! ( TrimSplitWhitespace => [ TRIM_SPLIT_WHITESPACE ] ) ;
477+
478+ impl < ' tcx > LateLintPass < ' tcx > for TrimSplitWhitespace {
479+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & Expr < ' _ > ) {
480+ let tyckres = cx. typeck_results ( ) ;
481+ if_chain ! {
482+ if let ExprKind :: MethodCall ( path, [ split_recv] , split_ws_span) = expr. kind;
483+ if path. ident. name == sym!( split_whitespace) ;
484+ if let Some ( split_ws_def_id) = tyckres. type_dependent_def_id( expr. hir_id) ;
485+ if cx. tcx. is_diagnostic_item( sym:: str_split_whitespace, split_ws_def_id) ;
486+ if let ExprKind :: MethodCall ( path, [ _trim_recv] , trim_span) = split_recv. kind;
487+ if let trim_fn_name @ ( "trim" | "trim_start" | "trim_end" ) = path. ident. name. as_str( ) ;
488+ if let Some ( trim_def_id) = tyckres. type_dependent_def_id( split_recv. hir_id) ;
489+ if is_one_of_trim_diagnostic_items( cx, trim_def_id) ;
490+ then {
491+ span_lint_and_sugg(
492+ cx,
493+ TRIM_SPLIT_WHITESPACE ,
494+ trim_span. with_hi( split_ws_span. lo( ) ) ,
495+ & format!( "found call to `str::{}` before `str::split_whitespace`" , trim_fn_name) ,
496+ & format!( "remove `{}()`" , trim_fn_name) ,
497+ String :: new( ) ,
498+ Applicability :: MachineApplicable ,
499+ ) ;
500+ }
501+ }
502+ }
503+ }
504+
505+ fn is_one_of_trim_diagnostic_items ( cx : & LateContext < ' _ > , trim_def_id : DefId ) -> bool {
506+ cx. tcx . is_diagnostic_item ( sym:: str_trim, trim_def_id)
507+ || cx. tcx . is_diagnostic_item ( sym:: str_trim_start, trim_def_id)
508+ || cx. tcx . is_diagnostic_item ( sym:: str_trim_end, trim_def_id)
509+ }
0 commit comments