]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/src/main.rs
Rollup merge of #89317 - JulianKnodt:precise_errors, r=BoxyUwU
[rust.git] / src / tools / clippy / src / main.rs
1 #![cfg_attr(feature = "deny-warnings", deny(warnings))]
2 // warn on lints, that are included in `rust-lang/rust`s bootstrap
3 #![warn(rust_2018_idioms, unused_lifetimes)]
4
5 use rustc_tools_util::VersionInfo;
6 use std::env;
7 use std::path::PathBuf;
8 use std::process::{self, Command};
9
10 const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code.
11
12 Usage:
13     cargo clippy [options] [--] [<opts>...]
14
15 Common options:
16     --no-deps                Run Clippy only on the given crate, without linting the dependencies
17     --fix                    Automatically apply lint suggestions. This flag implies `--no-deps`
18     -h, --help               Print this message
19     -V, --version            Print version info and exit
20
21 Other options are the same as `cargo check`.
22
23 To allow or deny a lint from the command line you can use `cargo clippy --`
24 with:
25
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
30
31 You can use tool lints to allow or deny lints from your code, eg.:
32
33     #[allow(clippy::needless_lifetimes)]
34 "#;
35
36 fn show_help() {
37     println!("{}", CARGO_CLIPPY_HELP);
38 }
39
40 fn show_version() {
41     let version_info = rustc_tools_util::get_version_info!();
42     println!("{}", version_info);
43 }
44
45 pub fn main() {
46     // Check for version and help flags even when invoked as 'cargo-clippy'
47     if env::args().any(|a| a == "--help" || a == "-h") {
48         show_help();
49         return;
50     }
51
52     if env::args().any(|a| a == "--version" || a == "-V") {
53         show_version();
54         return;
55     }
56
57     if let Err(code) = process(env::args().skip(2)) {
58         process::exit(code);
59     }
60 }
61
62 struct ClippyCmd {
63     cargo_subcommand: &'static str,
64     args: Vec<String>,
65     clippy_args: Vec<String>,
66 }
67
68 impl ClippyCmd {
69     fn new<I>(mut old_args: I) -> Self
70     where
71         I: Iterator<Item = String>,
72     {
73         let mut cargo_subcommand = "check";
74         let mut args = vec![];
75         let mut clippy_args: Vec<String> = vec![];
76
77         for arg in old_args.by_ref() {
78             match arg.as_str() {
79                 "--fix" => {
80                     cargo_subcommand = "fix";
81                     continue;
82                 },
83                 "--no-deps" => {
84                     clippy_args.push("--no-deps".into());
85                     continue;
86                 },
87                 "--" => break,
88                 _ => {},
89             }
90
91             args.push(arg);
92         }
93
94         clippy_args.append(&mut (old_args.collect()));
95         if cargo_subcommand == "fix" && !clippy_args.iter().any(|arg| arg == "--no-deps") {
96             clippy_args.push("--no-deps".into());
97         }
98
99         ClippyCmd {
100             cargo_subcommand,
101             args,
102             clippy_args,
103         }
104     }
105
106     fn path() -> PathBuf {
107         let mut path = env::current_exe()
108             .expect("current executable path invalid")
109             .with_file_name("clippy-driver");
110
111         if cfg!(windows) {
112             path.set_extension("exe");
113         }
114
115         path
116     }
117
118     fn into_std_cmd(self) -> Command {
119         let mut cmd = Command::new("cargo");
120         let clippy_args: String = self
121             .clippy_args
122             .iter()
123             .map(|arg| format!("{}__CLIPPY_HACKERY__", arg))
124             .collect();
125
126         cmd.env("RUSTC_WORKSPACE_WRAPPER", Self::path())
127             .env("CLIPPY_ARGS", clippy_args)
128             .arg(self.cargo_subcommand)
129             .args(&self.args);
130
131         cmd
132     }
133 }
134
135 fn process<I>(old_args: I) -> Result<(), i32>
136 where
137     I: Iterator<Item = String>,
138 {
139     let cmd = ClippyCmd::new(old_args);
140
141     let mut cmd = cmd.into_std_cmd();
142
143     let exit_status = cmd
144         .spawn()
145         .expect("could not run cargo")
146         .wait()
147         .expect("failed to wait for cargo?");
148
149     if exit_status.success() {
150         Ok(())
151     } else {
152         Err(exit_status.code().unwrap_or(-1))
153     }
154 }
155
156 #[cfg(test)]
157 mod tests {
158     use super::ClippyCmd;
159
160     #[test]
161     fn fix() {
162         let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
163         let cmd = ClippyCmd::new(args);
164         assert_eq!("fix", cmd.cargo_subcommand);
165         assert!(!cmd.args.iter().any(|arg| arg.ends_with("unstable-options")));
166     }
167
168     #[test]
169     fn fix_implies_no_deps() {
170         let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
171         let cmd = ClippyCmd::new(args);
172         assert!(cmd.clippy_args.iter().any(|arg| arg == "--no-deps"));
173     }
174
175     #[test]
176     fn no_deps_not_duplicated_with_fix() {
177         let args = "cargo clippy --fix -- --no-deps"
178             .split_whitespace()
179             .map(ToString::to_string);
180         let cmd = ClippyCmd::new(args);
181         assert_eq!(cmd.clippy_args.iter().filter(|arg| *arg == "--no-deps").count(), 1);
182     }
183
184     #[test]
185     fn check() {
186         let args = "cargo clippy".split_whitespace().map(ToString::to_string);
187         let cmd = ClippyCmd::new(args);
188         assert_eq!("check", cmd.cargo_subcommand);
189     }
190 }