aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay <[email protected]>2021-09-13 17:49:18 +0100
committerAkshay <[email protected]>2021-09-13 17:49:18 +0100
commit7a3c3822ba4a368b0475bc5de89ced78fa8b3cb5 (patch)
tree6c7c9d658958af3d5a7f9727202926421efd2e6a
parent171a4fa4d15c4be14d63fdc5d258610bb26bd162 (diff)
add proc-macro to define lint
-rw-r--r--macros/Cargo.toml9
-rw-r--r--macros/src/lib.rs134
2 files changed, 138 insertions, 5 deletions
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
index 6a47e03..b2027fc 100644
--- a/macros/Cargo.toml
+++ b/macros/Cargo.toml
@@ -6,3 +6,12 @@ edition = "2018"
6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 7
8[dependencies] 8[dependencies]
9quote = "1.0"
10proc-macro2 = "1.0.27"
11
12[dependencies.syn]
13version = "1.0"
14features = [ "full" ]
15
16[lib]
17proc-macro = true
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index 31e1bb2..a420d56 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -1,7 +1,131 @@
1#[cfg(test)] 1use std::collections::HashMap;
2mod tests { 2
3 #[test] 3use proc_macro::TokenStream;
4 fn it_works() { 4use proc_macro2::TokenStream as TokenStream2;
5 assert_eq!(2 + 2, 4); 5
6use quote::{format_ident, quote};
7use syn::{
8 parse::{Parse, ParseStream, Result as ParseResult},
9 parse_macro_input,
10 punctuated::Punctuated,
11 Ident, ItemStruct, Lit, Path, Token,
12};
13
14struct KeyValue {
15 key: Ident,
16 _eq: Token![=],
17 value: Lit,
18}
19
20impl Parse for KeyValue {
21 fn parse(input: ParseStream) -> ParseResult<Self> {
22 Ok(Self {
23 key: input.parse()?,
24 _eq: input.parse()?,
25 value: input.parse()?,
26 })
27 }
28}
29
30struct LintMeta(HashMap<Ident, Lit>);
31
32impl Parse for LintMeta {
33 fn parse(input: ParseStream) -> ParseResult<Self> {
34 Ok(Self(
35 Punctuated::<KeyValue, Token![,]>::parse_terminated(input)?
36 .into_iter()
37 .map(|item| (item.key, item.value))
38 .collect(),
39 ))
40 }
41}
42
43fn generate_self_impl(struct_name: &Ident) -> TokenStream2 {
44 quote! {
45 impl #struct_name {
46 pub fn new() -> Box<Self> {
47 Box::new(Self)
48 }
49 }
50 }
51}
52
53fn generate_meta_impl(struct_name: &Ident, meta: &LintMeta) -> TokenStream2 {
54 let name_fn = generate_name_fn(meta);
55 let note_fn = generate_note_fn(meta);
56 let match_with_fn = generate_match_with_fn(meta);
57 quote! {
58 impl Metadata for #struct_name {
59 #name_fn
60 #note_fn
61 #match_with_fn
62 }
6 } 63 }
7} 64}
65
66fn generate_name_fn(meta: &LintMeta) -> TokenStream2 {
67 let name = meta
68 .0
69 .get(&format_ident!("name"))
70 .unwrap_or_else(|| panic!("`name` not present"));
71 quote! {
72 fn name(&self) -> &str {
73 #name
74 }
75 }
76}
77
78fn generate_note_fn(meta: &LintMeta) -> TokenStream2 {
79 let note = meta
80 .0
81 .get(&format_ident!("note"))
82 .unwrap_or_else(|| panic!("`note` not present"));
83 quote! {
84 fn note(&self) -> &str {
85 #note
86 }
87 }
88}
89
90fn generate_match_with_fn(meta: &LintMeta) -> TokenStream2 {
91 let match_with_lit = meta
92 .0
93 .get(&format_ident!("match_with"))
94 .unwrap_or_else(|| panic!("`match_with` not present"));
95 if let Lit::Str(match_with) = match_with_lit {
96 let path: Path = match_with
97 .parse()
98 .ok()
99 .unwrap_or_else(|| panic!("`match_with` does not contain valid path"));
100 quote! {
101 fn match_with(&self, with: &SyntaxKind) -> bool {
102 *with == #path
103 }
104 }
105 } else {
106 panic!("`match_with` has non-literal value")
107 }
108}
109
110#[proc_macro_attribute]
111pub fn lint(attr: TokenStream, item: TokenStream) -> TokenStream {
112 let struct_item = parse_macro_input!(item as ItemStruct);
113 let meta = parse_macro_input!(attr as LintMeta);
114
115 let struct_name = &struct_item.ident;
116 let self_impl = generate_self_impl(struct_name);
117 let meta_impl = generate_meta_impl(struct_name, &meta);
118 (quote! {
119 #struct_item
120
121 ::lazy_static::lazy_static! {
122 pub static ref LINT: Box<dyn crate::Lint> = #struct_name::new();
123 }
124
125 #self_impl
126 #meta_impl
127
128 impl Lint for #struct_name {}
129 })
130 .into()
131}