Skip to content

Commit c137e23

Browse files
committed
Generate new lints easily
- Add option in clippy_dev to automatically generate boilerplate code for adding new lints
1 parent 62ff639 commit c137e23

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

clippy_dev/src/main.rs

+138
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
use clap::{App, Arg, SubCommand};
44
use clippy_dev::*;
5+
use std::fs::{File, OpenOptions};
6+
use std::io;
7+
use std::io::prelude::*;
8+
use std::io::ErrorKind;
9+
use std::path::PathBuf;
510

611
mod fmt;
712
mod stderr_length_check;
@@ -51,6 +56,26 @@ fn main() {
5156
.help("Checks that util/dev update_lints has been run. Used on CI."),
5257
),
5358
)
59+
.subcommand(
60+
SubCommand::with_name("new_lint")
61+
.about("Create new lint and run util/dev update_lints")
62+
.arg(
63+
Arg::with_name("type")
64+
.long("type")
65+
.help("Create early or late lint")
66+
.takes_value(true)
67+
.possible_values(&["early", "late"])
68+
.required(true),
69+
)
70+
.arg(
71+
Arg::with_name("name")
72+
.short("n")
73+
.long("name")
74+
.help("Name of the new lint in snake case, ex: fn_too_long")
75+
.takes_value(true)
76+
.required(true),
77+
),
78+
)
5479
.arg(
5580
Arg::with_name("limit-stderr-length")
5681
.long("limit-stderr-length")
@@ -75,10 +100,111 @@ fn main() {
75100
update_lints(&UpdateMode::Change);
76101
}
77102
},
103+
("new_lint", Some(matches)) => {
104+
create_new_lint(matches.value_of("type"), matches.value_of("name"));
105+
},
78106
_ => {},
79107
}
80108
}
81109

110+
fn project_root() -> Result<PathBuf, io::Error> {
111+
let current_dir = std::env::current_dir()?;
112+
for path in current_dir.ancestors() {
113+
let result = std::fs::read_to_string(path.join("Cargo.toml"));
114+
if let Err(err) = &result {
115+
if err.kind() == io::ErrorKind::NotFound {
116+
continue;
117+
}
118+
}
119+
120+
let content = result?;
121+
if content.contains("[package]\nname = \"clippy\"") {
122+
return Ok(path.to_path_buf());
123+
}
124+
}
125+
Err(io::Error::new(ErrorKind::Other, "Unable to find project root"))
126+
}
127+
128+
fn open_files(lint_name: &str) -> Result<(File, File), io::Error> {
129+
let project_root = project_root()?;
130+
131+
let test_file_path = project_root.join(format!("tests/ui/{}.rs", lint_name));
132+
let test_file = OpenOptions::new().write(true).create_new(true).open(test_file_path)?;
133+
134+
let lint_file_path = project_root.join(format!("clippy_lints/src/{}.rs", lint_name));
135+
let lint_file = OpenOptions::new().write(true).create_new(true).open(lint_file_path)?;
136+
137+
Ok((test_file, lint_file))
138+
}
139+
140+
fn to_camel_case(name: &str) -> String {
141+
return name.split('_')
142+
.map(|s| [&s[0..1].to_uppercase(), &s[1..]].concat())
143+
.collect::<String>();
144+
}
145+
146+
fn create_new_lint(lint_type: Option<&str>, lint_name: Option<&str>) {
147+
let lint_type = lint_type.expect("`type` argument is validated by clap");
148+
let lint_name = lint_name.expect("`name` argument is validated by clap");
149+
150+
match open_files(lint_name) {
151+
Ok((mut test_file, mut lint_file)) => {
152+
let pass_type = match lint_type {
153+
"early" => "EarlyLintPass",
154+
"late" => "LateLintPass",
155+
_ => {
156+
eprintln!("`pass_type` should only ever be `early` or `late`!");
157+
return;
158+
}
159+
};
160+
161+
let camel_case_name = to_camel_case(lint_name);
162+
163+
test_file
164+
.write_all(
165+
format!(
166+
"#![warn(clippy::{})]
167+
168+
fn main() {{
169+
// test code goes here
170+
}}
171+
",
172+
lint_name
173+
)
174+
.as_bytes(),
175+
)
176+
.unwrap();
177+
178+
lint_file
179+
.write_all(
180+
format!(
181+
"use rustc::lint::{{LintArray, LintPass, {type}}};
182+
use rustc::declare_lint_pass;
183+
use rustc_session::declare_tool_lint;
184+
185+
declare_clippy_lint! {{
186+
pub {name_upper},
187+
nursery,
188+
\"default lint description\"
189+
}}
190+
191+
declare_lint_pass!({name_camel} => [{name_upper}]);
192+
193+
impl {type} for {name_camel} {{}}
194+
",
195+
type=pass_type,
196+
name_upper=lint_name.to_uppercase(),
197+
name_camel=camel_case_name
198+
)
199+
.as_bytes(),
200+
)
201+
.unwrap();
202+
update_lints(&UpdateMode::Change);
203+
},
204+
Err(e) => eprintln!("Unable to create lint: {}", e),
205+
}
206+
}
207+
82208
fn print_lints() {
83209
let lint_list = gather_all();
84210
let usable_lints: Vec<Lint> = Lint::usable_lints(lint_list).collect();
@@ -232,3 +358,15 @@ fn update_lints(update_mode: &UpdateMode) {
232358
std::process::exit(1);
233359
}
234360
}
361+
362+
363+
#[test]
364+
fn test_camel_case() {
365+
let s = "a_lint";
366+
let s2 = to_camel_case(s);
367+
assert_eq!(s2, "ALint");
368+
369+
let name = "a_really_long_new_lint";
370+
let name2 = to_camel_case(name);
371+
assert_eq!(name2, "AReallyLongNewLint")
372+
}

0 commit comments

Comments
 (0)