]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/case_sensitive_file_extension_comparisons.rs
Merge remote-tracking branch 'upstream/master' into rustup
[rust.git] / clippy_lints / src / case_sensitive_file_extension_comparisons.rs
1 use crate::utils::paths::STRING;
2 use crate::utils::{match_def_path, span_lint_and_help};
3 use if_chain::if_chain;
4 use lazy_static::lazy_static;
5 use regex::Regex;
6 use rustc_ast::ast::LitKind;
7 use rustc_hir::{Expr, ExprKind, PathSegment};
8 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_middle::ty;
10 use rustc_session::{declare_lint_pass, declare_tool_lint};
11 use rustc_span::{source_map::Spanned, Span};
12
13 declare_clippy_lint! {
14     /// **What it does:**
15     /// Checks for calls to `ends_with` with possible file extensions
16     /// and suggests to use a case-insensitive approach instead.
17     ///
18     /// **Why is this bad?**
19     /// `ends_with` is case-sensitive and may not detect files with a valid extension.
20     ///
21     /// **Known problems:** None.
22     ///
23     /// **Example:**
24     ///
25     /// ```rust
26     /// fn is_rust_file(filename: &str) -> bool {
27     ///     filename.ends_with(".rs")
28     /// }
29     /// ```
30     /// Use instead:
31     /// ```rust
32     /// fn is_rust_file(filename: &str) -> bool {
33     ///     filename.rsplit('.').next().map(|ext| ext.eq_ignore_ascii_case("rs")) == Some(true)
34     /// }
35     /// ```
36     pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
37     pedantic,
38     "Checks for calls to ends_with with case-sensitive file extensions"
39 }
40
41 declare_lint_pass!(CaseSensitiveFileExtensionComparisons => [CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS]);
42
43 fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Span> {
44     lazy_static! {
45         static ref RE: Regex = Regex::new(r"^\.([a-z0-9]{1,5}|[A-Z0-9]{1,5})$").unwrap();
46     }
47     if_chain! {
48         if let ExprKind::MethodCall(PathSegment { ident, .. }, _, [obj, extension, ..], span) = expr.kind;
49         if ident.as_str() == "ends_with";
50         if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = extension.kind;
51         if RE.is_match(&ext_literal.as_str());
52         then {
53             let mut ty = ctx.typeck_results().expr_ty(obj);
54             ty = match ty.kind() {
55                 ty::Ref(_, ty, ..) => ty,
56                 _ => ty
57             };
58
59             match ty.kind() {
60                 ty::Str => {
61                     return Some(span);
62                 },
63                 ty::Adt(&ty::AdtDef { did, .. }, _) => {
64                     if match_def_path(ctx, did, &STRING) {
65                         return Some(span);
66                     }
67                 },
68                 _ => { return None; }
69             }
70         }
71     }
72     None
73 }
74
75 impl LateLintPass<'tcx> for CaseSensitiveFileExtensionComparisons {
76     fn check_expr(&mut self, ctx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
77         if let Some(span) = check_case_sensitive_file_extension_comparison(ctx, expr) {
78             span_lint_and_help(
79                 ctx,
80                 CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
81                 span,
82                 "case-sensitive file extension comparison",
83                 None,
84                 "consider using a case-insensitive comparison instead",
85             );
86         }
87     }
88 }