]> git.lizzy.rs Git - rust.git/commitdiff
Adapt `cargo dev new_lint` to create tests for cargo lints
authorEduardo Broto <ebroto@tutanota.com>
Mon, 18 May 2020 19:03:37 +0000 (21:03 +0200)
committerEduardo Broto <ebroto@tutanota.com>
Thu, 21 May 2020 12:11:11 +0000 (14:11 +0200)
clippy_dev/src/new_lint.rs

index 44b2a5383d2113f367c1a6262e351b236ea33e16..843beaf32387764ae9d5cab17b7e5cdf3fc640c6 100644 (file)
 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 {
@@ -112,6 +131,19 @@ fn main() {{
     )
 }
 
+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,