1 #![feature(bool_to_option)]
2 #![cfg_attr(feature = "deny-warnings", deny(warnings))]
3 // warn on lints, that are included in `rust-lang/rust`s bootstrap
4 #![warn(rust_2018_idioms, unused_lifetimes)]
6 use rustc_tools_util::VersionInfo;
8 use std::ffi::OsString;
9 use std::path::PathBuf;
10 use std::process::{self, Command};
12 const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code.
15 cargo clippy [options] [--] [<opts>...]
18 -h, --help Print this message
19 -V, --version Print version info and exit
21 Other options are the same as `cargo check`.
23 To allow or deny a lint from the command line you can use `cargo clippy --`
26 -W --warn OPT Set lint warnings
27 -A --allow OPT Set lint allowed
28 -D --deny OPT Set lint denied
29 -F --forbid OPT Set lint forbidden
31 You can use tool lints to allow or deny lints from your code, eg.:
33 #[allow(clippy::needless_lifetimes)]
37 println!("{}", CARGO_CLIPPY_HELP);
41 let version_info = rustc_tools_util::get_version_info!();
42 println!("{}", version_info);
46 // Check for version and help flags even when invoked as 'cargo-clippy'
47 if env::args().any(|a| a == "--help" || a == "-h") {
52 if env::args().any(|a| a == "--version" || a == "-V") {
57 if let Err(code) = process(env::args().skip(2)) {
63 unstable_options: bool,
64 cargo_subcommand: &'static str,
66 rustflags: Option<String>,
67 clippy_args: Option<String>,
71 fn new<I>(mut old_args: I, rustflags: Option<String>) -> 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();
115 .then(|| rustflags.map_or_else(|| clippy_args.clone(), |flags| format!("{} {}", clippy_args, flags))),
116 clippy_args: has_args.then_some(clippy_args),
120 fn path_env(&self) -> &'static str {
121 if self.unstable_options {
122 "RUSTC_WORKSPACE_WRAPPER"
128 fn path() -> PathBuf {
129 let mut path = env::current_exe()
130 .expect("current executable path invalid")
131 .with_file_name("clippy-driver");
134 path.set_extension("exe");
140 fn target_dir() -> Option<(&'static str, OsString)> {
141 env::var_os("CLIPPY_DOGFOOD")
143 env::var_os("CARGO_MANIFEST_DIR").map_or_else(
144 || std::ffi::OsString::from("clippy_dogfood"),
146 std::path::PathBuf::from(d)
153 .map(|p| ("CARGO_TARGET_DIR", p))
156 fn into_std_cmd(self) -> Command {
157 let mut cmd = Command::new("cargo");
159 cmd.env(self.path_env(), Self::path())
160 .envs(ClippyCmd::target_dir())
161 .arg(self.cargo_subcommand)
164 // HACK: pass Clippy args to the driver *also* through RUSTFLAGS.
165 // This guarantees that new builds will be triggered when Clippy flags change.
166 if let (Some(clippy_args), Some(rustflags)) = (self.clippy_args, self.rustflags) {
167 cmd.env("CLIPPY_ARGS", clippy_args);
168 cmd.env("RUSTFLAGS", rustflags);
175 fn process<I>(old_args: I) -> Result<(), i32>
177 I: Iterator<Item = String>,
179 let cmd = ClippyCmd::new(old_args, env::var("RUSTFLAGS").ok());
181 let mut cmd = cmd.into_std_cmd();
183 let exit_status = cmd
185 .expect("could not run cargo")
187 .expect("failed to wait for cargo?");
189 if exit_status.success() {
192 Err(exit_status.code().unwrap_or(-1))
198 use super::ClippyCmd;
202 fn fix_without_unstable() {
203 let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
204 let _ = ClippyCmd::new(args, None);
209 let args = "cargo clippy --fix -Zunstable-options"
211 .map(ToString::to_string);
212 let cmd = ClippyCmd::new(args, None);
214 assert_eq!("fix", cmd.cargo_subcommand);
215 assert_eq!("RUSTC_WORKSPACE_WRAPPER", cmd.path_env());
216 assert!(cmd.args.iter().any(|arg| arg.ends_with("unstable-options")));
220 fn fix_implies_no_deps() {
221 let args = "cargo clippy --fix -Zunstable-options"
223 .map(ToString::to_string);
224 let cmd = ClippyCmd::new(args, None);
226 assert!(cmd.clippy_args.unwrap().contains("--no-deps"));
230 fn no_deps_not_duplicated_with_fix() {
231 let args = "cargo clippy --fix -Zunstable-options -- --no-deps"
233 .map(ToString::to_string);
234 let cmd = ClippyCmd::new(args, None);
236 assert_eq!(1, cmd.clippy_args.unwrap().matches("--no-deps").count());
241 let args = "cargo clippy".split_whitespace().map(ToString::to_string);
242 let cmd = ClippyCmd::new(args, None);
244 assert_eq!("check", cmd.cargo_subcommand);
245 assert_eq!("RUSTC_WRAPPER", cmd.path_env());
249 fn check_unstable() {
250 let args = "cargo clippy -Zunstable-options"
252 .map(ToString::to_string);
253 let cmd = ClippyCmd::new(args, None);
255 assert_eq!("check", cmd.cargo_subcommand);
256 assert_eq!("RUSTC_WORKSPACE_WRAPPER", cmd.path_env());
260 fn clippy_args_into_rustflags() {
261 let args = "cargo clippy -- -W clippy::as_conversions"
263 .map(ToString::to_string);
264 let rustflags = None;
265 let cmd = ClippyCmd::new(args, rustflags);
267 assert_eq!("-W clippy::as_conversions", cmd.rustflags.unwrap());
271 fn clippy_args_respect_existing_rustflags() {
272 let args = "cargo clippy -- -D clippy::await_holding_lock"
274 .map(ToString::to_string);
275 let rustflags = Some(r#"--cfg feature="some_feat""#.into());
276 let cmd = ClippyCmd::new(args, rustflags);
279 r#"-D clippy::await_holding_lock --cfg feature="some_feat""#,
280 cmd.rustflags.unwrap()
285 fn no_env_change_if_no_clippy_args() {
286 let args = "cargo clippy".split_whitespace().map(ToString::to_string);
287 let rustflags = Some(r#"--cfg feature="some_feat""#.into());
288 let cmd = ClippyCmd::new(args, rustflags);
290 assert!(cmd.clippy_args.is_none());
291 assert!(cmd.rustflags.is_none());
295 fn no_env_change_if_no_clippy_args_nor_rustflags() {
296 let args = "cargo clippy".split_whitespace().map(ToString::to_string);
297 let rustflags = None;
298 let cmd = ClippyCmd::new(args, rustflags);
300 assert!(cmd.clippy_args.is_none());
301 assert!(cmd.rustflags.is_none());