]> git.lizzy.rs Git - rust.git/blob - src/driver.rs
146e3fe2d08f03dc0c76036902a11966196c270a
[rust.git] / src / driver.rs
1 #![cfg_attr(feature = "deny-warnings", deny(warnings))]
2 #![feature(rustc_private)]
3
4 // FIXME: switch to something more ergonomic here, once available.
5 // (Currently there is no way to opt into sysroot crates without `extern crate`.)
6 #[allow(unused_extern_crates)]
7 extern crate rustc_driver;
8 #[allow(unused_extern_crates)]
9 extern crate rustc_interface;
10 #[allow(unused_extern_crates)]
11 extern crate rustc_plugin;
12
13 use rustc_interface::interface;
14 use rustc_tools_util::*;
15
16 use std::path::{Path, PathBuf};
17 use std::process::{exit, Command};
18
19 mod lintlist;
20
21 /// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If
22 /// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`.
23 fn arg_value<'a>(
24     args: impl IntoIterator<Item = &'a String>,
25     find_arg: &str,
26     pred: impl Fn(&str) -> bool,
27 ) -> Option<&'a str> {
28     let mut args = args.into_iter().map(String::as_str);
29
30     while let Some(arg) = args.next() {
31         let arg: Vec<_> = arg.splitn(2, '=').collect();
32         if arg.get(0) != Some(&find_arg) {
33             continue;
34         }
35
36         let value = arg.get(1).cloned().or_else(|| args.next());
37         if value.as_ref().map_or(false, |p| pred(p)) {
38             return value;
39         }
40     }
41     None
42 }
43
44 #[test]
45 fn test_arg_value() {
46     let args: Vec<_> = ["--bar=bar", "--foobar", "123", "--foo"]
47         .iter()
48         .map(std::string::ToString::to_string)
49         .collect();
50
51     assert_eq!(arg_value(None, "--foobar", |_| true), None);
52     assert_eq!(arg_value(&args, "--bar", |_| false), None);
53     assert_eq!(arg_value(&args, "--bar", |_| true), Some("bar"));
54     assert_eq!(arg_value(&args, "--bar", |p| p == "bar"), Some("bar"));
55     assert_eq!(arg_value(&args, "--bar", |p| p == "foo"), None);
56     assert_eq!(arg_value(&args, "--foobar", |p| p == "foo"), None);
57     assert_eq!(arg_value(&args, "--foobar", |p| p == "123"), Some("123"));
58     assert_eq!(arg_value(&args, "--foo", |_| true), None);
59 }
60
61 #[allow(clippy::too_many_lines)]
62
63 struct ClippyCallbacks;
64
65 impl rustc_driver::Callbacks for ClippyCallbacks {
66     fn after_parsing(&mut self, compiler: &interface::Compiler) -> bool {
67         let sess = compiler.session();
68         let mut registry = rustc_plugin::registry::Registry::new(
69             sess,
70             compiler
71                 .parse()
72                 .expect(
73                     "at this compilation stage \
74                      the crate must be parsed",
75                 )
76                 .peek()
77                 .span,
78         );
79         registry.args_hidden = Some(Vec::new());
80
81         let conf = clippy_lints::read_conf(&registry);
82         clippy_lints::register_plugins(&mut registry, &conf);
83
84         let rustc_plugin::registry::Registry {
85             early_lint_passes,
86             late_lint_passes,
87             lint_groups,
88             llvm_passes,
89             attributes,
90             ..
91         } = registry;
92         let mut ls = sess.lint_store.borrow_mut();
93         for pass in early_lint_passes {
94             ls.register_early_pass(Some(sess), true, false, pass);
95         }
96         for pass in late_lint_passes {
97             ls.register_late_pass(Some(sess), true, false, false, pass);
98         }
99
100         for (name, (to, deprecated_name)) in lint_groups {
101             ls.register_group(Some(sess), true, name, deprecated_name, to);
102         }
103         clippy_lints::register_pre_expansion_lints(sess, &mut ls, &conf);
104         clippy_lints::register_renamed(&mut ls);
105
106         sess.plugin_llvm_passes.borrow_mut().extend(llvm_passes);
107         sess.plugin_attributes.borrow_mut().extend(attributes);
108
109         // Continue execution
110         true
111     }
112 }
113
114 #[allow(clippy::find_map, clippy::filter_map)]
115 fn describe_lints() {
116     use lintlist::*;
117     use std::collections::HashSet;
118
119     println!(
120         "
121 Available lint options:
122     -W <foo>           Warn about <foo>
123     -A <foo>           Allow <foo>
124     -D <foo>           Deny <foo>
125     -F <foo>           Forbid <foo> (deny <foo> and all attempts to override)
126
127 "
128     );
129
130     let lint_level = |lint: &Lint| {
131         LINT_LEVELS
132             .iter()
133             .find(|level_mapping| level_mapping.0 == lint.group)
134             .map(|(_, level)| match level {
135                 Level::Allow => "allow",
136                 Level::Warn => "warn",
137                 Level::Deny => "deny",
138             })
139             .unwrap()
140     };
141
142     let mut lints: Vec<_> = ALL_LINTS.iter().collect();
143     // The sort doesn't case-fold but it's doubtful we care.
144     lints.sort_by_cached_key(|x: &&Lint| (lint_level(x), x.name));
145
146     let max_lint_name_len = lints
147         .iter()
148         .map(|lint| lint.name.len())
149         .map(|len| len + "clippy::".len())
150         .max()
151         .unwrap_or(0);
152
153     let padded = |x: &str| {
154         let mut s = " ".repeat(max_lint_name_len - x.chars().count());
155         s.push_str(x);
156         s
157     };
158
159     let scoped = |x: &str| format!("clippy::{}", x);
160
161     let lint_groups: HashSet<_> = lints.iter().map(|lint| lint.group).collect();
162
163     println!("Lint checks provided by clippy:\n");
164     println!("    {}  {:7.7}  meaning", padded("name"), "default");
165     println!("    {}  {:7.7}  -------", padded("----"), "-------");
166
167     let print_lints = |lints: &[&Lint]| {
168         for lint in lints {
169             let name = lint.name.replace("_", "-");
170             println!(
171                 "    {}  {:7.7}  {}",
172                 padded(&scoped(&name)),
173                 lint_level(lint),
174                 lint.desc
175             );
176         }
177         println!("\n");
178     };
179
180     print_lints(&lints);
181
182     let max_group_name_len = std::cmp::max(
183         "clippy::all".len(),
184         lint_groups
185             .iter()
186             .map(|group| group.len())
187             .map(|len| len + "clippy::".len())
188             .max()
189             .unwrap_or(0),
190     );
191
192     let padded_group = |x: &str| {
193         let mut s = " ".repeat(max_group_name_len - x.chars().count());
194         s.push_str(x);
195         s
196     };
197
198     println!("Lint groups provided by clippy:\n");
199     println!("    {}  sub-lints", padded_group("name"));
200     println!("    {}  ---------", padded_group("----"));
201     println!("    {}  the set of all clippy lints", padded_group("clippy::all"));
202
203     let print_lint_groups = || {
204         for group in lint_groups {
205             let name = group.to_lowercase().replace("_", "-");
206             let desc = lints
207                 .iter()
208                 .filter(|&lint| lint.group == group)
209                 .map(|lint| lint.name)
210                 .map(|name| name.replace("_", "-"))
211                 .collect::<Vec<String>>()
212                 .join(", ");
213             println!("    {}  {}", padded_group(&scoped(&name)), desc);
214         }
215         println!("\n");
216     };
217
218     print_lint_groups();
219 }
220
221 fn display_help() {
222     println!(
223         "\
224 Checks a package to catch common mistakes and improve your Rust code.
225
226 Usage:
227     cargo clippy [options] [--] [<opts>...]
228
229 Common options:
230     -h, --help               Print this message
231     -V, --version            Print version info and exit
232
233 Other options are the same as `cargo check`.
234
235 To allow or deny a lint from the command line you can use `cargo clippy --`
236 with:
237
238     -W --warn OPT       Set lint warnings
239     -A --allow OPT      Set lint allowed
240     -D --deny OPT       Set lint denied
241     -F --forbid OPT     Set lint forbidden
242
243 You can use tool lints to allow or deny lints from your code, eg.:
244
245     #[allow(clippy::needless_lifetimes)]
246 "
247     );
248 }
249
250 pub fn main() {
251     rustc_driver::init_rustc_env_logger();
252     exit(
253         rustc_driver::report_ices_to_stderr_if_any(move || {
254             use std::env;
255
256             if std::env::args().any(|a| a == "--version" || a == "-V") {
257                 let version_info = rustc_tools_util::get_version_info!();
258                 println!("{}", version_info);
259                 exit(0);
260             }
261
262             let mut orig_args: Vec<String> = env::args().collect();
263
264             // Get the sysroot, looking from most specific to this invocation to the least:
265             // - command line
266             // - runtime environment
267             //    - SYSROOT
268             //    - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN
269             // - sysroot from rustc in the path
270             // - compile-time environment
271             let sys_root_arg = arg_value(&orig_args, "--sysroot", |_| true);
272             let have_sys_root_arg = sys_root_arg.is_some();
273             let sys_root = sys_root_arg
274                 .map(PathBuf::from)
275                 .or_else(|| std::env::var("SYSROOT").ok().map(PathBuf::from))
276                 .or_else(|| {
277                     let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
278                     let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
279                     home.and_then(|home| {
280                         toolchain.map(|toolchain| {
281                             let mut path = PathBuf::from(home);
282                             path.push("toolchains");
283                             path.push(toolchain);
284                             path
285                         })
286                     })
287                 })
288                 .or_else(|| {
289                     Command::new("rustc")
290                         .arg("--print")
291                         .arg("sysroot")
292                         .output()
293                         .ok()
294                         .and_then(|out| String::from_utf8(out.stdout).ok())
295                         .map(|s| PathBuf::from(s.trim()))
296                 })
297                 .or_else(|| option_env!("SYSROOT").map(PathBuf::from))
298                 .map(|pb| pb.to_string_lossy().to_string())
299                 .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust");
300
301             // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument.
302             // We're invoking the compiler programmatically, so we ignore this/
303             let wrapper_mode = Path::new(&orig_args[1]).file_stem() == Some("rustc".as_ref());
304
305             if wrapper_mode {
306                 // we still want to be able to invoke it normally though
307                 orig_args.remove(1);
308             }
309
310             if !wrapper_mode && std::env::args().any(|a| a == "--help" || a == "-h") {
311                 display_help();
312                 exit(0);
313             }
314
315             let should_describe_lints = || {
316                 let args: Vec<_> = std::env::args().collect();
317                 args.windows(2).any(|args| {
318                     args[1] == "help"
319                         && match args[0].as_str() {
320                             "-W" | "-A" | "-D" | "-F" => true,
321                             _ => false,
322                         }
323                 })
324             };
325
326             if !wrapper_mode && should_describe_lints() {
327                 describe_lints();
328                 exit(0);
329             }
330
331             // this conditional check for the --sysroot flag is there so users can call
332             // `clippy_driver` directly
333             // without having to pass --sysroot or anything
334             let mut args: Vec<String> = if have_sys_root_arg {
335                 orig_args.clone()
336             } else {
337                 orig_args
338                     .clone()
339                     .into_iter()
340                     .chain(Some("--sysroot".to_owned()))
341                     .chain(Some(sys_root))
342                     .collect()
343             };
344
345             // this check ensures that dependencies are built but not linted and the final
346             // crate is
347             // linted but not built
348             let clippy_enabled = env::var("CLIPPY_TESTS").ok().map_or(false, |val| val == "true")
349                 || arg_value(&orig_args, "--emit", |val| val.split(',').any(|e| e == "metadata")).is_some();
350
351             if clippy_enabled {
352                 args.extend_from_slice(&["--cfg".to_owned(), r#"feature="cargo-clippy""#.to_owned()]);
353                 if let Ok(extra_args) = env::var("CLIPPY_ARGS") {
354                     args.extend(extra_args.split("__CLIPPY_HACKERY__").filter_map(|s| {
355                         if s.is_empty() {
356                             None
357                         } else {
358                             Some(s.to_string())
359                         }
360                     }));
361                 }
362             }
363
364             let mut clippy = ClippyCallbacks;
365             let mut default = rustc_driver::DefaultCallbacks;
366             let callbacks: &mut (dyn rustc_driver::Callbacks + Send) =
367                 if clippy_enabled { &mut clippy } else { &mut default };
368             let args = args;
369             rustc_driver::run_compiler(&args, callbacks, None, None)
370         })
371         .and_then(|result| result)
372         .is_err() as i32,
373     )
374 }