2020-03-31 15:09:11 +02:00
|
|
|
use crate::clippy_project_root;
|
2019-12-31 18:07:39 -07:00
|
|
|
use std::fs::{File, OpenOptions};
|
|
|
|
use std::io;
|
|
|
|
use std::io::prelude::*;
|
|
|
|
use std::io::ErrorKind;
|
2020-01-31 07:30:44 +01:00
|
|
|
use std::path::Path;
|
2019-12-31 18:07:39 -07:00
|
|
|
|
2020-03-31 15:09:11 +02:00
|
|
|
/// 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
|
2019-12-31 18:07:39 -07:00
|
|
|
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)) => {
|
2020-01-16 10:05:29 -08:00
|
|
|
let (pass_type, pass_lifetimes, pass_import, context_import) = match pass {
|
2020-03-01 12:23:33 +09:00
|
|
|
"early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
|
2020-01-16 10:05:29 -08:00
|
|
|
"late" => ("LateLintPass", "<'_, '_>", "use rustc_hir::*;", "LateContext"),
|
2019-12-31 18:07:39 -07:00
|
|
|
_ => {
|
|
|
|
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,
|
2020-01-16 10:05:29 -08:00
|
|
|
pass_lifetimes,
|
2019-12-31 18:07:39 -07:00
|
|
|
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),
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn open_files(lint_name: &str) -> Result<(File, File), io::Error> {
|
2020-01-31 07:30:44 +01:00
|
|
|
let project_root = clippy_project_root();
|
2019-12-31 18:07:39 -07:00
|
|
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
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),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
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)?;
|
|
|
|
|
|
|
|
Ok((test_file, lint_file))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_camel_case(name: &str) -> String {
|
|
|
|
name.split('_')
|
|
|
|
.map(|s| {
|
|
|
|
if s.is_empty() {
|
|
|
|
String::from("")
|
|
|
|
} else {
|
|
|
|
[&s[0..1].to_uppercase(), &s[1..]].concat()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_test_file_contents(lint_name: &str) -> String {
|
|
|
|
format!(
|
|
|
|
"#![warn(clippy::{})]
|
|
|
|
|
|
|
|
fn main() {{
|
|
|
|
// test code goes here
|
|
|
|
}}
|
|
|
|
",
|
|
|
|
lint_name
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_lint_file_contents(
|
|
|
|
pass_type: &str,
|
2020-01-16 10:05:29 -08:00
|
|
|
pass_lifetimes: &str,
|
2019-12-31 18:07:39 -07:00
|
|
|
lint_name: &str,
|
|
|
|
camel_case_name: &str,
|
|
|
|
category: &str,
|
|
|
|
pass_import: &str,
|
|
|
|
context_import: &str,
|
|
|
|
) -> String {
|
|
|
|
format!(
|
2020-02-10 19:24:01 -08:00
|
|
|
"use rustc_lint::{{{type}, {context_import}}};
|
2019-12-31 18:07:39 -07:00
|
|
|
use rustc_session::{{declare_lint_pass, declare_tool_lint}};
|
|
|
|
{pass_import}
|
|
|
|
|
|
|
|
declare_clippy_lint! {{
|
|
|
|
/// **What it does:**
|
|
|
|
///
|
|
|
|
/// **Why is this bad?**
|
|
|
|
///
|
|
|
|
/// **Known problems:** None.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
///
|
|
|
|
/// ```rust
|
2020-03-18 01:43:39 +01:00
|
|
|
/// // example code where clippy issues a warning
|
|
|
|
/// ```
|
|
|
|
/// Use instead:
|
|
|
|
/// ```rust
|
|
|
|
/// // example code which does not raise clippy warning
|
2019-12-31 18:07:39 -07:00
|
|
|
/// ```
|
|
|
|
pub {name_upper},
|
|
|
|
{category},
|
|
|
|
\"default lint description\"
|
|
|
|
}}
|
|
|
|
|
|
|
|
declare_lint_pass!({name_camel} => [{name_upper}]);
|
|
|
|
|
2020-01-16 10:05:29 -08:00
|
|
|
impl {type}{lifetimes} for {name_camel} {{}}
|
2019-12-31 18:07:39 -07:00
|
|
|
",
|
|
|
|
type=pass_type,
|
2020-01-16 10:05:29 -08:00
|
|
|
lifetimes=pass_lifetimes,
|
2019-12-31 18:07:39 -07:00
|
|
|
name_upper=lint_name.to_uppercase(),
|
|
|
|
name_camel=camel_case_name,
|
|
|
|
category=category,
|
|
|
|
pass_import=pass_import,
|
|
|
|
context_import=context_import
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_camel_case() {
|
|
|
|
let s = "a_lint";
|
|
|
|
let s2 = to_camel_case(s);
|
|
|
|
assert_eq!(s2, "ALint");
|
|
|
|
|
|
|
|
let name = "a_really_long_new_lint";
|
|
|
|
let name2 = to_camel_case(name);
|
|
|
|
assert_eq!(name2, "AReallyLongNewLint");
|
|
|
|
|
|
|
|
let name3 = "lint__name";
|
|
|
|
let name4 = to_camel_case(name3);
|
|
|
|
assert_eq!(name4, "LintName");
|
|
|
|
}
|