1 use crate::clippy_project_root;
3 use std::fs::{self, OpenOptions};
4 use std::io::prelude::*;
5 use std::io::{self, ErrorKind};
6 use std::path::{Path, PathBuf};
12 project_root: PathBuf,
16 fn context<C: AsRef<str>>(self, text: C) -> Self;
19 impl<T> Context for io::Result<T> {
20 fn context<C: AsRef<str>>(self, text: C) -> Self {
24 let message = format!("{}: {}", text.as_ref(), e);
25 Err(io::Error::new(ErrorKind::Other, message))
31 /// Creates the files required to implement and test a new lint and runs `update_lints`.
35 /// This function errors out if the files couldn't be created or written to.
36 pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>, msrv: bool) -> io::Result<()> {
38 pass: pass.expect("`pass` argument is validated by clap"),
39 name: lint_name.expect("`name` argument is validated by clap"),
40 category: category.expect("`category` argument is validated by clap"),
41 project_root: clippy_project_root(),
44 create_lint(&lint, msrv).context("Unable to create lint implementation")?;
45 create_test(&lint).context("Unable to create a test for the new lint")
48 fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
49 let lint_contents = get_lint_file_contents(lint, enable_msrv);
51 let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
52 write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())
55 fn create_test(lint: &LintData<'_>) -> io::Result<()> {
56 fn create_project_layout<P: Into<PathBuf>>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> {
57 let mut path = location.into().join(case);
58 fs::create_dir(&path)?;
59 write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?;
62 fs::create_dir(&path)?;
63 let header = format!("// compile-flags: --crate-name={}", lint_name);
64 write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?;
69 if lint.category == "cargo" {
70 let relative_test_dir = format!("tests/ui-cargo/{}", lint.name);
71 let test_dir = lint.project_root.join(relative_test_dir);
72 fs::create_dir(&test_dir)?;
74 create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?;
75 create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")
77 let test_path = format!("tests/ui/{}.rs", lint.name);
78 let test_contents = get_test_file_contents(lint.name, None);
79 write_file(lint.project_root.join(test_path), test_contents)
83 fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
84 fn inner(path: &Path, contents: &[u8]) -> io::Result<()> {
92 inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display()))
95 fn to_camel_case(name: &str) -> String {
101 [&s[0..1].to_uppercase(), &s[1..]].concat()
107 fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {
108 let mut contents = format!(
113 // test code goes here
119 if let Some(header) = header_commands {
120 contents = format!("{}\n{}", header, contents);
126 fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
142 fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
143 let mut result = String::new();
145 let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
146 "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
147 "late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
149 unreachable!("`pass_type` should only ever be `early` or `late`!");
153 let lint_name = lint.name;
154 let pass_name = lint.pass;
155 let category = lint.category;
156 let name_camel = to_camel_case(lint.name);
157 let name_upper = lint_name.to_uppercase();
159 result.push_str(&if enable_msrv {
162 use clippy_utils::msrvs;
164 use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
165 use rustc_semver::RustcVersion;
166 use rustc_session::{{declare_tool_lint, impl_lint_pass}};
169 pass_type = pass_type,
170 pass_import = pass_import,
171 context_import = context_import,
177 use rustc_lint::{{{context_import}, {pass_type}}};
178 use rustc_session::{{declare_lint_pass, declare_tool_lint}};
181 pass_import = pass_import,
182 pass_type = pass_type,
183 context_import = context_import
187 result.push_str(&format!(
189 declare_clippy_lint! {{
192 /// ### Why is this bad?
196 /// // example code where clippy issues a warning
200 /// // example code which does not raise clippy warning
204 \"default lint description\"
207 name_upper = name_upper,
211 result.push_str(&if enable_msrv {
214 pub struct {name_camel} {{
215 msrv: Option<RustcVersion>,
220 pub fn new(msrv: Option<RustcVersion>) -> Self {{
225 impl_lint_pass!({name_camel} => [{name_upper}]);
227 impl {pass_type}{pass_lifetimes} for {name_camel} {{
228 extract_msrv_attr!({context_import});
231 // TODO: Register the lint pass in `clippy_lints/src/lib.rs`,
232 // e.g. store.register_{pass_name}_pass(move || Box::new({module_name}::{name_camel}::new(msrv)));
233 // TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
234 // TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
235 // TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
237 pass_type = pass_type,
238 pass_lifetimes = pass_lifetimes,
239 pass_name = pass_name,
240 name_upper = name_upper,
241 name_camel = name_camel,
242 module_name = lint_name,
243 context_import = context_import,
248 declare_lint_pass!({name_camel} => [{name_upper}]);
250 impl {pass_type}{pass_lifetimes} for {name_camel} {{}}
252 // TODO: Register the lint pass in `clippy_lints/src/lib.rs`,
253 // e.g. store.register_{pass_name}_pass(|| Box::new({module_name}::{name_camel}));
255 pass_type = pass_type,
256 pass_lifetimes = pass_lifetimes,
257 pass_name = pass_name,
258 name_upper = name_upper,
259 name_camel = name_camel,
260 module_name = lint_name,
268 fn test_camel_case() {
270 let s2 = to_camel_case(s);
271 assert_eq!(s2, "ALint");
273 let name = "a_really_long_new_lint";
274 let name2 = to_camel_case(name);
275 assert_eq!(name2, "AReallyLongNewLint");
277 let name3 = "lint__name";
278 let name4 = to_camel_case(name3);
279 assert_eq!(name4, "LintName");