168 lines
5.2 KiB
Rust
168 lines
5.2 KiB
Rust
#![feature(let_chains)]
|
|
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
|
|
// warn on lints, that are included in `rust-lang/rust`s bootstrap
|
|
#![warn(rust_2018_idioms, unused_lifetimes)]
|
|
|
|
use proc_macro::TokenStream;
|
|
use quote::{format_ident, quote};
|
|
use syn::parse::{Parse, ParseStream};
|
|
use syn::{parse_macro_input, Attribute, Error, Expr, ExprLit, Ident, Lit, LitStr, Meta, Result, Token};
|
|
|
|
fn parse_attr<const LEN: usize>(path: [&'static str; LEN], attr: &Attribute) -> Option<LitStr> {
|
|
if let Meta::NameValue(name_value) = &attr.meta {
|
|
let path_idents = name_value.path.segments.iter().map(|segment| &segment.ident);
|
|
|
|
if itertools::equal(path_idents, path)
|
|
&& let Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) = &name_value.value
|
|
{
|
|
return Some(s.clone());
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
struct ClippyLint {
|
|
attrs: Vec<Attribute>,
|
|
explanation: String,
|
|
name: Ident,
|
|
category: Ident,
|
|
description: LitStr,
|
|
}
|
|
|
|
impl Parse for ClippyLint {
|
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
|
let attrs = input.call(Attribute::parse_outer)?;
|
|
|
|
let mut in_code = false;
|
|
let mut explanation = String::new();
|
|
let mut version = None;
|
|
for attr in &attrs {
|
|
if let Some(lit) = parse_attr(["doc"], attr) {
|
|
let value = lit.value();
|
|
let line = value.strip_prefix(' ').unwrap_or(&value);
|
|
|
|
if line.starts_with("```") {
|
|
explanation += "```\n";
|
|
in_code = !in_code;
|
|
} else if !(in_code && line.starts_with("# ")) {
|
|
explanation += line;
|
|
explanation.push('\n');
|
|
}
|
|
} else if let Some(lit) = parse_attr(["clippy", "version"], attr) {
|
|
if let Some(duplicate) = version.replace(lit) {
|
|
return Err(Error::new_spanned(duplicate, "duplicate clippy::version"));
|
|
}
|
|
} else {
|
|
return Err(Error::new_spanned(attr, "unexpected attribute"));
|
|
}
|
|
}
|
|
|
|
input.parse::<Token![pub]>()?;
|
|
let name = input.parse()?;
|
|
input.parse::<Token![,]>()?;
|
|
|
|
let category = input.parse()?;
|
|
input.parse::<Token![,]>()?;
|
|
|
|
let description = input.parse()?;
|
|
|
|
Ok(Self {
|
|
attrs,
|
|
explanation,
|
|
name,
|
|
category,
|
|
description,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Macro used to declare a Clippy lint.
|
|
///
|
|
/// Every lint declaration consists of 4 parts:
|
|
///
|
|
/// 1. The documentation, which is used for the website and `cargo clippy --explain`
|
|
/// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions.
|
|
/// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or
|
|
/// `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of.
|
|
/// 4. The `description` that contains a short explanation on what's wrong with code where the lint
|
|
/// is triggered.
|
|
///
|
|
/// Currently the categories `style`, `correctness`, `suspicious`, `complexity` and `perf` are
|
|
/// enabled by default. As said in the README.md of this repository, if the lint level mapping
|
|
/// changes, please update README.md.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```ignore
|
|
/// use rustc_session::declare_tool_lint;
|
|
///
|
|
/// declare_clippy_lint! {
|
|
/// /// ### What it does
|
|
/// /// Checks for ... (describe what the lint matches).
|
|
/// ///
|
|
/// /// ### Why is this bad?
|
|
/// /// Supply the reason for linting the code.
|
|
/// ///
|
|
/// /// ### Example
|
|
/// /// ```rust
|
|
/// /// Insert a short example of code that triggers the lint
|
|
/// /// ```
|
|
/// ///
|
|
/// /// Use instead:
|
|
/// /// ```rust
|
|
/// /// Insert a short example of improved code that doesn't trigger the lint
|
|
/// /// ```
|
|
/// #[clippy::version = "1.65.0"]
|
|
/// pub LINT_NAME,
|
|
/// pedantic,
|
|
/// "description"
|
|
/// }
|
|
/// ```
|
|
/// [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
|
|
#[proc_macro]
|
|
pub fn declare_clippy_lint(input: TokenStream) -> TokenStream {
|
|
let ClippyLint {
|
|
attrs,
|
|
explanation,
|
|
name,
|
|
category,
|
|
description,
|
|
} = parse_macro_input!(input as ClippyLint);
|
|
|
|
let mut category = category.to_string();
|
|
|
|
let level = format_ident!(
|
|
"{}",
|
|
match category.as_str() {
|
|
"correctness" => "Deny",
|
|
"style" | "suspicious" | "complexity" | "perf" => "Warn",
|
|
"pedantic" | "restriction" | "cargo" | "nursery" | "internal" => "Allow",
|
|
_ => panic!("unknown category {category}"),
|
|
},
|
|
);
|
|
|
|
let info_name = format_ident!("{name}_INFO");
|
|
|
|
(&mut category[0..1]).make_ascii_uppercase();
|
|
let category_variant = format_ident!("{category}");
|
|
|
|
let output = quote! {
|
|
rustc_session::declare_tool_lint! {
|
|
#(#attrs)*
|
|
pub clippy::#name,
|
|
#level,
|
|
#description,
|
|
report_in_external_macro: true
|
|
}
|
|
|
|
pub(crate) static #info_name: &'static crate::LintInfo = &crate::LintInfo {
|
|
lint: &#name,
|
|
category: crate::LintCategory::#category_variant,
|
|
explanation: #explanation,
|
|
};
|
|
};
|
|
|
|
TokenStream::from(output)
|
|
}
|