aboutsummaryrefslogtreecommitdiff
path: root/macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'macros/src')
-rw-r--r--macros/src/lib.rs134
1 files changed, 129 insertions, 5 deletions
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}