Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Implement --plugin option
  • Loading branch information
smoelius committed Feb 14, 2021
commit 2d4d7fa49f834cfc29d95849890e2d35eb426d32
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ out
/clippy_utils/target
/clippy_workspace_tests/target
/clippy_dev/target
/plugin_example/target
/plugin_example/tests/ui/*/target
/rustc_tools_util/target

# Generated by dogfood
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ path = "src/driver.rs"
# begin automatic update
clippy_lints = { version = "0.1.50", path = "clippy_lints" }
# end automatic update
clippy_utils = { path = "clippy_utils" }
libloading = "0.7"
semver = "0.11"
rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" }
tempfile = { version = "3.1.0", optional = true }
Expand Down
17 changes: 17 additions & 0 deletions plugin_example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "plugin_example"
version = "0.1.0"
authors = ["The Rust Clippy Developers"]
edition = "2018"

[lib]
name = "plugin_example"
crate-type = ["dylib"]

[dependencies]
clippy_utils = { path = "../clippy_utils" }
if_chain = "1.0.1"

[dev-dependencies]
assert_cmd = "1.0.3"
lazy_static = "1.4.0"
78 changes: 78 additions & 0 deletions plugin_example/src/allow_clippy_lints.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use clippy_utils::{declare_clippy_lint, span_lint_and_sugg};
use if_chain::if_chain;
use rustc_ast::{Attribute, NestedMetaItem};
use rustc_errors::Applicability;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
use rustc_span::symbol::SymbolStr;

declare_clippy_lint! {
/// **What it does:** This tongue-in-cheek lint checks for `#[allow(clippy::...)]`.
/// It is based on `blanket_clippy_restriction_lints`:
/// https://rust-lang.github.io/rust-clippy/master/#blanket_clippy_restriction_lints
///
/// **Why is this bad?** It's not really. This is just an example of a Clippy plugin.
///
/// **Known problems:** None.
///
/// **Example:**
/// Bad:
/// ```rust
/// #![allow(clippy::assertions_on_constants)]
/// ```
///
/// Good:
/// ```rust
/// #[deny(clippy::restriction, clippy::style, clippy::pedantic, clippy::complexity, clippy::perf, clippy::cargo, clippy::nursery)]
/// ```
pub ALLOW_CLIPPY_LINTS,
correctness,
"use of `#[allow(clippy::...)]`"
}

declare_lint_pass!(AllowClippyLints => [
ALLOW_CLIPPY_LINTS,
]);

impl<'tcx> LateLintPass<'tcx> for AllowClippyLints {
fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
if let Some(items) = &attr.meta_item_list() {
if let Some(ident) = attr.ident() {
let ident = &*ident.as_str();
if ident == "allow" {
check_clippy_lint_names(cx, &attr, items);
}
}
}
}
}

/// Returns the lint name if it is clippy lint.
fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<SymbolStr> {
if_chain! {
if let Some(meta_item) = lint.meta_item();
if meta_item.path.segments.len() > 1;
if let tool_name = meta_item.path.segments[0].ident;
if tool_name.name == sym::clippy;
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
then {
return Some(lint_name.as_str());
}
}
None
}

fn check_clippy_lint_names(cx: &LateContext<'_>, attr: &Attribute, items: &[NestedMetaItem]) {
if items.iter().find_map(extract_clippy_lint).is_some() {
span_lint_and_sugg(
cx,
ALLOW_CLIPPY_LINTS,
attr.span,
"`allow`ing Clippy lints `deny`s your project of its true potential",
"use",
"#[deny(clippy::restriction, clippy::style, clippy::pedantic, clippy::complexity, clippy::perf, clippy::cargo, clippy::nursery)]".to_string(),
Applicability::MachineApplicable,
);
}
}
20 changes: 20 additions & 0 deletions plugin_example/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#![feature(rustc_private)]

extern crate rustc_ast;
extern crate rustc_errors;
extern crate rustc_hir;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_session;
extern crate rustc_span;

use clippy_utils::conf::Conf;
use rustc_session::Session;

mod allow_clippy_lints;

#[no_mangle]
pub extern "C" fn register_plugins(store: &mut rustc_lint::LintStore, _sess: &Session, _conf: &Conf) {
store.register_lints(&[&allow_clippy_lints::ALLOW_CLIPPY_LINTS]);
store.register_late_pass(|| Box::new(allow_clippy_lints::AllowClippyLints));
}
37 changes: 37 additions & 0 deletions plugin_example/tests/clippy-test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use assert_cmd::prelude::*;
use lazy_static::lazy_static;
use std::fs::{canonicalize, metadata, read_dir, read_to_string};
use std::path::{Path, PathBuf};
use std::process::Command;

lazy_static! {
static ref CLIPPY: PathBuf = canonicalize("../target/debug/cargo-clippy").unwrap();
}

const PLUGIN: &str = "libplugin_example.so";

#[test]
fn clippy_test() {
let src_base = Path::new("tests").join("ui");
let plugin = Path::new("..")
.join("..")
.join("..")
.join("target")
.join("release")
.join(PLUGIN);

for entry in read_dir(src_base).unwrap() {
let path = entry.unwrap().path();

if !metadata(&path).unwrap().is_dir() {
continue;
}

Command::new(&*CLIPPY)
.current_dir(&path)
.args(&["cargo-clippy", "--quiet", "--plugin", &plugin.to_string_lossy()])
.assert()
.stdout(read_to_string(&*(path.to_string_lossy() + ".stdout")).unwrap())
.stderr(read_to_string(&*(path.to_string_lossy() + ".stderr")).unwrap());
}
}
14 changes: 14 additions & 0 deletions plugin_example/tests/ui/allow_clippy_lints.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: `allow`ing Clippy lints `deny`s your project of its true potential
--> src/main.rs:1:1
|
1 | #[allow(clippy::assertions_on_constants)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `#[deny(clippy::restriction, clippy::style, clippy::pedantic, clippy::complexity, clippy::perf, clippy::cargo, clippy::nursery)]`
|
= note: `#[deny(clippy::allow_clippy_lints)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#allow_clippy_lints

error: aborting due to previous error

error: could not compile `allow_clippy_lints`

To learn more, run the command again with --verbose.
Empty file.
7 changes: 7 additions & 0 deletions plugin_example/tests/ui/allow_clippy_lints/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "allow_clippy_lints"
version = "0.1.0"
authors = ["Samuel E. Moelius III <[email protected]>"]
edition = "2018"

[dependencies]
4 changes: 4 additions & 0 deletions plugin_example/tests/ui/allow_clippy_lints/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[allow(clippy::assertions_on_constants)]
fn main() {
assert!(true);
}
95 changes: 80 additions & 15 deletions src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@
extern crate rustc_driver;
extern crate rustc_errors;
extern crate rustc_interface;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_session;

use rustc_interface::interface;
use rustc_middle::ty::TyCtxt;
use rustc_session::{config::ErrorOutputType, early_error, Session};
use rustc_tools_util::VersionInfo;

use clippy_utils::conf::Conf;
use std::borrow::Cow;
use std::env;
use std::lazy::SyncLazy;
Expand Down Expand Up @@ -64,10 +68,57 @@ fn test_arg_value() {
struct DefaultCallbacks;
impl rustc_driver::Callbacks for DefaultCallbacks {}

struct ClippyCallbacks;
type RegisterPluginsFunc = unsafe fn(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf);

/// A Clippy plugin that has been loaded but not necessarily registered.
struct LoadedPlugin {
path: PathBuf,
lib: libloading::Library,
}

impl LoadedPlugin {
fn register_plugins(&self, store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
if let Ok(func) = unsafe { self.lib.get::<RegisterPluginsFunc>(b"register_plugins") } {
unsafe {
func(store, sess, conf);
}
} else {
sess.err(&format!(
"could not find `register_plugins` in `{}`",
self.path.to_string_lossy()
));
}
}
}

struct ClippyCallbacks {
loaded_plugins: Vec<LoadedPlugin>,
}

impl ClippyCallbacks {
// smoelius: Load the libraries when ClippyCallbacks is created and not later (e.g., in `config`)
// to ensure that the libraries live long enough.
fn new(clippy_plugins: Vec<PathBuf>) -> ClippyCallbacks {
let mut loaded_plugins = Vec::new();
for path in clippy_plugins {
unsafe {
let lib = libloading::Library::new(&path).unwrap_or_else(|_| {
early_error(
ErrorOutputType::default(),
&format!("could not load plugin `{}`", path.to_string_lossy()),
);
});
loaded_plugins.push(LoadedPlugin { path, lib });
}
}
ClippyCallbacks { loaded_plugins }
}
}

impl rustc_driver::Callbacks for ClippyCallbacks {
fn config(&mut self, config: &mut interface::Config) {
let previous = config.register_lints.take();
let loaded_plugins = self.loaded_plugins.split_off(0);
config.register_lints = Some(Box::new(move |sess, mut lint_store| {
// technically we're ~guaranteed that this is none but might as well call anything that
// is there already. Certainly it can't hurt.
Expand All @@ -77,6 +128,9 @@ impl rustc_driver::Callbacks for ClippyCallbacks {

let conf = clippy_lints::read_conf(&[], &sess);
clippy_lints::register_plugins(&mut lint_store, &sess, &conf);
for loaded_plugin in &loaded_plugins {
loaded_plugin.register_plugins(&mut lint_store, &sess, &conf);
}
clippy_lints::register_pre_expansion_lints(&mut lint_store);
clippy_lints::register_renamed(&mut lint_store);
}));
Expand Down Expand Up @@ -279,19 +333,30 @@ pub fn main() {
};

let mut no_deps = false;
let clippy_args = env::var("CLIPPY_ARGS")
.unwrap_or_default()
.split("__CLIPPY_HACKERY__")
.filter_map(|s| match s {
"" => None,
"--no-deps" => {
no_deps = true;
None
},
_ => Some(s.to_string()),
})
.chain(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()])
.collect::<Vec<String>>();
let mut clippy_args = Vec::new();
let mut clippy_plugins = Vec::new();
if let Ok(args) = env::var("CLIPPY_ARGS") {
let mut iter = args.split("__CLIPPY_HACKERY__");
while let Some(s) = iter.next() {
match s {
"" => {},
"--no-deps" => {
no_deps = true;
},
"--plugin" => {
// smoelius: The paths should already have bee canonicalized in `ClippyCmd::new`.
let path = iter.next().expect("missing argument to `--plugin`");
clippy_plugins.push(PathBuf::from(path));
},
_ => {
clippy_args.push(s.to_string());
},
}
}
}

clippy_args.push("--cfg".into());
clippy_args.push(r#"feature="cargo-clippy""#.into());

// We enable Clippy if one of the following conditions is met
// - IF Clippy is run on its test suite OR
Expand All @@ -307,7 +372,7 @@ pub fn main() {
args.extend(clippy_args);
}

let mut clippy = ClippyCallbacks;
let mut clippy = ClippyCallbacks::new(clippy_plugins);
let mut default = DefaultCallbacks;
let callbacks: &mut (dyn rustc_driver::Callbacks + Send) =
if clippy_enabled { &mut clippy } else { &mut default };
Expand Down
15 changes: 13 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use rustc_tools_util::VersionInfo;
use std::env;
use std::ffi::OsString;
use std::fs::canonicalize;
use std::path::PathBuf;
use std::process::{self, Command};

Expand All @@ -15,6 +16,7 @@ Usage:

Common options:
-h, --help Print this message
--plugin PLUGIN Load lints in PLUGIN
-V, --version Print version info and exit

Other options are the same as `cargo check`.
Expand Down Expand Up @@ -73,13 +75,22 @@ impl ClippyCmd {
let mut cargo_subcommand = "check";
let mut unstable_options = false;
let mut args = vec![];
let mut clippy_args = vec![];

for arg in old_args.by_ref() {
while let Some(arg) = old_args.next() {
match arg.as_str() {
"--fix" => {
cargo_subcommand = "fix";
continue;
},
"--plugin" => {
let plugin = old_args.next().expect("missing argument to `--plugin`");
// smoelius: canonicalize in case clippy-driver is run from a different directory.
let path = canonicalize(&plugin).unwrap_or_else(|_| panic!("could not find `{}`", plugin));
clippy_args.push(arg);
clippy_args.push(path.to_string_lossy().to_string());
continue;
},
"--" => break,
// Cover -Zunstable-options and -Z unstable-options
s if s.ends_with("unstable-options") => unstable_options = true,
Expand All @@ -99,7 +110,7 @@ impl ClippyCmd {
args.insert(0, "+nightly".to_string());
}

let mut clippy_args: Vec<String> = old_args.collect();
clippy_args.extend(old_args);
if cargo_subcommand == "fix" && !clippy_args.iter().any(|arg| arg == "--no-deps") {
clippy_args.push("--no-deps".into());
}
Expand Down