Skip to content

Commit 0d7885e

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 0d7885e

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed

clippy_dev/src/main.rs

+164
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,47 @@ 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("pass")
64+
.short("p")
65+
.long("pass")
66+
.help("Specify whether the lint runs during the early or late pass")
67+
.takes_value(true)
68+
.possible_values(&["early", "late"])
69+
.required(true),
70+
)
71+
.arg(
72+
Arg::with_name("name")
73+
.short("n")
74+
.long("name")
75+
.help("Name of the new lint in snake case, ex: fn_too_long")
76+
.takes_value(true)
77+
.required(true),
78+
)
79+
.arg(
80+
Arg::with_name("category")
81+
.short("c")
82+
.long("category")
83+
.help("What category the lint belongs to, defaults to `nursery`")
84+
.default_value("nursery")
85+
.possible_values(&[
86+
"style",
87+
"correctness",
88+
"complexity",
89+
"perf",
90+
"pedantic",
91+
"restriction",
92+
"cargo",
93+
"nursery",
94+
"internal",
95+
"internal_warn",
96+
])
97+
.takes_value(true),
98+
),
99+
)
54100
.arg(
55101
Arg::with_name("limit-stderr-length")
56102
.long("limit-stderr-length")
@@ -75,10 +121,117 @@ fn main() {
75121
update_lints(&UpdateMode::Change);
76122
}
77123
},
124+
("new_lint", Some(matches)) => {
125+
create_new_lint(
126+
matches.value_of("pass"),
127+
matches.value_of("name"),
128+
matches.value_of("category"),
129+
);
130+
},
78131
_ => {},
79132
}
80133
}
81134

135+
fn project_root() -> Result<PathBuf, io::Error> {
136+
let current_dir = std::env::current_dir()?;
137+
for path in current_dir.ancestors() {
138+
let result = std::fs::read_to_string(path.join("Cargo.toml"));
139+
if let Err(err) = &result {
140+
if err.kind() == io::ErrorKind::NotFound {
141+
continue;
142+
}
143+
}
144+
145+
let content = result?;
146+
if content.contains("[package]\nname = \"clippy\"") {
147+
return Ok(path.to_path_buf());
148+
}
149+
}
150+
Err(io::Error::new(ErrorKind::Other, "Unable to find project root"))
151+
}
152+
153+
fn open_files(lint_name: &str) -> Result<(File, File), io::Error> {
154+
let project_root = project_root()?;
155+
156+
let test_file_path = project_root.join(format!("tests/ui/{}.rs", lint_name));
157+
let test_file = OpenOptions::new().write(true).create_new(true).open(test_file_path)?;
158+
159+
let lint_file_path = project_root.join(format!("clippy_lints/src/{}.rs", lint_name));
160+
let lint_file = OpenOptions::new().write(true).create_new(true).open(lint_file_path)?;
161+
162+
Ok((test_file, lint_file))
163+
}
164+
165+
fn to_camel_case(name: &str) -> String {
166+
name.split('_')
167+
.map(|s| [&s[0..1].to_uppercase(), &s[1..]].concat())
168+
.collect::<String>()
169+
}
170+
171+
fn create_new_lint(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>) {
172+
let pass = pass.expect("`pass` argument is validated by clap");
173+
let lint_name = lint_name.expect("`name` argument is validated by clap");
174+
let category = category.expect("`category` argument is validated by clap");
175+
176+
match open_files(lint_name) {
177+
Ok((mut test_file, mut lint_file)) => {
178+
let pass_type = match pass {
179+
"early" => "EarlyLintPass",
180+
"late" => "LateLintPass",
181+
_ => {
182+
eprintln!("`pass_type` should only ever be `early` or `late`!");
183+
return;
184+
},
185+
};
186+
187+
let camel_case_name = to_camel_case(lint_name);
188+
189+
test_file
190+
.write_all(
191+
format!(
192+
"#![warn(clippy::{})]
193+
194+
fn main() {{
195+
// test code goes here
196+
}}
197+
",
198+
lint_name
199+
)
200+
.as_bytes(),
201+
)
202+
.unwrap();
203+
204+
lint_file
205+
.write_all(
206+
format!(
207+
"use rustc::lint::{{LintArray, LintPass, {type}}};
208+
use rustc::declare_lint_pass;
209+
use rustc_session::declare_tool_lint;
210+
211+
declare_clippy_lint! {{
212+
pub {name_upper},
213+
{category},
214+
\"default lint description\"
215+
}}
216+
217+
declare_lint_pass!({name_camel} => [{name_upper}]);
218+
219+
impl {type} for {name_camel} {{}}
220+
",
221+
type=pass_type,
222+
name_upper=lint_name.to_uppercase(),
223+
name_camel=camel_case_name,
224+
category=category
225+
)
226+
.as_bytes(),
227+
)
228+
.unwrap();
229+
update_lints(&UpdateMode::Change);
230+
},
231+
Err(e) => eprintln!("Unable to create lint: {}", e),
232+
}
233+
}
234+
82235
fn print_lints() {
83236
let lint_list = gather_all();
84237
let usable_lints: Vec<Lint> = Lint::usable_lints(lint_list).collect();
@@ -232,3 +385,14 @@ fn update_lints(update_mode: &UpdateMode) {
232385
std::process::exit(1);
233386
}
234387
}
388+
389+
#[test]
390+
fn test_camel_case() {
391+
let s = "a_lint";
392+
let s2 = to_camel_case(s);
393+
assert_eq!(s2, "ALint");
394+
395+
let name = "a_really_long_new_lint";
396+
let name2 = to_camel_case(name);
397+
assert_eq!(name2, "AReallyLongNewLint")
398+
}

0 commit comments

Comments
 (0)