1 use crate::clippy_project_root;
3 use std::fmt::Write as _;
4 use std::fs::{self, OpenOptions};
5 use std::io::prelude::*;
6 use std::io::{self, ErrorKind};
7 use std::path::{Path, PathBuf};
13 project_root: PathBuf,
17 fn context<C: AsRef<str>>(self, text: C) -> Self;
20 impl<T> Context for io::Result<T> {
21 fn context<C: AsRef<str>>(self, text: C) -> Self {
25 let message = format!("{}: {}", text.as_ref(), e);
26 Err(io::Error::new(ErrorKind::Other, message))
32 /// Creates the files required to implement and test a new lint and runs `update_lints`.
36 /// This function errors out if the files couldn't be created or written to.
38 pass: Option<&String>,
39 lint_name: Option<&String>,
40 category: Option<&String>,
44 pass: pass.expect("`pass` argument is validated by clap"),
45 name: lint_name.expect("`name` argument is validated by clap"),
46 category: category.expect("`category` argument is validated by clap"),
47 project_root: clippy_project_root(),
50 create_lint(&lint, msrv).context("Unable to create lint implementation")?;
51 create_test(&lint).context("Unable to create a test for the new lint")?;
52 add_lint(&lint, msrv).context("Unable to add lint to clippy_lints/src/lib.rs")
55 fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
56 let lint_contents = get_lint_file_contents(lint, enable_msrv);
58 let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
59 write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())
62 fn create_test(lint: &LintData<'_>) -> io::Result<()> {
63 fn create_project_layout<P: Into<PathBuf>>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> {
64 let mut path = location.into().join(case);
65 fs::create_dir(&path)?;
66 write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?;
69 fs::create_dir(&path)?;
70 let header = format!("// compile-flags: --crate-name={}", lint_name);
71 write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?;
76 if lint.category == "cargo" {
77 let relative_test_dir = format!("tests/ui-cargo/{}", lint.name);
78 let test_dir = lint.project_root.join(relative_test_dir);
79 fs::create_dir(&test_dir)?;
81 create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?;
82 create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")
84 let test_path = format!("tests/ui/{}.rs", lint.name);
85 let test_contents = get_test_file_contents(lint.name, None);
86 write_file(lint.project_root.join(test_path), test_contents)
90 fn add_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
91 let path = "clippy_lints/src/lib.rs";
92 let mut lib_rs = fs::read_to_string(path).context("reading")?;
94 let comment_start = lib_rs.find("// add lints here,").expect("Couldn't find comment");
96 let new_lint = if enable_msrv {
98 "store.register_{lint_pass}_pass(move || Box::new({module_name}::{camel_name}::new(msrv)));\n ",
99 lint_pass = lint.pass,
100 module_name = lint.name,
101 camel_name = to_camel_case(lint.name),
105 "store.register_{lint_pass}_pass(|| Box::new({module_name}::{camel_name}));\n ",
106 lint_pass = lint.pass,
107 module_name = lint.name,
108 camel_name = to_camel_case(lint.name),
112 lib_rs.insert_str(comment_start, &new_lint);
114 fs::write(path, lib_rs).context("writing")
117 fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
118 fn inner(path: &Path, contents: &[u8]) -> io::Result<()> {
126 inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display()))
129 fn to_camel_case(name: &str) -> String {
135 [&s[0..1].to_uppercase(), &s[1..]].concat()
141 fn get_stabilization_version() -> String {
142 fn parse_manifest(contents: &str) -> Option<String> {
143 let version = contents
145 .filter_map(|l| l.split_once('='))
146 .find_map(|(k, v)| (k.trim() == "version").then(|| v.trim()))?;
147 let Some(("0", version)) = version.get(1..version.len() - 1)?.split_once('.') else {
150 let (minor, patch) = version.split_once('.')?;
153 minor.parse::<u32>().ok()?,
154 patch.parse::<u32>().ok()?
157 let contents = fs::read_to_string("Cargo.toml").expect("Unable to read `Cargo.toml`");
158 parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`")
161 fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {
162 let mut contents = format!(
167 // test code goes here
173 if let Some(header) = header_commands {
174 contents = format!("{}\n{}", header, contents);
180 fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
196 fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
197 let mut result = String::new();
199 let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
200 "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
201 "late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
203 unreachable!("`pass_type` should only ever be `early` or `late`!");
207 let version = get_stabilization_version();
208 let lint_name = lint.name;
209 let category = lint.category;
210 let name_camel = to_camel_case(lint.name);
211 let name_upper = lint_name.to_uppercase();
213 result.push_str(&if enable_msrv {
216 use clippy_utils::msrvs;
218 use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
219 use rustc_semver::RustcVersion;
220 use rustc_session::{{declare_tool_lint, impl_lint_pass}};
223 pass_type = pass_type,
224 pass_import = pass_import,
225 context_import = context_import,
231 use rustc_lint::{{{context_import}, {pass_type}}};
232 use rustc_session::{{declare_lint_pass, declare_tool_lint}};
235 pass_import = pass_import,
236 pass_type = pass_type,
237 context_import = context_import
244 declare_clippy_lint! {{
247 /// ### Why is this bad?
251 /// // example code where clippy issues a warning
255 /// // example code which does not raise clippy warning
257 #[clippy::version = "{version}"]
260 "default lint description"
264 name_upper = name_upper,
268 result.push_str(&if enable_msrv {
271 pub struct {name_camel} {{
272 msrv: Option<RustcVersion>,
277 pub fn new(msrv: Option<RustcVersion>) -> Self {{
282 impl_lint_pass!({name_camel} => [{name_upper}]);
284 impl {pass_type}{pass_lifetimes} for {name_camel} {{
285 extract_msrv_attr!({context_import});
288 // TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
289 // TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
290 // TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
292 pass_type = pass_type,
293 pass_lifetimes = pass_lifetimes,
294 name_upper = name_upper,
295 name_camel = name_camel,
296 context_import = context_import,
301 declare_lint_pass!({name_camel} => [{name_upper}]);
303 impl {pass_type}{pass_lifetimes} for {name_camel} {{}}
305 pass_type = pass_type,
306 pass_lifetimes = pass_lifetimes,
307 name_upper = name_upper,
308 name_camel = name_camel,
316 fn test_camel_case() {
318 let s2 = to_camel_case(s);
319 assert_eq!(s2, "ALint");
321 let name = "a_really_long_new_lint";
322 let name2 = to_camel_case(name);
323 assert_eq!(name2, "AReallyLongNewLint");
325 let name3 = "lint__name";
326 let name4 = to_camel_case(name3);
327 assert_eq!(name4, "LintName");