about summary refs log tree commit diff
path: root/clippy_lints/src/strings.rs
diff options
context:
space:
mode:
Diffstat (limited to 'clippy_lints/src/strings.rs')
-rw-r--r--clippy_lints/src/strings.rs56
1 files changed, 56 insertions, 0 deletions
diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs
index 3573f632a36..7c196ccaa8c 100644
--- a/clippy_lints/src/strings.rs
+++ b/clippy_lints/src/strings.rs
@@ -5,6 +5,7 @@ use clippy_utils::{get_parent_expr, is_lint_allowed, match_function_call, method
 use clippy_utils::{peel_blocks, SpanlessEq};
 use if_chain::if_chain;
 use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
 use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, QPath};
 use rustc_lint::{LateContext, LateLintPass, LintContext};
 use rustc_middle::lint::in_external_macro;
@@ -451,3 +452,58 @@ impl<'tcx> LateLintPass<'tcx> for StringToString {
         }
     }
 }
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Warns about calling `str::trim` (or variants) before `str::split_whitespace`.
+    ///
+    /// ### Why is this bad?
+    /// `split_whitespace` already ignores leading and trailing whitespace.
+    ///
+    /// ### Example
+    /// ```rust
+    /// " A B C ".trim().split_whitespace();
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// " A B C ".split_whitespace();
+    /// ```
+    #[clippy::version = "1.62.0"]
+    pub TRIM_SPLIT_WHITESPACE,
+    style,
+    "using `str::trim()` or alike before `str::split_whitespace`"
+}
+declare_lint_pass!(TrimSplitWhitespace => [TRIM_SPLIT_WHITESPACE]);
+
+impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
+        let tyckres = cx.typeck_results();
+        if_chain! {
+            if let ExprKind::MethodCall(path, [split_recv], split_ws_span) = expr.kind;
+            if path.ident.name == sym!(split_whitespace);
+            if let Some(split_ws_def_id) = tyckres.type_dependent_def_id(expr.hir_id);
+            if cx.tcx.is_diagnostic_item(sym::str_split_whitespace, split_ws_def_id);
+            if let ExprKind::MethodCall(path, [_trim_recv], trim_span) = split_recv.kind;
+            if let trim_fn_name @ ("trim" | "trim_start" | "trim_end") = path.ident.name.as_str();
+            if let Some(trim_def_id) = tyckres.type_dependent_def_id(split_recv.hir_id);
+            if is_one_of_trim_diagnostic_items(cx, trim_def_id);
+            then {
+                span_lint_and_sugg(
+                    cx,
+                    TRIM_SPLIT_WHITESPACE,
+                    trim_span.with_hi(split_ws_span.lo()),
+                    &format!("found call to `str::{}` before `str::split_whitespace`", trim_fn_name),
+                    &format!("remove `{}()`", trim_fn_name),
+                    String::new(),
+                    Applicability::MachineApplicable,
+                );
+            }
+        }
+    }
+}
+
+fn is_one_of_trim_diagnostic_items(cx: &LateContext<'_>, trim_def_id: DefId) -> bool {
+    cx.tcx.is_diagnostic_item(sym::str_trim, trim_def_id)
+        || cx.tcx.is_diagnostic_item(sym::str_trim_start, trim_def_id)
+        || cx.tcx.is_diagnostic_item(sym::str_trim_end, trim_def_id)
+}