2020-03-31 15:09:11 +02:00
|
|
|
use crate::clippy_project_root;
|
2021-10-21 13:11:36 +02:00
|
|
|
use indoc::indoc;
|
2020-05-28 15:45:24 +02:00
|
|
|
use std::fs::{self, OpenOptions};
|
2019-12-31 18:07:39 -07:00
|
|
|
use std::io::prelude::*;
|
2020-05-28 15:45:24 +02:00
|
|
|
use std::io::{self, ErrorKind};
|
|
|
|
use std::path::{Path, PathBuf};
|
2019-12-31 18:07:39 -07:00
|
|
|
|
2020-05-28 15:45:24 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Context for io::Result<T> {
|
|
|
|
fn context<C: AsRef<str>>(self, text: C) -> Self {
|
|
|
|
match self {
|
|
|
|
Ok(t) => Ok(t),
|
|
|
|
Err(e) => {
|
|
|
|
let message = format!("{}: {}", text.as_ref(), e);
|
|
|
|
Err(io::Error::new(ErrorKind::Other, message))
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates the files required to implement and test a new lint and runs `update_lints`.
|
2020-03-31 15:09:11 +02:00
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
2020-05-28 15:45:24 +02:00
|
|
|
/// This function errors out if the files couldn't be created or written to.
|
2021-10-21 13:11:36 +02:00
|
|
|
pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>, msrv: bool) -> io::Result<()> {
|
2020-05-28 15:45:24 +02:00
|
|
|
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(),
|
|
|
|
};
|
|
|
|
|
2021-10-21 13:11:36 +02:00
|
|
|
create_lint(&lint, msrv).context("Unable to create lint implementation")?;
|
2021-11-04 12:52:36 +00:00
|
|
|
create_test(&lint).context("Unable to create a test for the new lint")?;
|
|
|
|
add_lint(&lint, msrv).context("Unable to add lint to clippy_lints/src/lib.rs")
|
2020-05-28 15:45:24 +02:00
|
|
|
}
|
|
|
|
|
2021-10-21 13:11:36 +02:00
|
|
|
fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
|
|
|
|
let lint_contents = get_lint_file_contents(lint, enable_msrv);
|
2020-05-28 15:45:24 +02:00
|
|
|
|
|
|
|
let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
|
|
|
|
write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())
|
2019-12-31 18:07:39 -07:00
|
|
|
}
|
|
|
|
|
2021-04-14 09:20:49 -04:00
|
|
|
fn create_test(lint: &LintData<'_>) -> io::Result<()> {
|
2020-05-28 15:45:24 +02:00
|
|
|
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))?;
|
2019-12-31 18:07:39 -07:00
|
|
|
|
2020-05-28 15:45:24 +02:00
|
|
|
path.push("src");
|
|
|
|
fs::create_dir(&path)?;
|
|
|
|
let header = format!("// compile-flags: --crate-name={}", lint_name);
|
|
|
|
write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?;
|
2019-12-31 18:07:39 -07:00
|
|
|
|
2020-05-28 15:45:24 +02:00
|
|
|
Ok(())
|
2019-12-31 18:07:39 -07:00
|
|
|
}
|
2020-05-28 15:45:24 +02:00
|
|
|
|
|
|
|
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, None);
|
|
|
|
write_file(lint.project_root.join(test_path), test_contents)
|
2019-12-31 18:07:39 -07:00
|
|
|
}
|
2020-05-28 15:45:24 +02:00
|
|
|
}
|
2019-12-31 18:07:39 -07:00
|
|
|
|
2021-11-04 12:52:36 +00:00
|
|
|
fn add_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
|
|
|
|
let path = "clippy_lints/src/lib.rs";
|
|
|
|
let mut lib_rs = fs::read_to_string(path).context("reading")?;
|
|
|
|
|
|
|
|
let comment_start = lib_rs.find("// add lints here,").expect("Couldn't find comment");
|
|
|
|
|
|
|
|
let new_lint = if enable_msrv {
|
|
|
|
format!(
|
|
|
|
"store.register_{lint_pass}_pass(move || Box::new({module_name}::{camel_name}::new(msrv)));\n ",
|
|
|
|
lint_pass = lint.pass,
|
|
|
|
module_name = lint.name,
|
|
|
|
camel_name = to_camel_case(lint.name),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
format!(
|
|
|
|
"store.register_{lint_pass}_pass(|| Box::new({module_name}::{camel_name}));\n ",
|
|
|
|
lint_pass = lint.pass,
|
|
|
|
module_name = lint.name,
|
|
|
|
camel_name = to_camel_case(lint.name),
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
lib_rs.insert_str(comment_start, &new_lint);
|
|
|
|
|
|
|
|
fs::write(path, lib_rs).context("writing")
|
|
|
|
}
|
|
|
|
|
2020-05-28 15:45:24 +02:00
|
|
|
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)
|
|
|
|
}
|
2019-12-31 18:07:39 -07:00
|
|
|
|
2020-05-28 15:45:24 +02:00
|
|
|
inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display()))
|
2019-12-31 18:07:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2021-12-06 12:33:31 +01:00
|
|
|
fn get_stabilisation_version() -> String {
|
|
|
|
let mut command = cargo_metadata::MetadataCommand::new();
|
|
|
|
command.no_deps();
|
|
|
|
if let Ok(metadata) = command.exec() {
|
|
|
|
if let Some(pkg) = metadata.packages.iter().find(|pkg| pkg.name == "clippy") {
|
|
|
|
return format!("{}.{}.0", pkg.version.minor, pkg.version.patch);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String::from("<TODO set version(see doc/adding_lints.md)>")
|
|
|
|
}
|
|
|
|
|
2020-05-28 15:45:24 +02:00
|
|
|
fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {
|
|
|
|
let mut contents = format!(
|
2021-10-21 13:11:36 +02:00
|
|
|
indoc! {"
|
|
|
|
#![warn(clippy::{})]
|
2019-12-31 18:07:39 -07:00
|
|
|
|
2021-10-21 13:11:36 +02:00
|
|
|
fn main() {{
|
|
|
|
// test code goes here
|
|
|
|
}}
|
|
|
|
"},
|
2019-12-31 18:07:39 -07:00
|
|
|
lint_name
|
2020-05-28 15:45:24 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if let Some(header) = header_commands {
|
|
|
|
contents = format!("{}\n{}", header, contents);
|
|
|
|
}
|
|
|
|
|
|
|
|
contents
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
|
|
|
|
format!(
|
2021-10-21 13:11:36 +02:00
|
|
|
indoc! {r#"
|
|
|
|
# {}
|
2020-05-28 15:45:24 +02:00
|
|
|
|
2021-10-21 13:11:36 +02:00
|
|
|
[package]
|
|
|
|
name = "{}"
|
|
|
|
version = "0.1.0"
|
|
|
|
publish = false
|
2020-06-09 14:36:01 +00:00
|
|
|
|
2021-10-21 13:11:36 +02:00
|
|
|
[workspace]
|
|
|
|
"#},
|
2020-05-28 15:45:24 +02:00
|
|
|
hint, lint_name
|
2019-12-31 18:07:39 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-10-21 13:11:36 +02:00
|
|
|
fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
|
|
|
|
let mut result = String::new();
|
|
|
|
|
|
|
|
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`!");
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2021-12-06 12:33:31 +01:00
|
|
|
let version = get_stabilisation_version();
|
2021-10-21 13:11:36 +02:00
|
|
|
let lint_name = lint.name;
|
|
|
|
let category = lint.category;
|
|
|
|
let name_camel = to_camel_case(lint.name);
|
|
|
|
let name_upper = lint_name.to_uppercase();
|
|
|
|
|
|
|
|
result.push_str(&if enable_msrv {
|
|
|
|
format!(
|
|
|
|
indoc! {"
|
|
|
|
use clippy_utils::msrvs;
|
|
|
|
{pass_import}
|
|
|
|
use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
|
|
|
|
use rustc_semver::RustcVersion;
|
|
|
|
use rustc_session::{{declare_tool_lint, impl_lint_pass}};
|
|
|
|
|
|
|
|
"},
|
|
|
|
pass_type = pass_type,
|
|
|
|
pass_import = pass_import,
|
|
|
|
context_import = context_import,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
format!(
|
|
|
|
indoc! {"
|
|
|
|
{pass_import}
|
|
|
|
use rustc_lint::{{{context_import}, {pass_type}}};
|
|
|
|
use rustc_session::{{declare_lint_pass, declare_tool_lint}};
|
|
|
|
|
|
|
|
"},
|
|
|
|
pass_import = pass_import,
|
|
|
|
pass_type = pass_type,
|
|
|
|
context_import = context_import
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
|
|
|
result.push_str(&format!(
|
2021-12-06 12:33:31 +01:00
|
|
|
indoc! {r#"
|
2021-10-21 13:11:36 +02:00
|
|
|
declare_clippy_lint! {{
|
|
|
|
/// ### What it does
|
|
|
|
///
|
|
|
|
/// ### Why is this bad?
|
|
|
|
///
|
|
|
|
/// ### Example
|
|
|
|
/// ```rust
|
|
|
|
/// // example code where clippy issues a warning
|
|
|
|
/// ```
|
|
|
|
/// Use instead:
|
|
|
|
/// ```rust
|
|
|
|
/// // example code which does not raise clippy warning
|
|
|
|
/// ```
|
2021-12-06 12:33:31 +01:00
|
|
|
#[clippy::version = "{version}"]
|
2021-10-21 13:11:36 +02:00
|
|
|
pub {name_upper},
|
|
|
|
{category},
|
2021-12-06 12:33:31 +01:00
|
|
|
"default lint description"
|
2021-10-21 13:11:36 +02:00
|
|
|
}}
|
2021-12-06 12:33:31 +01:00
|
|
|
"#},
|
|
|
|
version = version,
|
2021-10-21 13:11:36 +02:00
|
|
|
name_upper = name_upper,
|
|
|
|
category = category,
|
|
|
|
));
|
|
|
|
|
|
|
|
result.push_str(&if enable_msrv {
|
|
|
|
format!(
|
|
|
|
indoc! {"
|
|
|
|
pub struct {name_camel} {{
|
|
|
|
msrv: Option<RustcVersion>,
|
|
|
|
}}
|
|
|
|
|
|
|
|
impl {name_camel} {{
|
|
|
|
#[must_use]
|
|
|
|
pub fn new(msrv: Option<RustcVersion>) -> Self {{
|
|
|
|
Self {{ msrv }}
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
|
|
|
|
impl_lint_pass!({name_camel} => [{name_upper}]);
|
|
|
|
|
|
|
|
impl {pass_type}{pass_lifetimes} for {name_camel} {{
|
|
|
|
extract_msrv_attr!({context_import});
|
|
|
|
}}
|
|
|
|
|
|
|
|
// TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
|
|
|
|
// TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
|
|
|
|
// TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
|
|
|
|
"},
|
|
|
|
pass_type = pass_type,
|
|
|
|
pass_lifetimes = pass_lifetimes,
|
|
|
|
name_upper = name_upper,
|
|
|
|
name_camel = name_camel,
|
|
|
|
context_import = context_import,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
format!(
|
|
|
|
indoc! {"
|
|
|
|
declare_lint_pass!({name_camel} => [{name_upper}]);
|
|
|
|
|
|
|
|
impl {pass_type}{pass_lifetimes} for {name_camel} {{}}
|
|
|
|
"},
|
|
|
|
pass_type = pass_type,
|
|
|
|
pass_lifetimes = pass_lifetimes,
|
|
|
|
name_upper = name_upper,
|
|
|
|
name_camel = name_camel,
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
|
|
|
result
|
2019-12-31 18:07:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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");
|
|
|
|
}
|