diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 843aa6a40f09..199d6f5ecd6a 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -578,6 +578,8 @@ define_Conf! { /// Use the Cognitive Complexity lint instead. #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)] cyclomatic_complexity_threshold: u64 = 25, + /// Lints to disable in tests. + disabled_in_tests: Vec = Vec::new(), /// The list of disallowed macros, written as fully qualified paths. /// /// **Fields:** diff --git a/src/driver.rs b/src/driver.rs index 102ca3fa69f7..3271488fbf52 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -8,7 +8,9 @@ // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) +extern crate rustc_data_structures; extern crate rustc_driver; +extern crate rustc_errors; extern crate rustc_interface; extern crate rustc_session; extern crate rustc_span; @@ -18,15 +20,19 @@ extern crate rustc_span; #[cfg(feature = "jemalloc")] extern crate tikv_jemalloc_sys as jemalloc_sys; +use clippy_config::Conf; use clippy_utils::sym; use declare_clippy_lint::LintListBuilder; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::DiagInner; use rustc_interface::interface; -use rustc_session::EarlyDiagCtxt; use rustc_session::config::ErrorOutputType; use rustc_session::parse::ParseSess; +use rustc_session::{EarlyDiagCtxt, Session}; use rustc_span::symbol::Symbol; use std::env; +use std::fmt::Write; use std::fs::read_to_string; use std::path::Path; use std::process::exit; @@ -156,11 +162,14 @@ impl rustc_driver::Callbacks for ClippyCallbacks { (previous)(sess, lint_store); } + let conf = clippy_config::Conf::read(sess, &conf_path); + let disabled = build_disabled_set(sess, conf); + set_post_expect_filter(sess, disabled); + let mut list_builder = LintListBuilder::default(); list_builder.insert(clippy_lints::declared_lints::LINTS); list_builder.register(lint_store); - let conf = clippy_config::Conf::read(sess, &conf_path); clippy_lints::register_lint_passes(lint_store, conf); #[cfg(feature = "internal")] @@ -181,6 +190,46 @@ impl rustc_driver::Callbacks for ClippyCallbacks { } } +fn build_disabled_set(sess: &Session, conf: &'static Conf) -> FxHashSet<&'static str> { + if !sess.is_test_crate() { + return FxHashSet::default(); + } + let disabled = conf + .disabled_in_tests + .iter() + .map(String::as_str) + .collect::>(); + let declared_lint_names = clippy_lints::declared_lints::LINTS + .iter() + .map(|lint| lint.name_lower()) + .collect::>(); + #[allow(rustc::potential_query_instability)] + for &name in &disabled { + if !declared_lint_names.contains(name) { + let mut msg = format!("unknown lint `{name}`"); + let replaced = name.replace('-', "_"); + if declared_lint_names.contains(&replaced) { + writeln!(msg, ". Did you mean `{replaced}`?").unwrap(); + } + sess.dcx().warn(msg); + } + } + disabled +} + +#[allow(clippy::implicit_hasher)] +pub fn set_post_expect_filter(sess: &Session, disabled: FxHashSet<&'static str>) { + if !disabled.is_empty() { + sess.dcx().set_post_expect_filter(Box::new(move |diag: &DiagInner| { + diag.lint_name().is_some_and(|name| { + let name = name.strip_prefix("clippy::").unwrap_or(name); + disabled.contains(name) + }) + })); + } +} + +#[allow(clippy::ignored_unit_patterns)] fn display_help() { println!("{}", help_message()); } diff --git a/tests/ui-toml/disabled_in_tests_expect/clippy.toml b/tests/ui-toml/disabled_in_tests_expect/clippy.toml new file mode 100644 index 000000000000..68de82c02565 --- /dev/null +++ b/tests/ui-toml/disabled_in_tests_expect/clippy.toml @@ -0,0 +1 @@ +disabled-in-tests = ["unwrap_used"] diff --git a/tests/ui-toml/disabled_in_tests_expect/expect.rs b/tests/ui-toml/disabled_in_tests_expect/expect.rs new file mode 100644 index 000000000000..f4cc9c2fcae8 --- /dev/null +++ b/tests/ui-toml/disabled_in_tests_expect/expect.rs @@ -0,0 +1,17 @@ +//@check-pass +//@compile-flags: --test + +#![allow(clippy::unnecessary_literal_unwrap)] +#![warn(clippy::unwrap_used)] + +fn main() {} + +fn foo(opt: Option) { + #[expect(clippy::unwrap_used)] + opt.unwrap(); +} + +#[test] +fn unwrap_some() { + Some(()).unwrap(); +} diff --git a/tests/ui-toml/disabled_in_tests_expect/expect.stderr b/tests/ui-toml/disabled_in_tests_expect/expect.stderr new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/ui-toml/disabled_in_tests_typo/clippy.toml b/tests/ui-toml/disabled_in_tests_typo/clippy.toml new file mode 100644 index 000000000000..90599edd07dc --- /dev/null +++ b/tests/ui-toml/disabled_in_tests_typo/clippy.toml @@ -0,0 +1 @@ +disabled-in-tests = ["unwrap-used"] diff --git a/tests/ui-toml/disabled_in_tests_typo/typo.rs b/tests/ui-toml/disabled_in_tests_typo/typo.rs new file mode 100644 index 000000000000..051f75e98fd5 --- /dev/null +++ b/tests/ui-toml/disabled_in_tests_typo/typo.rs @@ -0,0 +1,4 @@ +//@check-pass +//@compile-flags: --test + +fn main() {} diff --git a/tests/ui-toml/disabled_in_tests_typo/typo.stderr b/tests/ui-toml/disabled_in_tests_typo/typo.stderr new file mode 100644 index 000000000000..c57e158f13a8 --- /dev/null +++ b/tests/ui-toml/disabled_in_tests_typo/typo.stderr @@ -0,0 +1,4 @@ +warning: unknown lint `unwrap-used`. Did you mean `unwrap_used`? + +warning: 1 warning emitted + diff --git a/tests/ui-toml/disabled_in_tests_unwrap_used/clippy.toml b/tests/ui-toml/disabled_in_tests_unwrap_used/clippy.toml new file mode 100644 index 000000000000..68de82c02565 --- /dev/null +++ b/tests/ui-toml/disabled_in_tests_unwrap_used/clippy.toml @@ -0,0 +1 @@ +disabled-in-tests = ["unwrap_used"] diff --git a/tests/ui-toml/disabled_in_tests_unwrap_used/unwrap_used.fixed b/tests/ui-toml/disabled_in_tests_unwrap_used/unwrap_used.fixed new file mode 100644 index 000000000000..cf735d56c565 --- /dev/null +++ b/tests/ui-toml/disabled_in_tests_unwrap_used/unwrap_used.fixed @@ -0,0 +1,34 @@ +//@compile-flags: --test + +#![warn(clippy::unwrap_used)] +#![warn(clippy::get_unwrap)] + +fn main() {} + +#[test] +fn test() { + let boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let _ = &boxed_slice[1]; + //~^ get_unwrap +} + +#[cfg(test)] +mod issue9612 { + // should not lint in `#[cfg(test)]` modules + #[test] + fn test_fn() { + let _a: u8 = 2.try_into().unwrap(); + let _a: u8 = 3.try_into().expect(""); + + util(); + } + + #[allow(unconditional_panic)] + fn util() { + let _a: u8 = 4.try_into().unwrap(); + let _a: u8 = 5.try_into().expect(""); + // should still warn + let _ = &Box::new([0])[1]; + //~^ get_unwrap + } +} diff --git a/tests/ui-toml/disabled_in_tests_unwrap_used/unwrap_used.rs b/tests/ui-toml/disabled_in_tests_unwrap_used/unwrap_used.rs new file mode 100644 index 000000000000..9dfffe6cbb67 --- /dev/null +++ b/tests/ui-toml/disabled_in_tests_unwrap_used/unwrap_used.rs @@ -0,0 +1,34 @@ +//@compile-flags: --test + +#![warn(clippy::unwrap_used)] +#![warn(clippy::get_unwrap)] + +fn main() {} + +#[test] +fn test() { + let boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let _ = boxed_slice.get(1).unwrap(); + //~^ get_unwrap +} + +#[cfg(test)] +mod issue9612 { + // should not lint in `#[cfg(test)]` modules + #[test] + fn test_fn() { + let _a: u8 = 2.try_into().unwrap(); + let _a: u8 = 3.try_into().expect(""); + + util(); + } + + #[allow(unconditional_panic)] + fn util() { + let _a: u8 = 4.try_into().unwrap(); + let _a: u8 = 5.try_into().expect(""); + // should still warn + let _ = Box::new([0]).get(1).unwrap(); + //~^ get_unwrap + } +} diff --git a/tests/ui-toml/disabled_in_tests_unwrap_used/unwrap_used.stderr b/tests/ui-toml/disabled_in_tests_unwrap_used/unwrap_used.stderr new file mode 100644 index 000000000000..8c400b1e4e51 --- /dev/null +++ b/tests/ui-toml/disabled_in_tests_unwrap_used/unwrap_used.stderr @@ -0,0 +1,28 @@ +error: called `.get().unwrap()` on a slice + --> tests/ui-toml/disabled_in_tests_unwrap_used/unwrap_used.rs:11:13 + | +LL | let _ = boxed_slice.get(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::get-unwrap` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::get_unwrap)]` +help: using `[]` is clearer and more concise + | +LL - let _ = boxed_slice.get(1).unwrap(); +LL + let _ = &boxed_slice[1]; + | + +error: called `.get().unwrap()` on a slice + --> tests/ui-toml/disabled_in_tests_unwrap_used/unwrap_used.rs:31:17 + | +LL | let _ = Box::new([0]).get(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: using `[]` is clearer and more concise + | +LL - let _ = Box::new([0]).get(1).unwrap(); +LL + let _ = &Box::new([0])[1]; + | + +error: aborting due to 2 previous errors + diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 20aeb4bb8498..118e7986b89c 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -36,6 +36,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect check-private-items cognitive-complexity-threshold const-literal-digits-threshold + disabled-in-tests disallowed-macros disallowed-methods disallowed-names @@ -131,6 +132,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect check-private-items cognitive-complexity-threshold const-literal-digits-threshold + disabled-in-tests disallowed-macros disallowed-methods disallowed-names @@ -226,6 +228,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni check-private-items cognitive-complexity-threshold const-literal-digits-threshold + disabled-in-tests disallowed-macros disallowed-methods disallowed-names