From 654154d8e7e770dc7328024efb5451a7503d5d6d Mon Sep 17 00:00:00 2001
From: Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>
Date: Wed, 17 Feb 2016 18:16:29 +0100
Subject: [PATCH] `cargo clippy` subcommand

---
 .travis.yml      |   3 +-
 Cargo.toml       |   6 ++
 README.md        |  30 +++++++++-
 src/lib.rs       | 144 +++++++++++++++++++++++++++++++++++++++++++++--
 tests/dogfood.rs |  16 ++++--
 5 files changed, 185 insertions(+), 14 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 012d6045cd2..b204bb0b2f5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,8 +18,9 @@ script:
  - remark -f README.md > /dev/null
  - python util/update_lints.py -c
  - cargo build --features debugging
- - rm -rf target/ Cargo.lock
  - cargo test --features debugging
+ - SYSROOT=~/rust cargo install
+ - cargo clippy --lib -- -D clippy
 
 after_success:
 # only test regex_macros if it compiles
diff --git a/Cargo.toml b/Cargo.toml
index cd6b2834c56..470d2dd7096 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,12 @@ keywords = ["clippy", "lint", "plugin"]
 [lib]
 name = "clippy"
 plugin = true
+test = false
+
+[[bin]]
+name = "cargo-clippy"
+path = "src/lib.rs"
+test = false
 
 [dependencies]
 regex-syntax = "0.3.0"
