]> git.lizzy.rs Git - rust.git/blob - declare_clippy_lint/src/lib.rs
Auto merge of #9920 - naosense:suppress_lint_in_const, r=xFrednet
[rust.git] / declare_clippy_lint / src / lib.rs
1 #![feature(let_chains)]
2 #![cfg_attr(feature = "deny-warnings", deny(warnings))]
3 // warn on lints, that are included in `rust-lang/rust`s bootstrap
4 #![warn(rust_2018_idioms, unused_lifetimes)]
5
6 use proc_macro::TokenStream;
7 use quote::{format_ident, quote};
8 use syn::parse::{Parse, ParseStream};
9 use syn::{parse_macro_input, Attribute, Error, Ident, Lit, LitStr, Meta, Result, Token};
10
11 fn parse_attr<const LEN: usize>(path: [&'static str; LEN], attr: &Attribute) -> Option<LitStr> {
12     if let Meta::NameValue(name_value) = attr.parse_meta().ok()? {
13         let path_idents = name_value.path.segments.iter().map(|segment| &segment.ident);
14
15         if itertools::equal(path_idents, path)
16             && let Lit::Str(lit) = name_value.lit
17         {
18             return Some(lit);
19         }
20     }
21
22     None
23 }
24
25 struct ClippyLint {
26     attrs: Vec<Attribute>,
27     explanation: String,
28     name: Ident,
29     category: Ident,
30     description: LitStr,
31 }
32
33 impl Parse for ClippyLint {
34     fn parse(input: ParseStream<'_>) -> Result<Self> {
35         let attrs = input.call(Attribute::parse_outer)?;
36
37         let mut in_code = false;
38         let mut explanation = String::new();
39         let mut version = None;
40         for attr in &attrs {
41             if let Some(lit) = parse_attr(["doc"], attr) {
42                 let value = lit.value();
43                 let line = value.strip_prefix(' ').unwrap_or(&value);
44
45                 if line.starts_with("```") {
46                     explanation += "```\n";
47                     in_code = !in_code;
48                 } else if !(in_code && line.starts_with("# ")) {
49                     explanation += line;
50                     explanation.push('\n');
51                 }
52             } else if let Some(lit) = parse_attr(["clippy", "version"], attr) {
53                 if let Some(duplicate) = version.replace(lit) {
54                     return Err(Error::new_spanned(duplicate, "duplicate clippy::version"));
55                 }
56             } else {
57                 return Err(Error::new_spanned(attr, "unexpected attribute"));
58             }
59         }
60
61         input.parse::<Token![pub]>()?;
62         let name = input.parse()?;
63         input.parse::<Token![,]>()?;
64
65         let category = input.parse()?;
66         input.parse::<Token![,]>()?;
67
68         let description = input.parse()?;
69
70         Ok(Self {
71             attrs,
72             explanation,
73             name,
74             category,
75             description,
76         })
77     }
78 }
79
80 /// Macro used to declare a Clippy lint.
81 ///
82 /// Every lint declaration consists of 4 parts:
83 ///
84 /// 1. The documentation, which is used for the website and `cargo clippy --explain`
85 /// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions.
86 /// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or
87 ///    `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of.
88 /// 4. The `description` that contains a short explanation on what's wrong with code where the
89 ///    lint is triggered.
90 ///
91 /// Currently the categories `style`, `correctness`, `suspicious`, `complexity` and `perf` are
92 /// enabled by default. As said in the README.md of this repository, if the lint level mapping
93 /// changes, please update README.md.
94 ///
95 /// # Example
96 ///
97 /// ```
98 /// use rustc_session::declare_tool_lint;
99 ///
100 /// declare_clippy_lint! {
101 ///     /// ### What it does
102 ///     /// Checks for ... (describe what the lint matches).
103 ///     ///
104 ///     /// ### Why is this bad?
105 ///     /// Supply the reason for linting the code.
106 ///     ///
107 ///     /// ### Example
108 ///     /// ```rust
109 ///     /// Insert a short example of code that triggers the lint
110 ///     /// ```
111 ///     ///
112 ///     /// Use instead:
113 ///     /// ```rust
114 ///     /// Insert a short example of improved code that doesn't trigger the lint
115 ///     /// ```
116 ///     #[clippy::version = "1.65.0"]
117 ///     pub LINT_NAME,
118 ///     pedantic,
119 ///     "description"
120 /// }
121 /// ```
122 /// [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
123 #[proc_macro]
124 pub fn declare_clippy_lint(input: TokenStream) -> TokenStream {
125     let ClippyLint {
126         attrs,
127         explanation,
128         name,
129         category,
130         description,
131     } = parse_macro_input!(input as ClippyLint);
132
133     let mut category = category.to_string();
134
135     let level = format_ident!(
136         "{}",
137         match category.as_str() {
138             "correctness" => "Deny",
139             "style" | "suspicious" | "complexity" | "perf" | "internal_warn" => "Warn",
140             "pedantic" | "restriction" | "cargo" | "nursery" | "internal" => "Allow",
141             _ => panic!("unknown category {category}"),
142         },
143     );
144
145     let info = if category == "internal_warn" {
146         None
147     } else {
148         let info_name = format_ident!("{name}_INFO");
149
150         (&mut category[0..1]).make_ascii_uppercase();
151         let category_variant = format_ident!("{category}");
152
153         Some(quote! {
154             pub(crate) static #info_name: &'static crate::LintInfo = &crate::LintInfo {
155                 lint: &#name,
156                 category: crate::LintCategory::#category_variant,
157                 explanation: #explanation,
158             };
159         })
160     };
161
162     let output = quote! {
163         declare_tool_lint! {
164             #(#attrs)*
165             pub clippy::#name,
166             #level,
167             #description,
168             report_in_external_macro: true
169         }
170
171         #info
172     };
173
174     TokenStream::from(output)
175 }