about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTyler Weaver <maybe@tylerjw.dev>2022-12-21 12:47:39 -0700
committerTyler Weaver <tyler@picknik.ai>2023-01-02 16:27:46 -0700
commitbd83650e91a751ad352444b458f9f50549fac208 (patch)
treeee12f26f6712e8c63cde07583aa0cdee0bc838d4
parent1a46dc0b9f923af1c012ac300a2b351cf99560c6 (diff)
downloadrust-bd83650e91a751ad352444b458f9f50549fac208.tar.gz
rust-bd83650e91a751ad352444b458f9f50549fac208.zip
Suggest using Path for comparing extensions
Signed-off-by: Tyler Weaver <maybe@tylerjw.dev>
-rw-r--r--clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs38
-rw-r--r--tests/ui/case_sensitive_file_extension_comparisons.fixed64
-rw-r--r--tests/ui/case_sensitive_file_extension_comparisons.rs8
-rw-r--r--tests/ui/case_sensitive_file_extension_comparisons.stderr82
4 files changed, 175 insertions, 17 deletions
diff --git a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs
index d226c0bba65..bc9b7a73b5b 100644
--- a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs
+++ b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs
@@ -1,7 +1,9 @@
-use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg::Sugg;
 use clippy_utils::ty::is_type_lang_item;
 use if_chain::if_chain;
 use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, LangItem};
 use rustc_lint::LateContext;
 use rustc_span::{source_map::Spanned, Span};