diff --git a/README.md b/README.md
index 8f3519c95c4..ee234432c63 100644
--- a/README.md
+++ b/README.md
@@ -173,6 +173,8 @@ More to come, please [file an issue](https://github.com/Manishearth/rust-clippy/
 
 ## Usage
 
+### As a Compiler Plugin
+
 Compiler plugins are highly unstable and will only work with a nightly Rust for now.
 Since stable Rust is backwards compatible, you should be able to compile
 your stable programs with nightly Rust with clippy plugged in to circumvent
@@ -217,8 +219,28 @@ src/main.rs:8:5: 11:6 help: Try
 if let Some(y) = x { println!("{:?}", y) }
 ```
 
-An alternate way to use clippy is by compiling and using [`cargo clippy`](https://github.com/arcnmx/cargo-clippy),
-a custom cargo subcommand that runs clippy on a given project.
+### As a cargo subcommand (`cargo clippy`)
+
+An alternate way to use clippy is by installing clippy through cargo as a cargo
+subcommand.
+
+```terminal
+cargo install clippy
+```
+
+Now you can run clippy by invoking `cargo clippy`, or
+`multirust run nightly cargo clippy` directly from a directory that is usually
+compiled with stable.
+
+In case you are not using multirust, you need to set the environment flag
+`SYSROOT` during installation so clippy knows where to find `librustc` and
+similar crates.
+
+```terminal
+SYSROOT=/path/to/rustc/sysroot cargo install clippy
+```
+
+### Configuring clippy
 
 You can add options  to `allow`/`warn`/`deny`:
 
@@ -234,6 +256,8 @@ You can add options  to `allow`/`warn`/`deny`:
 
 Note: `deny` produces errors instead of warnings
 
+### Running clippy from the command line without installing
+
 To have cargo compile your crate with clippy without needing `#![plugin(clippy)]`
 in your code, you can use:
 
@@ -244,6 +268,8 @@ cargo rustc -- -L /path/to/clippy_so -Z extra-plugins=clippy
 *[Note](https://github.com/Manishearth/rust-clippy/wiki#a-word-of-warning):*
 Be sure that clippy was compiled with the same version of rustc that cargo invokes here!
 
+### Optional dependency
+
 If you want to make clippy an optional dependency, you can do the following:
 
 In your `Cargo.toml`:
diff --git a/src/lib.rs b/src/lib.rs
index e5d374a560f..5dd473c8d09 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,11 +9,145 @@
 #![allow(indexing_slicing, shadow_reuse, unknown_lints)]
 #![allow(float_arithmetic, integer_arithmetic)]
 
-// this only exists to allow the "dogfood" integration test to work
-#[allow(dead_code)]
-#[allow(print_stdout)]
-fn main() {
-    println!("What are you doing? Don't run clippy as an executable");
+extern crate rustc_driver;
+extern crate getopts;
+
+use rustc_driver::{driver, CompilerCalls, RustcDefaultCalls, Compilation};
+use rustc::session::{config, Session};
+use rustc::session::config::{Input, ErrorOutputType};
+use syntax::diagnostics;
+use std::path::PathBuf;
+
+struct ClippyCompilerCalls(RustcDefaultCalls);
+
+impl std::default::Default for ClippyCompilerCalls {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl ClippyCompilerCalls {
+    fn new() -> Self {
+        ClippyCompilerCalls(RustcDefaultCalls)
+    }
+}
+
+impl<'a> CompilerCalls<'a> for ClippyCompilerCalls {
+    fn early_callback(&mut self,
+                      matches: &getopts::Matches,
+                      sopts: &config::Options,
+                      descriptions: &diagnostics::registry::Registry,
+                      output: ErrorOutputType)
+                      -> Compilation {
+        self.0.early_callback(matches, sopts, descriptions, output)
+    }
+    fn no_input(&mut self,
+                matches: &getopts::Matches,
+                sopts: &config::Options,
+                odir: &Option<PathBuf>,
+                ofile: &Option<PathBuf>,
+                descriptions: &diagnostics::registry::Registry)
+                -> Option<(Input, Option<PathBuf>)> {
+        self.0.no_input(matches, sopts, odir, ofile, descriptions)
+    }
+    fn late_callback(&mut self,
+                     matches: &getopts::Matches,
+                     sess: &Session,
+                     input: &Input,
+                     odir: &Option<PathBuf>,
+                     ofile: &Option<PathBuf>)
+                     -> Compilation {
+        self.0.late_callback(matches, sess, input, odir, ofile)
+    }
+    fn build_controller(&mut self, sess: &Session, matches: &getopts::Matches) -> driver::CompileController<'a> {
+        let mut control = self.0.build_controller(sess, matches);
+
+        let old = std::mem::replace(&mut control.after_parse.callback, box |_| {});
+        control.after_parse.callback = Box::new(move |state| {
+            {
+                let mut registry = rustc_plugin::registry::Registry::new(state.session, state.krate.as_ref().expect("at this compilation stage the krate must be parsed"));
+                registry.args_hidden = Some(Vec::new());
+                plugin_registrar(&mut registry);
+
+                let rustc_plugin::registry::Registry { early_lint_passes, late_lint_passes, lint_groups, llvm_passes, attributes, mir_passes, .. } = registry;
+                let sess = &state.session;
+                let mut ls = sess.lint_store.borrow_mut();
+                for pass in early_lint_passes {
+                    ls.register_early_pass(Some(sess), true, pass);
+                }
+                for pass in late_lint_passes {
+                    ls.register_late_pass(Some(sess), true, pass);
+                }
+
+                for (name, to) in lint_groups {
+                    ls.register_group(Some(sess), true, name, to);
+                }
+
+                sess.plugin_llvm_passes.borrow_mut().extend(llvm_passes);
+                sess.mir_passes.borrow_mut().extend(mir_passes);
+                sess.plugin_attributes.borrow_mut().extend(attributes);
+            }
+            old(state);
+        });
+
+        control
+    }
+}
+
+use std::path::Path;
+
+pub fn main() {
+    use std::env;
+
+    if env::var("CLIPPY_DOGFOOD").map(|_| true).unwrap_or(false) {
+        return;
+    }
+
+    let dep_path = env::current_dir().expect("current dir is not readable").join("target").join("debug").join("deps");
+    let sys_root = match (option_env!("MULTIRUST_HOME"), option_env!("MULTIRUST_TOOLCHAIN")) {
+        (Some(home), Some(toolchain)) => format!("{}/toolchains/{}", home, toolchain),
+        _ => option_env!("SYSROOT").expect("need to specify SYSROOT env var during clippy compilation or use multirust").to_owned(),
+    };
+
+    if let Some("clippy") = std::env::args().nth(1).as_ref().map(AsRef::as_ref) {
+        let args = wrap_args(std::env::args().skip(2), dep_path, sys_root);
+        let path = std::env::current_exe().expect("current executable path invalid");
+        let run = std::process::Command::new("cargo")
+            .args(&args)
+            .env("RUSTC", path)
+            .spawn().expect("could not run cargo")
+            .wait().expect("failed to wait for cargo?")
+            .success();
+        assert!(run, "cargo rustc failed");
+    } else {
+        let args: Vec<String> = if env::args().any(|s| s == "--sysroot") {
+            env::args().collect()
+        } else {
+            env::args().chain(Some("--sysroot".to_owned())).chain(Some(sys_root)).collect()
+        };
+        rustc_driver::run_compiler(&args, &mut ClippyCompilerCalls::new());
+    }
+}
+
+fn wrap_args<P, I>(old_args: I, dep_path: P, sysroot: String) -> Vec<String>
+    where P: AsRef<Path>, I: Iterator<Item=String> {
+
+    let mut args = vec!["rustc".to_owned()];
+
+    let mut found_dashes = false;
+    for arg in old_args {
+        found_dashes |= arg == "--";
+        args.push(arg);
+    }
+    if !found_dashes {
+        args.push("--".to_owned());
+    }
+    args.push("-L".to_owned());
+    args.push(dep_path.as_ref().to_string_lossy().into_owned());
+    args.push(String::from("--sysroot"));
+    args.push(sysroot);
+    args.push("-Zno-trans".to_owned());
+    args
 }
 
 #[macro_use]
diff --git a/tests/dogfood.rs b/tests/dogfood.rs
index b5ae813ae51..d050b4fc5ba 100644
--- a/tests/dogfood.rs
+++ b/tests/dogfood.rs
@@ -1,9 +1,11 @@
-#![feature(test)]
+#![feature(test, plugin)]
+#![plugin(clippy)]
+#![deny(clippy, clippy_pedantic)]
 
 extern crate compiletest_rs as compiletest;
 extern crate test;
 
-use std::env::var;
+use std::env::{var, set_var};
 use std::path::PathBuf;
 use test::TestPaths;
 
@@ -11,15 +13,14 @@ use test::TestPaths;
 fn dogfood() {
     let mut config = compiletest::default_config();
 
-    let cfg_mode = "run-pass".parse().ok().expect("Invalid mode");
+    let cfg_mode = "run-pass".parse().expect("Invalid mode");
     let mut s = String::new();
     s.push_str(" -L target/debug/");
     s.push_str(" -L target/debug/deps");
     s.push_str(" -Zextra-plugins=clippy -Ltarget_recur/debug -Dclippy_pedantic -Dclippy");
     config.target_rustcflags = Some(s);
-    if let Ok(name) = var::<&str>("TESTNAME") {
-        let s : String = name.to_owned();
-        config.filter = Some(s)
+    if let Ok(name) = var("TESTNAME") {
+        config.filter = Some(name.to_owned())
     }
 
     config.mode = cfg_mode;
@@ -29,5 +30,8 @@ fn dogfood() {
         file: PathBuf::from("src/lib.rs"),
         relative_dir: PathBuf::new(),
     };
+
+    set_var("CLIPPY_DOGFOOD", "tastes like chicken");
+
     compiletest::runtest::run(config, &paths);
 }