2 #![feature(box_syntax)]
3 #![feature(rustc_private)]
5 #![allow(unknown_lints, missing_docs_in_private_items)]
7 extern crate clippy_lints;
10 extern crate rustc_driver;
11 extern crate rustc_errors;
12 extern crate rustc_plugin;
15 use rustc_driver::{driver, CompilerCalls, RustcDefaultCalls, Compilation};
16 use rustc::session::{config, Session, CompileIncomplete};
17 use rustc::session::config::{Input, ErrorOutputType};
18 use std::collections::HashMap;
19 use std::path::PathBuf;
20 use std::process::{self, Command};
22 use std::io::{self, Write};
24 extern crate cargo_metadata;
26 struct ClippyCompilerCalls {
27 default: RustcDefaultCalls,
31 impl ClippyCompilerCalls {
32 fn new(run_lints: bool) -> Self {
34 default: RustcDefaultCalls,
40 impl<'a> CompilerCalls<'a> for ClippyCompilerCalls {
43 matches: &getopts::Matches,
44 sopts: &config::Options,
45 cfg: &ast::CrateConfig,
46 descriptions: &rustc_errors::registry::Registry,
47 output: ErrorOutputType,
49 self.default.early_callback(
59 matches: &getopts::Matches,
60 sopts: &config::Options,
61 cfg: &ast::CrateConfig,
62 odir: &Option<PathBuf>,
63 ofile: &Option<PathBuf>,
64 descriptions: &rustc_errors::registry::Registry,
65 ) -> Option<(Input, Option<PathBuf>)> {
66 self.default.no_input(
77 matches: &getopts::Matches,
80 odir: &Option<PathBuf>,
81 ofile: &Option<PathBuf>,
83 self.default.late_callback(
91 fn build_controller(&mut self, sess: &Session, matches: &getopts::Matches) -> driver::CompileController<'a> {
92 let mut control = self.default.build_controller(sess, matches);
95 let old = std::mem::replace(&mut control.after_parse.callback, box |_| {});
96 control.after_parse.callback = Box::new(move |state| {
98 let mut registry = rustc_plugin::registry::Registry::new(
104 "at this compilation stage \
105 the krate must be parsed",
109 registry.args_hidden = Some(Vec::new());
110 clippy_lints::register_plugins(&mut registry);
112 let rustc_plugin::registry::Registry {
120 let sess = &state.session;
121 let mut ls = sess.lint_store.borrow_mut();
122 for pass in early_lint_passes {
123 ls.register_early_pass(Some(sess), true, pass);
125 for pass in late_lint_passes {
126 ls.register_late_pass(Some(sess), true, pass);
129 for (name, to) in lint_groups {
130 ls.register_group(Some(sess), true, name, to);
133 sess.plugin_llvm_passes.borrow_mut().extend(llvm_passes);
134 sess.plugin_attributes.borrow_mut().extend(attributes);
146 const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code.
149 cargo clippy [options] [--] [<opts>...]
152 -h, --help Print this message
153 --features Features to compile for the package
154 -V, --version Print version info and exit
156 Other options are the same as `cargo rustc`.
158 To allow or deny a lint from the command line you can use `cargo clippy --`
161 -W --warn OPT Set lint warnings
162 -A --allow OPT Set lint allowed
163 -D --deny OPT Set lint denied
164 -F --forbid OPT Set lint forbidden
166 The feature `cargo-clippy` is automatically defined for convenience. You can use
167 it to allow or deny lints from the code, eg.:
169 #[cfg_attr(feature = "cargo-clippy", allow(needless_lifetimes))]
172 #[allow(print_stdout)]
174 println!("{}", CARGO_CLIPPY_HELP);
177 #[allow(print_stdout)]
179 println!("{}", env!("CARGO_PKG_VERSION"));
185 if env::var("CLIPPY_DOGFOOD").map(|_| true).unwrap_or(false) {
189 // Check for version and help flags even when invoked as 'cargo-clippy'
190 if std::env::args().any(|a| a == "--help" || a == "-h") {
194 if std::env::args().any(|a| a == "--version" || a == "-V") {
199 if let Some("clippy") = std::env::args().nth(1).as_ref().map(AsRef::as_ref) {
200 // this arm is executed on the initial call to `cargo clippy`
202 let manifest_path_arg = std::env::args().skip(2).find(|val| {
203 val.starts_with("--manifest-path=")
207 if let Ok(metadata) = cargo_metadata::metadata(manifest_path_arg.as_ref().map(AsRef::as_ref)) {
210 let _ = io::stderr().write_fmt(format_args!("error: Could not obtain cargo metadata.\n"));
214 let manifest_path = manifest_path_arg.map(|arg| {
215 Path::new(&arg["--manifest-path=".len()..])
217 .expect("manifest path could not be canonicalized")
220 let package_index = {
221 if let Some(manifest_path) = manifest_path {
222 metadata.packages.iter().position(|package| {
223 let package_manifest_path = Path::new(&package.manifest_path).canonicalize().expect(
224 "package manifest path could not be canonicalized",
226 package_manifest_path == manifest_path
229 let package_manifest_paths: HashMap<_, _> = metadata
233 .map(|(i, package)| {
234 let package_manifest_path = Path::new(&package.manifest_path)
236 .expect("could not find parent directory of package manifest")
238 .expect("package directory cannot be canonicalized");
239 (package_manifest_path, i)
243 let current_dir = std::env::current_dir()
244 .expect("could not read current directory")
246 .expect("current directory cannot be canonicalized");
248 let mut current_path: &Path = ¤t_dir;
250 // This gets the most-recent parent (the one that takes the fewest `cd ..`s to
253 if let Some(&package_index) = package_manifest_paths.get(current_path) {
254 break Some(package_index);
256 // We'll never reach the filesystem root, because to get to this point in the
258 // the call to `cargo_metadata::metadata` must have succeeded. So it's okay to
259 // unwrap the current path's parent.
260 current_path = current_path.parent().unwrap_or_else(|| {
261 panic!("could not find parent of path {}", current_path.display())
266 }.expect("could not find matching package");
268 let package = metadata.packages.remove(package_index);
269 for target in package.targets {
270 let args = std::env::args().skip(2);
271 if let Some(first) = target.kind.get(0) {
272 if target.kind.len() > 1 || first.ends_with("lib") {
273 if let Err(code) = process(std::iter::once("--lib".to_owned()).chain(args)) {
274 std::process::exit(code);
276 } else if ["bin", "example", "test", "bench"].contains(&&**first) {
277 if let Err(code) = process(
278 vec![format!("--{}", first), target.name]
283 std::process::exit(code);
287 panic!("badly formatted cargo metadata: target::kind is an empty array");
291 // this arm is executed when cargo-clippy runs `cargo rustc` with the `RUSTC`
292 // env var set to itself
294 let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
295 let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
296 let sys_root = if let (Some(home), Some(toolchain)) = (home, toolchain) {
297 format!("{}/toolchains/{}", home, toolchain)
299 option_env!("SYSROOT")
300 .map(|s| s.to_owned())
302 Command::new("rustc")
307 .and_then(|out| String::from_utf8(out.stdout).ok())
308 .map(|s| s.trim().to_owned())
311 "need to specify SYSROOT env var during clippy compilation, or use rustup or multirust",
315 rustc_driver::in_rustc_thread(|| {
316 // this conditional check for the --sysroot flag is there so users can call
317 // `cargo-clippy` directly
318 // without having to pass --sysroot or anything
319 let mut args: Vec<String> = if env::args().any(|s| s == "--sysroot") {
320 env::args().collect()
323 .chain(Some("--sysroot".to_owned()))
324 .chain(Some(sys_root))
328 // this check ensures that dependencies are built but not linted and the final
330 // linted but not built
331 let clippy_enabled = env::args().any(|s| s == "--emit=metadata");
334 args.extend_from_slice(&["--cfg".to_owned(), r#"feature="cargo-clippy""#.to_owned()]);
337 let mut ccc = ClippyCompilerCalls::new(clippy_enabled);
338 let (result, _) = rustc_driver::run_compiler(&args, &mut ccc, None, None);
339 if let Err(CompileIncomplete::Errored(_)) = result {
340 std::process::exit(1);
342 }).expect("rustc_thread failed");
346 fn process<I>(old_args: I) -> Result<(), i32>
348 I: Iterator<Item = String>,
351 let mut args = vec!["rustc".to_owned()];
353 let mut found_dashes = false;
354 for arg in old_args {
355 found_dashes |= arg == "--";
359 args.push("--".to_owned());
361 args.push("--emit=metadata".to_owned());
362 args.push("--cfg".to_owned());
363 args.push(r#"feature="cargo-clippy""#.to_owned());
365 let path = std::env::current_exe().expect("current executable path invalid");
366 let exit_status = std::process::Command::new("cargo")
370 .expect("could not run cargo")
372 .expect("failed to wait for cargo?");
374 if exit_status.success() {
377 Err(exit_status.code().unwrap_or(-1))