Skip to content

Commit a385d34

Browse files
committed
Auto merge of rust-lang#10107 - tylerjw:suggest_path, r=Alexendoo
Suggest using Path for comparing extensions fixes rust-lang#10042 changelog: Sugg: [`case_sensitive_file_extension_comparisons`]: Now displays a suggestion with `Path` [rust-lang#10107](rust-lang/rust-clippy#10107) <!-- changelog_checked -->
2 parents 0bca8dd + ea6ff7e commit a385d34

4 files changed

+175
-17
lines changed

clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
use clippy_utils::diagnostics::span_lint_and_help;
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::source::snippet_opt;
3+
use clippy_utils::source::{indent_of, reindent_multiline};
24
use clippy_utils::ty::is_type_lang_item;
35
use if_chain::if_chain;
46
use rustc_ast::ast::LitKind;
7+
use rustc_errors::Applicability;
58
use rustc_hir::{Expr, ExprKind, LangItem};
69
use rustc_lint::LateContext;
710
use rustc_span::{source_map::Spanned, Span};
@@ -15,6 +18,15 @@ pub(super) fn check<'tcx>(
1518
recv: &'tcx Expr<'_>,
1619
arg: &'tcx Expr<'_>,
1720
) {
21+
if let ExprKind::MethodCall(path_segment, ..) = recv.kind {
22+
if matches!(
23+
path_segment.ident.name.as_str(),
24+
"to_lowercase" | "to_uppercase" | "to_ascii_lowercase" | "to_ascii_uppercase"
25+
) {
26+
return;
27+
}
28+
}
29+
1830
if_chain! {
1931
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
2032
if let Some(impl_id) = cx.tcx.impl_of_method(method_id);
@@ -28,13 +40,37 @@ pub(super) fn check<'tcx>(
2840
let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs();
2941
if recv_ty.is_str() || is_type_lang_item(cx, recv_ty, LangItem::String);
3042
then {
31-
span_lint_and_help(
43+
span_lint_and_then(
3244
cx,
3345
CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
34-
call_span,
46+
recv.span.to(call_span),
3547
"case-sensitive file extension comparison",
36-
None,
37-
"consider using a case-insensitive comparison instead",
48+
|diag| {
49+
diag.help("consider using a case-insensitive comparison instead");
50+
if let Some(mut recv_source) = snippet_opt(cx, recv.span) {
51+
52+
if !cx.typeck_results().expr_ty(recv).is_ref() {
53+
recv_source = format!("&{recv_source}");
54+
}
55+
56+
let suggestion_source = reindent_multiline(
57+
format!(
58+
"std::path::Path::new({})
59+
.extension()
60+
.map_or(false, |ext| ext.eq_ignore_ascii_case(\"{}\"))",
61+
recv_source, ext_str.strip_prefix('.').unwrap()).into(),
62+
true,
63+
Some(indent_of(cx, call_span).unwrap_or(0) + 4)
64+
);
65+
66+
diag.span_suggestion(
67+
recv.span.to(call_span),
68+
"use std::path::Path",
69+
suggestion_source,
70+
Applicability::MaybeIncorrect,
71+
);
72+
}
73+
}
3874
);
3975
}
4076
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// run-rustfix
2+
#![warn(clippy::case_sensitive_file_extension_comparisons)]
3+
4+
use std::string::String;
5+
6+
struct TestStruct;
7+
8+
impl TestStruct {
9+
fn ends_with(self, _arg: &str) {}
10+
}
11+
12+
#[allow(dead_code)]
13+
fn is_rust_file(filename: &str) -> bool {
14+
std::path::Path::new(filename)
15+
.extension()
16+
.map_or(false, |ext| ext.eq_ignore_ascii_case("rs"))
17+
}
18+
19+
fn main() {
20+
// std::string::String and &str should trigger the lint failure with .ext12
21+
let _ = std::path::Path::new(&String::new())
22+
.extension()
23+
.map_or(false, |ext| ext.eq_ignore_ascii_case("ext12"));
24+
let _ = std::path::Path::new("str")
25+
.extension()
26+
.map_or(false, |ext| ext.eq_ignore_ascii_case("ext12"));
27+
28+
// The fixup should preserve the indentation level
29+
{
30+
let _ = std::path::Path::new("str")
31+
.extension()
32+
.map_or(false, |ext| ext.eq_ignore_ascii_case("ext12"));
33+
}
34+
35+
// The test struct should not trigger the lint failure with .ext12
36+
TestStruct {}.ends_with(".ext12");
37+
38+
// std::string::String and &str should trigger the lint failure with .EXT12
39+
let _ = std::path::Path::new(&String::new())
40+
.extension()
41+
.map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
42+
let _ = std::path::Path::new("str")
43+
.extension()
44+
.map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
45+
46+
// Should not trigger the lint failure because of the calls to to_lowercase and to_uppercase
47+
let _ = String::new().to_lowercase().ends_with(".EXT12");
48+
let _ = String::new().to_uppercase().ends_with(".EXT12");
49+
50+
// The test struct should not trigger the lint failure with .EXT12
51+
TestStruct {}.ends_with(".EXT12");
52+
53+
// Should not trigger the lint failure with .eXT12
54+
let _ = String::new().ends_with(".eXT12");
55+
let _ = "str".ends_with(".eXT12");
56+
TestStruct {}.ends_with(".eXT12");
57+
58+
// Should not trigger the lint failure with .EXT123 (too long)
59+
let _ = String::new().ends_with(".EXT123");
60+
let _ = "str".ends_with(".EXT123");
61+
TestStruct {}.ends_with(".EXT123");
62+
63+
// Shouldn't fail if it doesn't start with a dot
64+
let _ = String::new().ends_with("a.ext");
65+
let _ = "str".ends_with("a.extA");
66+
TestStruct {}.ends_with("a.ext");
67+
}

tests/ui/case_sensitive_file_extension_comparisons.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
// run-rustfix
12
#![warn(clippy::case_sensitive_file_extension_comparisons)]
23

34
use std::string::String;
45

56
struct TestStruct;
67

78
impl TestStruct {
8-
fn ends_with(self, arg: &str) {}
9+
fn ends_with(self, _arg: &str) {}
910
}
1011

12+
#[allow(dead_code)]
1113
fn is_rust_file(filename: &str) -> bool {
1214
filename.ends_with(".rs")
1315
}
@@ -17,13 +19,22 @@ fn main() {
1719
let _ = String::new().ends_with(".ext12");
1820
let _ = "str".ends_with(".ext12");
1921

22+
// The fixup should preserve the indentation level
23+
{
24+
let _ = "str".ends_with(".ext12");
25+
}
26+
2027
// The test struct should not trigger the lint failure with .ext12
2128
TestStruct {}.ends_with(".ext12");
2229

2330
// std::string::String and &str should trigger the lint failure with .EXT12
2431
let _ = String::new().ends_with(".EXT12");
2532
let _ = "str".ends_with(".EXT12");
2633

34+
// Should not trigger the lint failure because of the calls to to_lowercase and to_uppercase
35+
let _ = String::new().to_lowercase().ends_with(".EXT12");
36+
let _ = String::new().to_uppercase().ends_with(".EXT12");
37+
2738
// The test struct should not trigger the lint failure with .EXT12
2839
TestStruct {}.ends_with(".EXT12");
2940

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,87 @@
11
error: case-sensitive file extension comparison
2-
--> $DIR/case_sensitive_file_extension_comparisons.rs:12:14
2+
--> $DIR/case_sensitive_file_extension_comparisons.rs:14:5
33
|
44
LL | filename.ends_with(".rs")
5-
| ^^^^^^^^^^^^^^^^
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
66
|
77
= help: consider using a case-insensitive comparison instead
88
= note: `-D clippy::case-sensitive-file-extension-comparisons` implied by `-D warnings`
9+
help: use std::path::Path
10+
|
11+
LL ~ std::path::Path::new(filename)
12+
LL + .extension()
13+
LL + .map_or(false, |ext| ext.eq_ignore_ascii_case("rs"))
14+
|
915

1016
error: case-sensitive file extension comparison
11-
--> $DIR/case_sensitive_file_extension_comparisons.rs:17:27
17+
--> $DIR/case_sensitive_file_extension_comparisons.rs:19:13
1218
|
1319
LL | let _ = String::new().ends_with(".ext12");
14-
| ^^^^^^^^^^^^^^^^^^^
20+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1521
|
1622
= help: consider using a case-insensitive comparison instead
23+
help: use std::path::Path
24+
|
25+
LL ~ let _ = std::path::Path::new(&String::new())
26+
LL + .extension()
27+
LL ~ .map_or(false, |ext| ext.eq_ignore_ascii_case("ext12"));
28+
|
1729

1830
error: case-sensitive file extension comparison
19-
--> $DIR/case_sensitive_file_extension_comparisons.rs:18:19
31+
--> $DIR/case_sensitive_file_extension_comparisons.rs:20:13
2032
|
2133
LL | let _ = "str".ends_with(".ext12");
22-
| ^^^^^^^^^^^^^^^^^^^
34+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
2335
|
2436
= help: consider using a case-insensitive comparison instead
37+
help: use std::path::Path
38+
|
39+
LL ~ let _ = std::path::Path::new("str")
40+
LL + .extension()
41+
LL ~ .map_or(false, |ext| ext.eq_ignore_ascii_case("ext12"));
42+
|
2543

2644
error: case-sensitive file extension comparison
27-
--> $DIR/case_sensitive_file_extension_comparisons.rs:24:27
45+
--> $DIR/case_sensitive_file_extension_comparisons.rs:24:17
46+
|
47+
LL | let _ = "str".ends_with(".ext12");
48+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
49+
|
50+
= help: consider using a case-insensitive comparison instead
51+
help: use std::path::Path
52+
|
53+
LL ~ let _ = std::path::Path::new("str")
54+
LL + .extension()
55+
LL ~ .map_or(false, |ext| ext.eq_ignore_ascii_case("ext12"));
56+
|
57+
58+
error: case-sensitive file extension comparison
59+
--> $DIR/case_sensitive_file_extension_comparisons.rs:31:13
2860
|
2961
LL | let _ = String::new().ends_with(".EXT12");
30-
| ^^^^^^^^^^^^^^^^^^^
62+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3163
|
3264
= help: consider using a case-insensitive comparison instead
65+
help: use std::path::Path
66+
|
67+
LL ~ let _ = std::path::Path::new(&String::new())
68+
LL + .extension()
69+
LL ~ .map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
70+
|
3371

3472
error: case-sensitive file extension comparison
35-
--> $DIR/case_sensitive_file_extension_comparisons.rs:25:19
73+
--> $DIR/case_sensitive_file_extension_comparisons.rs:32:13
3674
|
3775
LL | let _ = "str".ends_with(".EXT12");
38-
| ^^^^^^^^^^^^^^^^^^^
76+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
3977
|
4078
= help: consider using a case-insensitive comparison instead
79+
help: use std::path::Path
80+
|
81+
LL ~ let _ = std::path::Path::new("str")
82+
LL + .extension()
83+
LL ~ .map_or(false, |ext| ext.eq_ignore_ascii_case("EXT12"));
84+
|
4185

42-
error: aborting due to 5 previous errors
86+
error: aborting due to 6 previous errors
4387

0 commit comments

Comments
 (0)