use crate::clippy_project_root;
-use std::fs::{File, OpenOptions};
-use std::io;
+use std::fs::{self, OpenOptions};
use std::io::prelude::*;
-use std::io::ErrorKind;
-use std::path::Path;
+use std::io::{self, ErrorKind};
+use std::path::{Path, PathBuf};
+
+struct LintData<'a> {
+ pass: &'a str,
+ name: &'a str,
+ category: &'a str,
+ project_root: PathBuf,
+}
+
+trait Context {
+ fn context<C: AsRef<str>>(self, text: C) -> Self;
+}
-/// Creates files required to implement and test a new lint and runs `update_lints`.
+impl<T> Context for io::Result<T> {
+ fn context<C: AsRef<str>>(self, text: C) -> Self {
+ match self {
+ Err(e) => {
+ let message = format!("{}: {}", text.as_ref(), e);
+ Err(io::Error::new(ErrorKind::Other, message))
+ },
+ ok => ok,
+ }
+ }
+}
+
+/// Creates the files required to implement and test a new lint and runs `update_lints`.
///
/// # Errors
///
-/// This function errors, if the files couldn't be created
-pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>) -> Result<(), io::Error> {
- let pass = pass.expect("`pass` argument is validated by clap");
- let lint_name = lint_name.expect("`name` argument is validated by clap");
- let category = category.expect("`category` argument is validated by clap");
-
- match open_files(lint_name) {
- Ok((mut test_file, mut lint_file)) => {
- let (pass_type, pass_lifetimes, pass_import, context_import) = match pass {
- "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
- "late" => ("LateLintPass", "<'_, '_>", "use rustc_hir::*;", "LateContext"),
- _ => {
- unreachable!("`pass_type` should only ever be `early` or `late`!");
- },
- };
-
- let camel_case_name = to_camel_case(lint_name);
-
- if let Err(e) = test_file.write_all(get_test_file_contents(lint_name).as_bytes()) {
- return Err(io::Error::new(
- ErrorKind::Other,
- format!("Could not write to test file: {}", e),
- ));
- };
-
- if let Err(e) = lint_file.write_all(
- get_lint_file_contents(
- pass_type,
- pass_lifetimes,
- lint_name,
- &camel_case_name,
- category,
- pass_import,
- context_import,
- )
- .as_bytes(),
- ) {
- return Err(io::Error::new(
- ErrorKind::Other,
- format!("Could not write to lint file: {}", e),
- ));
- }
- Ok(())
+/// This function errors out if the files couldn't be created or written to.
+pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>) -> io::Result<()> {
+ let lint = LintData {
+ pass: pass.expect("`pass` argument is validated by clap"),
+ name: lint_name.expect("`name` argument is validated by clap"),
+ category: category.expect("`category` argument is validated by clap"),
+ project_root: clippy_project_root(),
+ };
+
+ create_lint(&lint).context("Unable to create lint implementation")?;
+ create_test(&lint).context("Unable to create a test for the new lint")
+}
+
+fn create_lint(lint: &LintData) -> io::Result<()> {
+ let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
+ "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
+ "late" => ("LateLintPass", "<'_, '_>", "use rustc_hir::*;", "LateContext"),
+ _ => {
+ unreachable!("`pass_type` should only ever be `early` or `late`!");
},
- Err(e) => Err(io::Error::new(
- ErrorKind::Other,
- format!("Unable to create lint: {}", e),
- )),
- }
+ };
+
+ let camel_case_name = to_camel_case(lint.name);
+ let lint_contents = get_lint_file_contents(
+ pass_type,
+ pass_lifetimes,
+ lint.name,
+ &camel_case_name,
+ lint.category,
+ pass_import,
+ context_import,
+ );
+
+ let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
+ write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())
}
-fn open_files(lint_name: &str) -> Result<(File, File), io::Error> {
- let project_root = clippy_project_root();
+fn create_test(lint: &LintData) -> io::Result<()> {
+ fn create_project_layout<P: Into<PathBuf>>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> {
+ let mut path = location.into().join(case);
+ fs::create_dir(&path)?;
+ write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?;
- let test_file_path = project_root.join("tests").join("ui").join(format!("{}.rs", lint_name));
- let lint_file_path = project_root
- .join("clippy_lints")
- .join("src")
- .join(format!("{}.rs", lint_name));
+ path.push("src");
+ fs::create_dir(&path)?;
+ write_file(path.join("main.rs"), get_test_file_contents(lint_name))?;
- if Path::new(&test_file_path).exists() {
- return Err(io::Error::new(
- ErrorKind::AlreadyExists,
- format!("test file {:?} already exists", test_file_path),
- ));
+ Ok(())
}
- if Path::new(&lint_file_path).exists() {
- return Err(io::Error::new(
- ErrorKind::AlreadyExists,
- format!("lint file {:?} already exists", lint_file_path),
- ));
+
+ if lint.category == "cargo" {
+ let relative_test_dir = format!("tests/ui-cargo/{}", lint.name);
+ let test_dir = lint.project_root.join(relative_test_dir);
+ fs::create_dir(&test_dir)?;
+
+ create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?;
+ create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")
+ } else {
+ let test_path = format!("tests/ui/{}.rs", lint.name);
+ let test_contents = get_test_file_contents(lint.name);
+ write_file(lint.project_root.join(test_path), test_contents)
}
+}
- let test_file = OpenOptions::new().write(true).create_new(true).open(test_file_path)?;
- let lint_file = OpenOptions::new().write(true).create_new(true).open(lint_file_path)?;
+fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
+ fn inner(path: &Path, contents: &[u8]) -> io::Result<()> {
+ OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(path)?
+ .write_all(contents)
+ }
- Ok((test_file, lint_file))
+ inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display()))
}
fn to_camel_case(name: &str) -> String {
)
}
+fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
+ format!(
+ r#"
+# {}
+
+[package]
+name = "{}"
+version = "0.1.0"
+"#,
+ hint, lint_name
+ )
+}
+
fn get_lint_file_contents(
pass_type: &str,
pass_lifetimes: &str,