diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index b5f68817c..b833fb551 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1275,6 +1275,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { ["add"] | ["offset"] | ["sub"] | ["wrapping_offset"] | ["wrapping_add"] | ["wrapping_sub"] => { check_pointer_offset(cx, expr, arg_lists[0]) }, + ["is_file", ..] => lint_filetype_is_file(cx, expr, arg_lists[0]), _ => {}, } @@ -3259,3 +3260,26 @@ fn check_pointer_offset(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[ } } } + +fn lint_filetype_is_file(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let ty = cx.tables.expr_ty(&args[0]); + + if !match_type(cx, ty, &paths::FILE_TYPE) { + return; + } + + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let hir::ExprKind::Unary(op, _) = parent.kind; + if op == hir::UnNot; + then { + let lint_msg = "`!FileType::is_file()` does not deny all readable file types"; + let help_msg = "use `FileType::is_dir()` instead"; + span_help_and_lint(cx, FILETYPE_IS_FILE, expr.span, lint_msg, help_msg); + } else { + let lint_msg = "`FileType::is_file()` does not cover all readable file types"; + let help_msg = "use `!FileType::is_dir()` instead"; + span_help_and_lint(cx, FILETYPE_IS_FILE, expr.span, lint_msg, help_msg); + } + } +} diff --git a/clippy_lints/src/utils/paths.rs b/clippy_lints/src/utils/paths.rs index 9338daf05..4f8fe02df 100644 --- a/clippy_lints/src/utils/paths.rs +++ b/clippy_lints/src/utils/paths.rs @@ -28,6 +28,7 @@ pub const DROP_TRAIT: [&str; 4] = ["core", "ops", "drop", "Drop"]; pub const DURATION: [&str; 3] = ["core", "time", "Duration"]; pub const EARLY_CONTEXT: [&str; 4] = ["rustc", "lint", "context", "EarlyContext"]; pub const EXIT: [&str; 3] = ["std", "process", "exit"]; +pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"]; pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"]; pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"]; pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"]; diff --git a/tests/ui/filetype_is_file.stderr b/tests/ui/filetype_is_file.stderr new file mode 100644 index 000000000..298cb5ad0 --- /dev/null +++ b/tests/ui/filetype_is_file.stderr @@ -0,0 +1,27 @@ +error: `FileType::is_file()` does not cover all readable file types + --> $DIR/filetype_is_file.rs:8:8 + | +LL | if fs::metadata("foo.txt")?.file_type().is_file() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::filetype-is-file` implied by `-D warnings` + = help: use `!FileType::is_dir()` instead + +error: `!FileType::is_file()` does not deny all readable file types + --> $DIR/filetype_is_file.rs:13:9 + | +LL | if !fs::metadata("foo.txt")?.file_type().is_file() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `FileType::is_dir()` instead + +error: `FileType::is_file()` does not cover all readable file types + --> $DIR/filetype_is_file.rs:18:9 + | +LL | if !fs::metadata("foo.txt")?.file_type().is_file().bitor(true) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `!FileType::is_dir()` instead + +error: aborting due to 3 previous errors +