]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/src/main.rs
Rollup merge of #103104 - SUPERCILEX:sep-ref, r=dtolnay
[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 std::env;
6 use std::path::PathBuf;
7 use std::process::{self, Command};
8
9 const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code.
10
11 Usage:
12     cargo clippy [options] [--] [<opts>...]
13
14 Common options:
15     --no-deps                Run Clippy only on the given crate, without linting the dependencies
16     --fix                    Automatically apply lint suggestions. This flag implies `--no-deps`
17     -h, --help               Print this message
18     -V, --version            Print version info and exit
19     --explain LINT           Print the documentation for a given lint
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 Some(pos) = env::args().position(|a| a == "--explain") {
58         if let Some(mut lint) = env::args().nth(pos + 1) {
59             lint.make_ascii_lowercase();
60             clippy_lints::explain(&lint.strip_prefix("clippy::").unwrap_or(&lint).replace('-', "_"));
61         } else {
62             show_help();
63         }
64         return;
65     }
66
67     if let Err(code) = process(env::args().skip(2)) {
68         process::exit(code);
69     }
70 }
71
72 struct ClippyCmd {
73     cargo_subcommand: &'static str,
74     args: Vec<String>,
75     clippy_args: Vec<String>,
76 }
77
78 impl ClippyCmd {
79     fn new<I>(mut old_args: I) -> Self
80     where
81         I: Iterator<Item = String>,
82     {
83         let mut cargo_subcommand = "check";
84         let mut args = vec![];
85         let mut clippy_args: Vec<String> = vec![];
86
87         for arg in old_args.by_ref() {
88             match arg.as_str() {
89                 "--fix" => {
90                     cargo_subcommand = "fix";
91                     continue;
92                 },
93                 "--no-deps" => {
94                     clippy_args.push("--no-deps".into());
95                     continue;
96                 },
97                 "--" => break,
98                 _ => {},
99             }
100
101             args.push(arg);
102         }
103
104         clippy_args.append(&mut (old_args.collect()));
105         if cargo_subcommand == "fix" && !clippy_args.iter().any(|arg| arg == "--no-deps") {
106             clippy_args.push("--no-deps".into());
107         }
108
109         Self {
110             cargo_subcommand,
111             args,
112             clippy_args,
113         }
114     }
115
116     fn path() -> PathBuf {
117         let mut path = env::current_exe()
118             .expect("current executable path invalid")
119             .with_file_name("clippy-driver");
120
121         if cfg!(windows) {
122             path.set_extension("exe");
123         }
124
125         path
126     }
127
128     fn into_std_cmd(self) -> Command {
129         let mut cmd = Command::new("cargo");
130         let clippy_args: String = self
131             .clippy_args
132             .iter()
133             .map(|arg| format!("{arg}__CLIPPY_HACKERY__"))
134             .collect();
135
136         // Currently, `CLIPPY_TERMINAL_WIDTH` is used only to format "unknown field" error messages.
137         let terminal_width = termize::dimensions().map_or(0, |(w, _)| w);
138
139         cmd.env("RUSTC_WORKSPACE_WRAPPER", Self::path())
140             .env("CLIPPY_ARGS", clippy_args)
141             .env("CLIPPY_TERMINAL_WIDTH", terminal_width.to_string())
142             .arg(self.cargo_subcommand)
143             .args(&self.args);
144
145         cmd
146     }
147 }
148
149 fn process<I>(old_args: I) -> Result<(), i32>
150 where
151     I: Iterator<Item = String>,
152 {
153     let cmd = ClippyCmd::new(old_args);
154
155     let mut cmd = cmd.into_std_cmd();
156
157     let exit_status = cmd
158         .spawn()
159         .expect("could not run cargo")
160         .wait()
161         .expect("failed to wait for cargo?");
162
163     if exit_status.success() {
164         Ok(())
165     } else {
166         Err(exit_status.code().unwrap_or(-1))
167     }
168 }
169
170 #[cfg(test)]
171 mod tests {
172     use super::ClippyCmd;
173
174     #[test]
175     fn fix() {
176         let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
177         let cmd = ClippyCmd::new(args);
178         assert_eq!("fix", cmd.cargo_subcommand);
179         assert!(!cmd.args.iter().any(|arg| arg.ends_with("unstable-options")));
180     }
181
182     #[test]
183     fn fix_implies_no_deps() {
184         let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
185         let cmd = ClippyCmd::new(args);
186         assert!(cmd.clippy_args.iter().any(|arg| arg == "--no-deps"));
187     }
188
189     #[test]
190     fn no_deps_not_duplicated_with_fix() {
191         let args = "cargo clippy --fix -- --no-deps"
192             .split_whitespace()
193             .map(ToString::to_string);
194         let cmd = ClippyCmd::new(args);
195         assert_eq!(cmd.clippy_args.iter().filter(|arg| *arg == "--no-deps").count(), 1);
196     }
197
198     #[test]
199     fn check() {
200         let args = "cargo clippy".split_whitespace().map(ToString::to_string);
201         let cmd = ClippyCmd::new(args);
202         assert_eq!("check", cmd.cargo_subcommand);
203     }
204 }