forked from rust-lang/rust
-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add case_sensitive_file_extensions lint
Closes rust-lang#6425 Looks for ends_with methods calls with case sensitive extensions.
- Loading branch information
1 parent
a6b72d3
commit 61f3d9d
Showing
6 changed files
with
182 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
clippy_lints/src/case_sensitive_file_extension_comparisons.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
use crate::utils::paths::STRING; | ||
use crate::utils::{match_def_path, span_lint_and_help}; | ||
use if_chain::if_chain; | ||
use lazy_static::lazy_static; | ||
use regex::Regex; | ||
use rustc_ast::ast::LitKind; | ||
use rustc_hir::{Expr, ExprKind, PathSegment}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_middle::ty; | ||
use rustc_session::{declare_lint_pass, declare_tool_lint}; | ||
use rustc_span::{source_map::Spanned, Span}; | ||
|
||
declare_clippy_lint! { | ||
/// **What it does:** | ||
/// Checks for calls to `ends_with` with possible file extensions | ||
/// and suggests to use a case-insensitive approach instead. | ||
/// | ||
/// **Why is this bad?** | ||
/// `ends_with` is case-sensitive and may not detect files with a valid extension. | ||
/// | ||
/// **Known problems:** None. | ||
/// | ||
/// **Example:** | ||
/// | ||
/// ```rust | ||
/// fn is_rust_file(filename: &str) -> bool { | ||
/// filename.ends_with(".rs") | ||
/// } | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// fn is_rust_file(filename: &str) -> bool { | ||
/// filename.rsplit('.').next().map(|ext| ext.eq_ignore_ascii_case("rs")) == Some(true) | ||
/// } | ||
/// ``` | ||
pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, | ||
pedantic, | ||
"default lint description" | ||
} | ||
|
||
declare_lint_pass!(CaseSensitiveFileExtensionComparisons => [CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS]); | ||
|
||
fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Span> { | ||
lazy_static! { | ||
static ref RE: Regex = Regex::new(r"^\.([a-z0-9]{1,5}|[A-Z0-9]{1,5})$").unwrap(); | ||
} | ||
if_chain! { | ||
if let ExprKind::MethodCall(PathSegment { ident, .. }, _, [obj, extension, ..], span) = expr.kind; | ||
if ident.as_str() == "ends_with"; | ||
if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = extension.kind; | ||
if RE.is_match(&ext_literal.as_str()); | ||
then { | ||
let mut ty = ctx.typeck_results().expr_ty(obj); | ||
ty = match ty.kind() { | ||
ty::Ref(_, ty, ..) => ty, | ||
_ => ty | ||
}; | ||
|
||
match ty.kind() { | ||
ty::Str => { | ||
return Some(span); | ||
}, | ||
ty::Adt(&ty::AdtDef { did, .. }, _) => { | ||
if match_def_path(ctx, did, &STRING) { | ||
return Some(span); | ||
} | ||
}, | ||
_ => { return None; } | ||
} | ||
} | ||
} | ||
None | ||
} | ||
|
||
impl LateLintPass<'tcx> for CaseSensitiveFileExtensionComparisons { | ||
fn check_expr(&mut self, ctx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { | ||
if let Some(span) = check_case_sensitive_file_extension_comparison(ctx, expr) { | ||
span_lint_and_help( | ||
ctx, | ||
CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, | ||
span, | ||
"case-sensitive file extension comparison", | ||
None, | ||
"consider using a case-insensitive comparison instead", | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#![warn(clippy::case_sensitive_file_extension_comparisons)] | ||
|
||
use std::string::String; | ||
|
||
struct TestStruct {} | ||
|
||
impl TestStruct { | ||
fn ends_with(self, arg: &str) {} | ||
} | ||
|
||
fn is_rust_file(filename: &str) -> bool { | ||
filename.ends_with(".rs") | ||
} | ||
|
||
fn main() { | ||
// std::string::String and &str should trigger the lint failure with .ext12 | ||
let _ = String::from("").ends_with(".ext12"); | ||
let _ = "str".ends_with(".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 _ = String::from("").ends_with(".EXT12"); | ||
let _ = "str".ends_with(".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::from("").ends_with(".eXT12"); | ||
let _ = "str".ends_with(".eXT12"); | ||
TestStruct {}.ends_with(".eXT12"); | ||
|
||
// Should not trigger the lint failure with .EXT123 (too long) | ||
let _ = String::from("").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::from("").ends_with("a.ext"); | ||
let _ = "str".ends_with("a.extA"); | ||
TestStruct {}.ends_with("a.ext"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
error: case-sensitive file extension comparison | ||
--> $DIR/case_sensitive_file_extension_comparisons.rs:12:14 | ||
| | ||
LL | filename.ends_with(".rs") | ||
| ^^^^^^^^^^^^^^^^ | ||
| | ||
= note: `-D clippy::case-sensitive-file-extension-comparisons` implied by `-D warnings` | ||
= help: consider using a case-insensitive comparison instead | ||
|
||
error: case-sensitive file extension comparison | ||
--> $DIR/case_sensitive_file_extension_comparisons.rs:17:30 | ||
| | ||
LL | let _ = String::from("").ends_with(".ext12"); | ||
| ^^^^^^^^^^^^^^^^^^^ | ||
| | ||
= help: consider using a case-insensitive comparison instead | ||
|
||
error: case-sensitive file extension comparison | ||
--> $DIR/case_sensitive_file_extension_comparisons.rs:18:19 | ||
| | ||
LL | let _ = "str".ends_with(".ext12"); | ||
| ^^^^^^^^^^^^^^^^^^^ | ||
| | ||
= help: consider using a case-insensitive comparison instead | ||
|
||
error: case-sensitive file extension comparison | ||
--> $DIR/case_sensitive_file_extension_comparisons.rs:24:30 | ||
| | ||
LL | let _ = String::from("").ends_with(".EXT12"); | ||
| ^^^^^^^^^^^^^^^^^^^ | ||
| | ||
= help: consider using a case-insensitive comparison instead | ||
|
||
error: case-sensitive file extension comparison | ||
--> $DIR/case_sensitive_file_extension_comparisons.rs:25:19 | ||
| | ||
LL | let _ = "str".ends_with(".EXT12"); | ||
| ^^^^^^^^^^^^^^^^^^^ | ||
| | ||
= help: consider using a case-insensitive comparison instead | ||
|
||
error: aborting due to 5 previous errors | ||
|