Adapt cargo dev new_lint
to create tests for cargo lints
This commit is contained in:
parent
7a0eccbd8a
commit
1eb6adf475
@ -1,91 +1,110 @@
|
||||
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};
|
||||
|
||||
/// Creates 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");
|
||||
struct LintData<'a> {
|
||||
pass: &'a str,
|
||||
name: &'a str,
|
||||
category: &'a str,
|
||||
project_root: PathBuf,
|
||||
}
|
||||
|
||||
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`!");
|
||||
},
|
||||
};
|
||||
trait Context {
|
||||
fn context<C: AsRef<str>>(self, text: C) -> Self;
|
||||
}
|
||||
|
||||
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(())
|
||||
},
|
||||
Err(e) => Err(io::Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Unable to create lint: {}", e),
|
||||
)),
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_files(lint_name: &str) -> Result<(File, File), io::Error> {
|
||||
let project_root = clippy_project_root();
|
||||
/// Creates the files required to implement and test a new lint and runs `update_lints`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 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(),
|
||||
};
|
||||
|
||||
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));
|
||||
create_lint(&lint).context("Unable to create lint implementation")?;
|
||||
create_test(&lint).context("Unable to create a test for the new lint")
|
||||
}
|
||||
|
||||
if Path::new(&test_file_path).exists() {
|
||||
return Err(io::Error::new(
|
||||
ErrorKind::AlreadyExists,
|
||||
format!("test file {:?} already exists", test_file_path),
|
||||
));
|
||||
}
|
||||
if Path::new(&lint_file_path).exists() {
|
||||
return Err(io::Error::new(
|
||||
ErrorKind::AlreadyExists,
|
||||
format!("lint file {:?} already exists", lint_file_path),
|
||||
));
|
||||
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`!");
|
||||
},
|
||||
};
|
||||
|
||||
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 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))?;
|
||||
|
||||
path.push("src");
|
||||
fs::create_dir(&path)?;
|
||||
write_file(path.join("main.rs"), get_test_file_contents(lint_name))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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)?;
|
||||
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)?;
|
||||
|
||||
Ok((test_file, lint_file))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user