Skip to content

Commit

Permalink
Generate new lints easily
Browse files Browse the repository at this point in the history
- Add option in clippy_dev to automatically generate boilerplate
  code for adding new lints
  • Loading branch information
bradsherman committed Jan 7, 2020
1 parent 62ff639 commit c137e23
Showing 1 changed file with 138 additions and 0 deletions.
138 changes: 138 additions & 0 deletions clippy_dev/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

use clap::{App, Arg, SubCommand};
use clippy_dev::*;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::prelude::*;
use std::io::ErrorKind;
use std::path::PathBuf;

mod fmt;
mod stderr_length_check;
Expand Down Expand Up @@ -51,6 +56,26 @@ fn main() {
.help("Checks that util/dev update_lints has been run. Used on CI."),
),
)
.subcommand(
SubCommand::with_name("new_lint")
.about("Create new lint and run util/dev update_lints")
.arg(
Arg::with_name("type")
.long("type")
.help("Create early or late lint")
.takes_value(true)
.possible_values(&["early", "late"])
.required(true),
)
.arg(
Arg::with_name("name")
.short("n")
.long("name")
.help("Name of the new lint in snake case, ex: fn_too_long")
.takes_value(true)
.required(true),
),
)
.arg(
Arg::with_name("limit-stderr-length")
.long("limit-stderr-length")
Expand All @@ -75,10 +100,111 @@ fn main() {
update_lints(&UpdateMode::Change);
}
},
("new_lint", Some(matches)) => {
create_new_lint(matches.value_of("type"), matches.value_of("name"));
},
_ => {},
}
}

fn project_root() -> Result<PathBuf, io::Error> {
let current_dir = std::env::current_dir()?;
for path in current_dir.ancestors() {
let result = std::fs::read_to_string(path.join("Cargo.toml"));
if let Err(err) = &result {
if err.kind() == io::ErrorKind::NotFound {
continue;
}
}

let content = result?;
if content.contains("[package]\nname = \"clippy\"") {
return Ok(path.to_path_buf());
}
}
Err(io::Error::new(ErrorKind::Other, "Unable to find project root"))
}

fn open_files(lint_name: &str) -> Result<(File, File), io::Error> {
let project_root = project_root()?;

let test_file_path = project_root.join(format!("tests/ui/{}.rs", lint_name));
let test_file = OpenOptions::new().write(true).create_new(true).open(test_file_path)?;

let lint_file_path = project_root.join(format!("clippy_lints/src/{}.rs", lint_name));
let lint_file = OpenOptions::new().write(true).create_new(true).open(lint_file_path)?;

Ok((test_file, lint_file))
}

fn to_camel_case(name: &str) -> String {
return name.split('_')
.map(|s| [&s[0..1].to_uppercase(), &s[1..]].concat())
.collect::<String>();
}

fn create_new_lint(lint_type: Option<&str>, lint_name: Option<&str>) {
let lint_type = lint_type.expect("`type` argument is validated by clap");
let lint_name = lint_name.expect("`name` argument is validated by clap");

match open_files(lint_name) {
Ok((mut test_file, mut lint_file)) => {
let pass_type = match lint_type {
"early" => "EarlyLintPass",
"late" => "LateLintPass",
_ => {
eprintln!("`pass_type` should only ever be `early` or `late`!");
return;
}
};

let camel_case_name = to_camel_case(lint_name);

test_file
.write_all(
format!(
"#![warn(clippy::{})]
fn main() {{
// test code goes here
}}
",
lint_name
)
.as_bytes(),
)
.unwrap();

lint_file
.write_all(
format!(
"use rustc::lint::{{LintArray, LintPass, {type}}};
use rustc::declare_lint_pass;
use rustc_session::declare_tool_lint;
declare_clippy_lint! {{
pub {name_upper},
nursery,
\"default lint description\"
}}
declare_lint_pass!({name_camel} => [{name_upper}]);
impl {type} for {name_camel} {{}}
",
type=pass_type,
name_upper=lint_name.to_uppercase(),
name_camel=camel_case_name
)
.as_bytes(),
)
.unwrap();
update_lints(&UpdateMode::Change);
},
Err(e) => eprintln!("Unable to create lint: {}", e),
}
}

fn print_lints() {
let lint_list = gather_all();
let usable_lints: Vec<Lint> = Lint::usable_lints(lint_list).collect();
Expand Down Expand Up @@ -232,3 +358,15 @@ fn update_lints(update_mode: &UpdateMode) {
std::process::exit(1);
}
}


#[test]
fn test_camel_case() {
let s = "a_lint";
let s2 = to_camel_case(s);
assert_eq!(s2, "ALint");

let name = "a_really_long_new_lint";
let name2 = to_camel_case(name);
assert_eq!(name2, "AReallyLongNewLint")
}

0 comments on commit c137e23

Please sign in to comment.