@@ -28,13 +30,39 @@ pub(super) fn check<'tcx>(
         let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs();
         if recv_ty.is_str() || is_type_lang_item(cx, recv_ty, LangItem::String);
         then {
-            span_lint_and_help(
+            span_lint_and_then(
                 cx,
                 CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
-                call_span,
+                recv.span.to(call_span),
                 "case-sensitive file extension comparison",
-                None,
-                "consider using a case-insensitive comparison instead",
+                |diag| {
+                    diag.help("consider using a case-insensitive comparison instead");
+                    let mut recv_str = Sugg::hir(cx, recv, "").to_string();
+
+                    if is_type_lang_item(cx, recv_ty, LangItem::String) {
+                        recv_str = format!("&{recv_str}");
+                    }
+
+                    if recv_str.contains(".to_lowercase()") {
+                        diag.note("to_lowercase allocates memory, this can be avoided by using Path");
+                        recv_str = recv_str.replace(".to_lowercase()", "");
+                    }
+
+                    if recv_str.contains(".to_uppercase()") {
+                        diag.note("to_uppercase allocates memory, this can be avoided by using Path");
+                        recv_str = recv_str.replace(".to_uppercase()", "");
+                    }
+
+                    diag.span_suggestion(
+                        recv.span.to(call_span),
+                        "use std::path::Path",
+                        format!("std::path::Path::new({})
+        .extension()
+        .map_or(false, |ext| ext.eq_ignore_ascii_case(\"{}\"))", 
+                            recv_str, ext_str.strip_prefix('.').unwrap()),
+                        Applicability::MaybeIncorrect,
+                    );
+                }
             );
         }
     }
diff --git a/tests/ui/case_sensitive_file_extension_comparisons.fixed b/tests/ui/case_sensitive_file_extension_comparisons.fixed
new file mode 100644
index 00000000000..a8b5950f208
--- /dev/null
+++ b/tests/ui/case_sensitive_file_extension_comparisons.fixed
@@ -0,0 +1,64 @@
+// run-rustfix
+#![warn(clippy::case_sensitive_file_extension_comparisons)]
+
+use std::string::String;
+
+struct TestStruct;
+
+impl TestStruct {
+    fn ends_with(self, _arg: &str) {}
+}
+
+#[allow(dead_code)]
+fn is_rust_file(filename: &str) -> bool {
+    std::path::Path::new(filename)
+        .extension()
+        .map_or(false, |ext| ext.eq_ignore_ascii_case("rs"))
+}
+
+fn main() {
+    // std::string::String and &str should trigger the lint failure with .ext12
+    let _ = std::path::Path::new(&String::new())
+        .extension()
+        .map_or(false, |ext| ext.eq_ignore_ascii_case("ext12"));
+    let _ = std::path::Path::new("str")
+        .extension()
+        .map_or(false, |ext| ext.eq_ignore_ascii_case("ext12"));
+
+    // The test struct should not trigger the lint failure with .ext12
+    TestStruct {}.ends_with(".ext12");
+
+    // std::string::String and &str should trigger the lint failure with .EXT12
+    let _ = std::path::Path::new(&String::new())
+        .extension()
+        .map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
+    let _ = std::path::Path::new("str")
+        .extension()
+        .map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
+
+    // This should print a note about how to_lowercase and to_uppercase allocates
+    let _ = std::path::Path::new(&String::new())
+        .extension()
+        .map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
+    let _ = std::path::Path::new(&String::new())
+        .extension()
+        .map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
+
+    // The test struct should not trigger the lint failure with .EXT12
+    TestStruct {}.ends_with(".EXT12");
+
+    // Should not trigger the lint failure with .eXT12
+    let _ = String::new().ends_with(".eXT12");
+    let _ = "str".ends_with(".eXT12");
+    TestStruct {}.ends_with(".eXT12");
+
+    // Should not trigger the lint failure with .EXT123 (too long)
+    let _ = String::new().ends_with(".EXT123");
+    let _ = "str".ends_with(".EXT123");
+    TestStruct {}.ends_with(".EXT123");
+
+    // Shouldn't fail if it doesn't start with a dot
+    let _ = String::new().ends_with("a.ext");
+    let _ = "str".ends_with("a.extA");
+    TestStruct {}.ends_with("a.ext");
+}
diff --git a/tests/ui/case_sensitive_file_extension_comparisons.rs b/tests/ui/case_sensitive_file_extension_comparisons.rs
index 6f0485b5279..ee9bcd73d52 100644
--- a/tests/ui/case_sensitive_file_extension_comparisons.rs
+++ b/tests/ui/case_sensitive_file_extension_comparisons.rs
@@ -1,3 +1,4 @@
+// run-rustfix
 #![warn(clippy::case_sensitive_file_extension_comparisons)]
 
 use std::string::String;
@@ -5,9 +6,10 @@ use std::string::String;
 struct TestStruct;
 
 impl TestStruct {
-    fn ends_with(self, arg: &str) {}
+    fn ends_with(self, _arg: &str) {}
 }
 
+#[allow(dead_code)]
 fn is_rust_file(filename: &str) -> bool {
     filename.ends_with(".rs")
 }
@@ -24,6 +26,10 @@ fn main() {
     let _ = String::new().ends_with(".EXT12");
     let _ = "str".ends_with(".EXT12");
 
+    // This should print a note about how to_lowercase and to_uppercase allocates
+    let _ = String::new().to_lowercase().ends_with(".EXT12");
+    let _ = String::new().to_uppercase().ends_with(".EXT12");
+
     // The test struct should not trigger the lint failure with .EXT12
     TestStruct {}.ends_with(".EXT12");
 
diff --git a/tests/ui/case_sensitive_file_extension_comparisons.stderr b/tests/ui/case_sensitive_file_extension_comparisons.stderr
index a28dd8bd5ad..33435f086d4 100644
--- a/tests/ui/case_sensitive_file_extension_comparisons.stderr
+++ b/tests/ui/case_sensitive_file_extension_comparisons.stderr
@@ -1,43 +1,103 @@
 error: case-sensitive file extension comparison
-  --> $DIR/case_sensitive_file_extension_comparisons.rs:12:14
+  --> $DIR/case_sensitive_file_extension_comparisons.rs:14:5
    |
 LL |     filename.ends_with(".rs")
-   |              ^^^^^^^^^^^^^^^^
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: consider using a case-insensitive comparison instead
    = note: `-D clippy::case-sensitive-file-extension-comparisons` implied by `-D warnings`
+help: use std::path::Path
+   |
+LL ~     std::path::Path::new(filename)
+LL +         .extension()
+LL +         .map_or(false, |ext| ext.eq_ignore_ascii_case("rs"))
+   |
 
 error: case-sensitive file extension comparison
-  --> $DIR/case_sensitive_file_extension_comparisons.rs:17:27
+  --> $DIR/case_sensitive_file_extension_comparisons.rs:19:13
    |
 LL |     let _ = String::new().ends_with(".ext12");
-   |                           ^^^^^^^^^^^^^^^^^^^
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: consider using a case-insensitive comparison instead
+help: use std::path::Path
+   |
+LL ~     let _ = std::path::Path::new(&String::new())
+LL +         .extension()
+LL ~         .map_or(false, |ext| ext.eq_ignore_ascii_case("ext12"));
+   |
 
 error: case-sensitive file extension comparison
-  --> $DIR/case_sensitive_file_extension_comparisons.rs:18:19
+  --> $DIR/case_sensitive_file_extension_comparisons.rs:20:13
    |
 LL |     let _ = "str".ends_with(".ext12");
-   |                   ^^^^^^^^^^^^^^^^^^^
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: consider using a case-insensitive comparison instead
+help: use std::path::Path
+   |
+LL ~     let _ = std::path::Path::new("str")
+LL +         .extension()
+LL ~         .map_or(false, |ext| ext.eq_ignore_ascii_case("ext12"));
+   |
 
 error: case-sensitive file extension comparison
-  --> $DIR/case_sensitive_file_extension_comparisons.rs:24:27
+  --> $DIR/case_sensitive_file_extension_comparisons.rs:26:13
    |
 LL |     let _ = String::new().ends_with(".EXT12");
-   |                           ^^^^^^^^^^^^^^^^^^^
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: consider using a case-insensitive comparison instead
+help: use std::path::Path
+   |
+LL ~     let _ = std::path::Path::new(&String::new())
+LL +         .extension()
+LL ~         .map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
+   |
 
 error: case-sensitive file extension comparison
-  --> $DIR/case_sensitive_file_extension_comparisons.rs:25:19
+  --> $DIR/case_sensitive_file_extension_comparisons.rs:27:13
    |
 LL |     let _ = "str".ends_with(".EXT12");
-   |                   ^^^^^^^^^^^^^^^^^^^
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: consider using a case-insensitive comparison instead
+help: use std::path::Path
+   |
+LL ~     let _ = std::path::Path::new("str")
+LL +         .extension()
+LL ~         .map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
+   |
+
+error: case-sensitive file extension comparison
+  --> $DIR/case_sensitive_file_extension_comparisons.rs:30:13
+   |
+LL |     let _ = String::new().to_lowercase().ends_with(".EXT12");
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: consider using a case-insensitive comparison instead
+   = note: to_lowercase allocates memory, this can be avoided by using Path
+help: use std::path::Path
+   |
+LL ~     let _ = std::path::Path::new(&String::new())
+LL +         .extension()
+LL ~         .map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
+   |
+
+error: case-sensitive file extension comparison
+  --> $DIR/case_sensitive_file_extension_comparisons.rs:31:13
+   |
+LL |     let _ = String::new().to_uppercase().ends_with(".EXT12");
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: consider using a case-insensitive comparison instead
+   = note: to_uppercase allocates memory, this can be avoided by using Path
+help: use std::path::Path
+   |
+LL ~     let _ = std::path::Path::new(&String::new())
+LL +         .extension()
+LL ~         .map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
+   |
 
-error: aborting due to 5 previous errors
+error: aborting due to 7 previous errors