1 use crate::clippy_project_root;
2 use std::fs::{self, OpenOptions};
3 use std::io::prelude::*;
4 use std::io::{self, ErrorKind};
5 use std::path::{Path, PathBuf};
11 project_root: PathBuf,
15 fn context<C: AsRef<str>>(self, text: C) -> Self;
18 impl<T> Context for io::Result<T> {
19 fn context<C: AsRef<str>>(self, text: C) -> Self {
23 let message = format!("{}: {}", text.as_ref(), e);
24 Err(io::Error::new(ErrorKind::Other, message))
30 /// Creates the files required to implement and test a new lint and runs `update_lints`.
34 /// This function errors out if the files couldn't be created or written to.
35 pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>) -> io::Result<()> {
37 pass: pass.expect("`pass` argument is validated by clap"),
38 name: lint_name.expect("`name` argument is validated by clap"),
39 category: category.expect("`category` argument is validated by clap"),
40 project_root: clippy_project_root(),
43 create_lint(&lint).context("Unable to create lint implementation")?;
44 create_test(&lint).context("Unable to create a test for the new lint")
47 fn create_lint(lint: &LintData) -> io::Result<()> {
48 let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
49 "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
50 "late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
52 unreachable!("`pass_type` should only ever be `early` or `late`!");
56 let camel_case_name = to_camel_case(lint.name);
57 let lint_contents = get_lint_file_contents(
67 let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
68 write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())
71 fn create_test(lint: &LintData) -> io::Result<()> {
72 fn create_project_layout<P: Into<PathBuf>>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> {
73 let mut path = location.into().join(case);
74 fs::create_dir(&path)?;
75 write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?;
78 fs::create_dir(&path)?;
79 let header = format!("// compile-flags: --crate-name={}", lint_name);
80 write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?;
85 if lint.category == "cargo" {
86 let relative_test_dir = format!("tests/ui-cargo/{}", lint.name);
87 let test_dir = lint.project_root.join(relative_test_dir);
88 fs::create_dir(&test_dir)?;
90 create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?;
91 create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")
93 let test_path = format!("tests/ui/{}.rs", lint.name);
94 let test_contents = get_test_file_contents(lint.name, None);
95 write_file(lint.project_root.join(test_path), test_contents)
99 fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
100 fn inner(path: &Path, contents: &[u8]) -> io::Result<()> {
108 inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display()))
111 fn to_camel_case(name: &str) -> String {
117 [&s[0..1].to_uppercase(), &s[1..]].concat()
123 fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {
124 let mut contents = format!(
125 "#![warn(clippy::{})]
128 // test code goes here
134 if let Some(header) = header_commands {
135 contents = format!("{}\n{}", header, contents);
141 fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
157 fn get_lint_file_contents(
159 pass_lifetimes: &str,
161 camel_case_name: &str,
164 context_import: &str,
167 "use rustc_lint::{{{type}, {context_import}}};
168 use rustc_session::{{declare_lint_pass, declare_tool_lint}};
171 declare_clippy_lint! {{
172 /// **What it does:**
174 /// **Why is this bad?**
176 /// **Known problems:** None.
181 /// // example code where clippy issues a warning
185 /// // example code which does not raise clippy warning
189 \"default lint description\"
192 declare_lint_pass!({name_camel} => [{name_upper}]);
194 impl {type}{lifetimes} for {name_camel} {{}}
197 lifetimes=pass_lifetimes,
198 name_upper=lint_name.to_uppercase(),
199 name_camel=camel_case_name,
201 pass_import=pass_import,
202 context_import=context_import
207 fn test_camel_case() {
209 let s2 = to_camel_case(s);
210 assert_eq!(s2, "ALint");
212 let name = "a_really_long_new_lint";
213 let name2 = to_camel_case(name);
214 assert_eq!(name2, "AReallyLongNewLint");
216 let name3 = "lint__name";
217 let name4 = to_camel_case(name3);
218 assert_eq!(name4, "LintName");