]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/src/main.rs
Rollup merge of #93966 - rkuhn:patch-1, r=tmandry
[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         Self {
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         // Currently, `CLIPPY_TERMINAL_WIDTH` is used only to format "unknown field" error messages.
127         let terminal_width = termize::dimensions().map_or(0, |(w, _)| w);
128
129         cmd.env("RUSTC_WORKSPACE_WRAPPER", Self::path())
130             .env("CLIPPY_ARGS", clippy_args)
131             .env("CLIPPY_TERMINAL_WIDTH", terminal_width.to_string())
132             .arg(self.cargo_subcommand)
133             .args(&self.args);
134
135         cmd
136     }
137 }
138
139 fn process<I>(old_args: I) -> Result<(), i32>
140 where
141     I: Iterator<Item = String>,
142 {
143     let cmd = ClippyCmd::new(old_args);
144
145     let mut cmd = cmd.into_std_cmd();
146
147     let exit_status = cmd
148         .spawn()
149         .expect("could not run cargo")
150         .wait()
151         .expect("failed to wait for cargo?");
152
153     if exit_status.success() {
154         Ok(())
155     } else {
156         Err(exit_status.code().unwrap_or(-1))
157     }
158 }
159
160 #[cfg(test)]
161 mod tests {
162     use super::ClippyCmd;
163
164     #[test]
165     fn fix() {
166         let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
167         let cmd = ClippyCmd::new(args);
168         assert_eq!("fix", cmd.cargo_subcommand);
169         assert!(!cmd.args.iter().any(|arg| arg.ends_with("unstable-options")));
170     }
171
172     #[test]
173     fn fix_implies_no_deps() {
174         let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
175         let cmd = ClippyCmd::new(args);
176         assert!(cmd.clippy_args.iter().any(|arg| arg == "--no-deps"));
177     }
178
179     #[test]
180     fn no_deps_not_duplicated_with_fix() {
181         let args = "cargo clippy --fix -- --no-deps"
182             .split_whitespace()
183             .map(ToString::to_string);
184         let cmd = ClippyCmd::new(args);
185         assert_eq!(cmd.clippy_args.iter().filter(|arg| *arg == "--no-deps").count(), 1);
186     }
187
188     #[test]
189     fn check() {
190         let args = "cargo clippy".split_whitespace().map(ToString::to_string);
191         let cmd = ClippyCmd::new(args);
192         assert_eq!("check", cmd.cargo_subcommand);
193     }
194 }