Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement better help for clippy-driver #4175

Merged
merged 11 commits into from Jun 13, 2019
22 changes: 22 additions & 0 deletions clippy_dev/src/main.rs
Expand Up @@ -87,9 +87,31 @@ fn print_lints() {

fn update_lints(update_mode: &UpdateMode) {
let lint_list: Vec<Lint> = gather_all().collect();

let usable_lints: Vec<Lint> = Lint::usable_lints(lint_list.clone().into_iter()).collect();
let lint_count = usable_lints.len();

let mut sorted_usable_lints = usable_lints.clone();
sorted_usable_lints.sort_by_key(|lint| lint.name.clone());

std::fs::write(
"../src/lintlist/mod.rs",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the fact review:

Shouldn't this also be done with the replace_region_in_file function and then only update the content in the ALL_LINTS array? When a PR adds a lint, this file needs to get updated like README.md, ... If the update is done with the replace_region_in_file function (like below), travis will fail if it's not updated. cc @phansch

&format!(
"\
//! This file is managed by `util/dev update_lints`. Do not edit.

pub mod lint;
pub use lint::Level;
pub use lint::Lint;
pub use lint::LINT_LEVELS;

pub const ALL_LINTS: [Lint; {}] = {:#?};\n",
sorted_usable_lints.len(),
sorted_usable_lints
),
)
.expect("can write to file");

let mut file_change = replace_region_in_file(
"../README.md",
r#"\[There are \d+ lints included in this crate!\]\(https://rust-lang.github.io/rust-clippy/master/index.html\)"#,
Expand Down
167 changes: 163 additions & 4 deletions src/driver.rs
Expand Up @@ -15,6 +15,8 @@ use rustc_tools_util::*;
use std::path::Path;
use std::process::{exit, Command};

mod lintlist;

/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If
/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`.
fn arg_value<'a>(
Expand Down Expand Up @@ -108,6 +110,142 @@ impl rustc_driver::Callbacks for ClippyCallbacks {
}
}

#[allow(clippy::find_map, clippy::filter_map)]
fn describe_lints() {
use lintlist::*;
use std::collections::HashSet;

println!(
"
Available lint options:
-W <foo> Warn about <foo>
-A <foo> Allow <foo>
-D <foo> Deny <foo>
-F <foo> Forbid <foo> (deny <foo> and all attempts to override)

"
);

let lint_level = |lint: &Lint| {
LINT_LEVELS
.iter()
.find(|level_mapping| level_mapping.0 == lint.group)
.map(|(_, level)| match level {
Level::Allow => "allow",
Level::Warn => "warn",
Level::Deny => "deny",
})
.unwrap()
};

let mut lints: Vec<_> = ALL_LINTS.iter().collect();
// The sort doesn't case-fold but it's doubtful we care.
lints.sort_by_cached_key(|x: &&Lint| (lint_level(x), x.name));

let max_lint_name_len = lints
.iter()
.map(|lint| lint.name.len())
.map(|len| len + "clippy::".len())
.max()
.unwrap_or(0);

let padded = |x: &str| {
let mut s = " ".repeat(max_lint_name_len - x.chars().count());
s.push_str(x);
s
};

let scoped = |x: &str| format!("clippy::{}", x);

let lint_groups: HashSet<_> = lints.iter().map(|lint| lint.group).collect();

println!("Lint checks provided by clippy:\n");
println!(" {} {:7.7} meaning", padded("name"), "default");
println!(" {} {:7.7} -------", padded("----"), "-------");

let print_lints = |lints: &[&Lint]| {
for lint in lints {
let name = lint.name.replace("_", "-");
println!(
" {} {:7.7} {}",
padded(&scoped(&name)),
lint_level(lint),
lint.desc
);
}
println!("\n");
};

print_lints(&lints);

let max_group_name_len = std::cmp::max(
"clippy::all".len(),
lint_groups
.iter()
.map(|group| group.len())
.map(|len| len + "clippy::".len())
.max()
.unwrap_or(0),
);

let padded_group = |x: &str| {
let mut s = " ".repeat(max_group_name_len - x.chars().count());
s.push_str(x);
s
};

println!("Lint groups provided by clippy:\n");
println!(" {} sub-lints", padded_group("name"));
println!(" {} ---------", padded_group("----"));
println!(" {} the set of all clippy lints", padded_group("clippy::all"));

let print_lint_groups = || {
for group in lint_groups {
let name = group.to_lowercase().replace("_", "-");
let desc = lints
.iter()
.filter(|&lint| lint.group == group)
.map(|lint| lint.name)
.map(|name| name.replace("_", "-"))
.collect::<Vec<String>>()
.join(", ");
println!(" {} {}", padded_group(&scoped(&name)), desc);
}
println!("\n");
};

print_lint_groups();
}

fn display_help() {
println!(
"\
Checks a package to catch common mistakes and improve your Rust code.

Usage:
cargo clippy [options] [--] [<opts>...]

Common options:
-h, --help Print this message
-V, --version Print version info and exit

Other options are the same as `cargo check`.

To allow or deny a lint from the command line you can use `cargo clippy --`
with:

-W --warn OPT Set lint warnings
-A --allow OPT Set lint allowed
-D --deny OPT Set lint denied
-F --forbid OPT Set lint forbidden

You can use tool lints to allow or deny lints from your code, eg.:

#[allow(clippy::needless_lifetimes)]
"
);
}

pub fn main() {
rustc_driver::init_rustc_env_logger();
exit(
Expand Down Expand Up @@ -153,13 +291,34 @@ pub fn main() {

// Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument.
// We're invoking the compiler programmatically, so we ignore this/
if orig_args.len() <= 1 {
std::process::exit(1);
}
if Path::new(&orig_args[1]).file_stem() == Some("rustc".as_ref()) {
let wrapper_mode = Path::new(&orig_args[1]).file_stem() == Some("rustc".as_ref());

if wrapper_mode {
// we still want to be able to invoke it normally though
orig_args.remove(1);
}

if !wrapper_mode && std::env::args().any(|a| a == "--help" || a == "-h") {
display_help();
exit(0);
}

let should_describe_lints = || {
let args: Vec<_> = std::env::args().collect();
args.windows(2).any(|args| {
args[1] == "help"
&& match args[0].as_str() {
"-W" | "-A" | "-D" | "-F" => true,
_ => false,
}
})
};

if !wrapper_mode && should_describe_lints() {
describe_lints();
exit(0);
}

// this conditional check for the --sysroot flag is there so users can call
// `clippy_driver` directly
// without having to pass --sysroot or anything
Expand Down
27 changes: 27 additions & 0 deletions src/lintlist/lint.rs
@@ -0,0 +1,27 @@
/// Lint data parsed from the Clippy source code.
#[derive(Clone, PartialEq, Debug)]
pub struct Lint {
pub name: &'static str,
pub group: &'static str,
pub desc: &'static str,
pub deprecation: Option<&'static str>,
pub module: &'static str,
}

#[derive(PartialOrd, PartialEq, Ord, Eq)]
pub enum Level {
Allow,
Warn,
Deny,
}

pub const LINT_LEVELS: [(&str, Level); 8] = [
("correctness", Level::Deny),
("style", Level::Warn),
("complexity", Level::Warn),
("perf", Level::Warn),
("restriction", Level::Allow),
("pedantic", Level::Allow),
("nursery", Level::Allow),
("cargo", Level::Allow),
];