1 #![feature(bool_to_option)]
2 #![feature(command_access)]
3 #![cfg_attr(feature = "deny-warnings", deny(warnings))]
4 // warn on lints, that are included in `rust-lang/rust`s bootstrap
5 #![warn(rust_2018_idioms, unused_lifetimes)]
7 use rustc_tools_util::VersionInfo;
9 use std::ffi::OsString;
10 use std::path::PathBuf;
11 use std::process::{self, Command};
13 const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code.
16 cargo clippy [options] [--] [<opts>...]
19 -h, --help Print this message
20 -V, --version Print version info and exit
22 Other options are the same as `cargo check`.
24 To allow or deny a lint from the command line you can use `cargo clippy --`
27 -W --warn OPT Set lint warnings
28 -A --allow OPT Set lint allowed
29 -D --deny OPT Set lint denied
30 -F --forbid OPT Set lint forbidden
32 You can use tool lints to allow or deny lints from your code, eg.:
34 #[allow(clippy::needless_lifetimes)]
38 println!("{}", CARGO_CLIPPY_HELP);
42 let version_info = rustc_tools_util::get_version_info!();
43 println!("{}", version_info);
47 // Check for version and help flags even when invoked as 'cargo-clippy'
48 if env::args().any(|a| a == "--help" || a == "-h") {
53 if env::args().any(|a| a == "--version" || a == "-V") {
58 if let Err(code) = process(env::args().skip(2)) {
64 unstable_options: bool,
65 cargo_subcommand: &'static str,
67 clippy_args: Option<String>,
71 fn new<I>(mut old_args: I) -> Self
73 I: Iterator<Item = String>,
75 let mut cargo_subcommand = "check";
76 let mut unstable_options = false;
77 let mut args = vec![];
79 for arg in old_args.by_ref() {
82 cargo_subcommand = "fix";
86 // Cover -Zunstable-options and -Z unstable-options
87 s if s.ends_with("unstable-options") => unstable_options = true,
94 if cargo_subcommand == "fix" && !unstable_options {
95 panic!("Usage of `--fix` requires `-Z unstable-options`");
98 // Run the dogfood tests directly on nightly cargo. This is required due
99 // to a bug in rustup.rs when running cargo on custom toolchains. See issue #3118.
100 if env::var_os("CLIPPY_DOGFOOD").is_some() && cfg!(windows) {
101 args.insert(0, "+nightly".to_string());
104 let mut clippy_args = old_args.collect::<Vec<String>>().join(" ");
105 if cargo_subcommand == "fix" && !clippy_args.contains("--no-deps") {
106 clippy_args = format!("{} --no-deps", clippy_args);
109 let has_args = !clippy_args.is_empty();
114 clippy_args: has_args.then_some(clippy_args),
118 fn path_env(&self) -> &'static str {
119 if self.unstable_options {
120 "RUSTC_WORKSPACE_WRAPPER"
126 fn path() -> PathBuf {
127 let mut path = env::current_exe()
128 .expect("current executable path invalid")
129 .with_file_name("clippy-driver");
132 path.set_extension("exe");
138 fn target_dir() -> Option<(&'static str, OsString)> {
139 env::var_os("CLIPPY_DOGFOOD")
141 env::var_os("CARGO_MANIFEST_DIR").map_or_else(
142 || std::ffi::OsString::from("clippy_dogfood"),
144 std::path::PathBuf::from(d)
151 .map(|p| ("CARGO_TARGET_DIR", p))
154 fn into_std_cmd(self, rustflags: Option<String>) -> Command {
155 let mut cmd = Command::new("cargo");
157 cmd.env(self.path_env(), Self::path())
158 .envs(ClippyCmd::target_dir())
159 .arg(self.cargo_subcommand)
162 // HACK: pass Clippy args to the driver *also* through RUSTFLAGS.
163 // This guarantees that new builds will be triggered when Clippy flags change.
164 if let Some(clippy_args) = self.clippy_args {
167 rustflags.map_or(clippy_args.clone(), |flags| format!("{} {}", clippy_args, flags)),
169 cmd.env("CLIPPY_ARGS", clippy_args);
176 fn process<I>(old_args: I) -> Result<(), i32>
178 I: Iterator<Item = String>,
180 let cmd = ClippyCmd::new(old_args);
182 let mut cmd = cmd.into_std_cmd(env::var("RUSTFLAGS").ok());
184 let exit_status = cmd
186 .expect("could not run cargo")
188 .expect("failed to wait for cargo?");
190 if exit_status.success() {
193 Err(exit_status.code().unwrap_or(-1))
199 use super::ClippyCmd;
204 fn fix_without_unstable() {
205 let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
206 let _ = ClippyCmd::new(args);
211 let args = "cargo clippy --fix -Zunstable-options"
213 .map(ToString::to_string);
214 let cmd = ClippyCmd::new(args);
216 assert_eq!("fix", cmd.cargo_subcommand);
217 assert_eq!("RUSTC_WORKSPACE_WRAPPER", cmd.path_env());
218 assert!(cmd.args.iter().any(|arg| arg.ends_with("unstable-options")));
222 fn fix_implies_no_deps() {
223 let args = "cargo clippy --fix -Zunstable-options"
225 .map(ToString::to_string);
226 let cmd = ClippyCmd::new(args);
228 assert!(cmd.clippy_args.unwrap().contains("--no-deps"));
232 fn no_deps_not_duplicated_with_fix() {
233 let args = "cargo clippy --fix -Zunstable-options -- --no-deps"
235 .map(ToString::to_string);
236 let cmd = ClippyCmd::new(args);
238 assert_eq!(1, cmd.clippy_args.unwrap().matches("--no-deps").count());
243 let args = "cargo clippy".split_whitespace().map(ToString::to_string);
244 let cmd = ClippyCmd::new(args);
246 assert_eq!("check", cmd.cargo_subcommand);
247 assert_eq!("RUSTC_WRAPPER", cmd.path_env());
251 fn check_unstable() {
252 let args = "cargo clippy -Zunstable-options"
254 .map(ToString::to_string);
255 let cmd = ClippyCmd::new(args);
257 assert_eq!("check", cmd.cargo_subcommand);
258 assert_eq!("RUSTC_WORKSPACE_WRAPPER", cmd.path_env());
262 fn clippy_args_into_rustflags() {
263 let args = "cargo clippy -- -W clippy::as_conversions"
265 .map(ToString::to_string);
266 let cmd = ClippyCmd::new(args);
268 let rustflags = None;
269 let cmd = cmd.into_std_cmd(rustflags);
273 .any(|(key, val)| key == "RUSTFLAGS" && val == Some(OsStr::new("-W clippy::as_conversions"))));
277 fn clippy_args_respect_existing_rustflags() {
278 let args = "cargo clippy -- -D clippy::await_holding_lock"
280 .map(ToString::to_string);
281 let cmd = ClippyCmd::new(args);
283 let rustflags = Some(r#"--cfg feature="some_feat""#.into());
284 let cmd = cmd.into_std_cmd(rustflags);
286 assert!(cmd.get_envs().any(|(key, val)| key == "RUSTFLAGS"
287 && val == Some(OsStr::new(r#"-D clippy::await_holding_lock --cfg feature="some_feat""#))));
291 fn no_env_change_if_no_clippy_args() {
292 let args = "cargo clippy".split_whitespace().map(ToString::to_string);
293 let cmd = ClippyCmd::new(args);
295 let rustflags = Some(r#"--cfg feature="some_feat""#.into());
296 let cmd = cmd.into_std_cmd(rustflags);
300 .any(|(key, _)| key == "RUSTFLAGS" || key == "CLIPPY_ARGS"));
304 fn no_env_change_if_no_clippy_args_nor_rustflags() {
305 let args = "cargo clippy".split_whitespace().map(ToString::to_string);
306 let cmd = ClippyCmd::new(args);
308 let rustflags = None;
309 let cmd = cmd.into_std_cmd(rustflags);
313 .any(|(key, _)| key == "RUSTFLAGS" || key == "CLIPPY_ARGS"))