@@ -1131,6 +1131,40 @@ declare_clippy_lint! {
11311131 "Check for offset calculations on raw pointers to zero-sized types"
11321132}
11331133
1134+ declare_clippy_lint ! {
1135+ /// **What it does:** Checks for `FileType::is_file()`.
1136+ ///
1137+ /// **Why is this bad?** When people testing a file type with `FileType::is_file`
1138+ /// they are testing whether a path is something they can get bytes from. But
1139+ /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover
1140+ /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention.
1141+ ///
1142+ /// **Example:**
1143+ ///
1144+ /// ```rust,ignore
1145+ /// let metadata = std::fs::metadata("foo.txt")?;
1146+ /// let filetype = metadata.file_type();
1147+ ///
1148+ /// if filetype.is_file() {
1149+ /// // read file
1150+ /// }
1151+ /// ```
1152+ ///
1153+ /// should be written as:
1154+ ///
1155+ /// ```rust,ignore
1156+ /// let metadata = std::fs::metadata("foo.txt")?;
1157+ /// let filetype = metadata.file_type();
1158+ ///
1159+ /// if !filetype.is_dir() {
1160+ /// // read file
1161+ /// }
1162+ /// ```
1163+ pub FILETYPE_IS_FILE ,
1164+ restriction,
1165+ "`FileType::is_file` is not recommended to test for readable file type"
1166+ }
1167+
11341168declare_lint_pass ! ( Methods => [
11351169 OPTION_UNWRAP_USED ,
11361170 RESULT_UNWRAP_USED ,
@@ -1178,6 +1212,7 @@ declare_lint_pass!(Methods => [
11781212 UNINIT_ASSUMED_INIT ,
11791213 MANUAL_SATURATING_ARITHMETIC ,
11801214 ZST_OFFSET ,
1215+ FILETYPE_IS_FILE ,
11811216] ) ;
11821217
11831218impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for Methods {
@@ -1241,6 +1276,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
12411276 [ "add" ] | [ "offset" ] | [ "sub" ] | [ "wrapping_offset" ] | [ "wrapping_add" ] | [ "wrapping_sub" ] => {
12421277 check_pointer_offset ( cx, expr, arg_lists[ 0 ] )
12431278 } ,
1279+ [ "is_file" , ..] => lint_filetype_is_file ( cx, expr, arg_lists[ 0 ] ) ,
12441280 _ => { } ,
12451281 }
12461282
@@ -3225,3 +3261,35 @@ fn check_pointer_offset(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[
32253261 }
32263262 }
32273263}
3264+
3265+ fn lint_filetype_is_file ( cx : & LateContext < ' _ , ' _ > , expr : & hir:: Expr < ' _ > , args : & [ hir:: Expr < ' _ > ] ) {
3266+ let ty = cx. tables . expr_ty ( & args[ 0 ] ) ;
3267+
3268+ if !match_type ( cx, ty, & paths:: FILE_TYPE ) {
3269+ return ;
3270+ }
3271+
3272+ let span: Span ;
3273+ let verb: & str ;
3274+ let lint_unary: & str ;
3275+ let help_unary: & str ;
3276+ if_chain ! {
3277+ if let Some ( parent) = get_parent_expr( cx, expr) ;
3278+ if let hir:: ExprKind :: Unary ( op, _) = parent. kind;
3279+ if op == hir:: UnOp :: UnNot ;
3280+ then {
3281+ lint_unary = "!" ;
3282+ verb = "denies" ;
3283+ help_unary = "" ;
3284+ span = parent. span;
3285+ } else {
3286+ lint_unary = "" ;
3287+ verb = "covers" ;
3288+ help_unary = "!" ;
3289+ span = expr. span;
3290+ }
3291+ }
3292+ let lint_msg = format ! ( "`{}FileType::is_file()` only {} regular files" , lint_unary, verb) ;
3293+ let help_msg = format ! ( "use `{}FileType::is_dir()` instead" , help_unary) ;
3294+ span_help_and_lint ( cx, FILETYPE_IS_FILE , span, & lint_msg, & help_msg) ;
3295+ }
0 commit comments