diff options
Diffstat (limited to 'macros/src/metadata.rs')
-rw-r--r-- | macros/src/metadata.rs | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/macros/src/metadata.rs b/macros/src/metadata.rs new file mode 100644 index 0000000..b41eb9c --- /dev/null +++ b/macros/src/metadata.rs | |||
@@ -0,0 +1,177 @@ | |||
1 | use std::collections::HashMap; | ||
2 | |||
3 | use proc_macro2::TokenStream as TokenStream2; | ||
4 | use quote::{format_ident, quote}; | ||
5 | use syn::{ | ||
6 | parse::{Parse, ParseStream, Result}, | ||
7 | punctuated::Punctuated, | ||
8 | Expr, ExprArray, Ident, Lit, Path, Token, | ||
9 | }; | ||
10 | |||
11 | struct KeyValue { | ||
12 | key: Ident, | ||
13 | _eq: Token![=], | ||
14 | value: Expr, | ||
15 | } | ||
16 | |||
17 | impl Parse for KeyValue { | ||
18 | fn parse(input: ParseStream) -> Result<Self> { | ||
19 | Ok(Self { | ||
20 | key: input.parse()?, | ||
21 | _eq: input.parse()?, | ||
22 | value: input.parse()?, | ||
23 | }) | ||
24 | } | ||
25 | } | ||
26 | |||
27 | pub struct RawLintMeta(HashMap<Ident, Expr>); | ||
28 | |||
29 | impl Parse for RawLintMeta { | ||
30 | fn parse(input: ParseStream) -> Result<Self> { | ||
31 | Ok(Self( | ||
32 | Punctuated::<KeyValue, Token![,]>::parse_terminated(input)? | ||
33 | .into_iter() | ||
34 | .map(|item| (item.key, item.value)) | ||
35 | .collect(), | ||
36 | )) | ||
37 | } | ||
38 | } | ||
39 | |||
40 | pub struct LintMeta<'μ> { | ||
41 | name: &'μ Lit, | ||
42 | note: &'μ Lit, | ||
43 | code: &'μ Lit, | ||
44 | match_with: MatchWith<'μ>, | ||
45 | } | ||
46 | |||
47 | enum MatchWith<'π> { | ||
48 | Path(&'π Path), | ||
49 | Array(&'π ExprArray), | ||
50 | } | ||
51 | |||
52 | fn extract<'λ>(id: &str, raw: &'λ RawLintMeta) -> &'λ Expr { | ||
53 | raw.0 | ||
54 | .get(&format_ident!("{}", id)) | ||
55 | .unwrap_or_else(|| panic!("`{}` not present", id)) | ||
56 | } | ||
57 | |||
58 | fn as_lit(e: &Expr) -> &Lit { | ||
59 | match e { | ||
60 | Expr::Lit(l) => &l.lit, | ||
61 | _ => panic!("expected a literal"), | ||
62 | } | ||
63 | } | ||
64 | |||
65 | impl<'μ> LintMeta<'μ> { | ||
66 | fn from_raw(raw: &'μ RawLintMeta) -> Self { | ||
67 | let name = as_lit(extract("name", raw)); | ||
68 | let note = as_lit(extract("note", raw)); | ||
69 | let code = as_lit(extract("code", raw)); | ||
70 | let match_with_expr = extract("match_with", raw); | ||
71 | let match_with = match match_with_expr { | ||
72 | Expr::Path(p) => MatchWith::Path(&p.path), | ||
73 | Expr::Array(a) => MatchWith::Array(a), | ||
74 | _ => panic!("`match_with` is neither a path nor an array"), | ||
75 | }; | ||
76 | Self { | ||
77 | name, | ||
78 | note, | ||
79 | code, | ||
80 | match_with, | ||
81 | } | ||
82 | } | ||
83 | |||
84 | fn generate_name_fn(&self) -> TokenStream2 { | ||
85 | let name_str = self.name; | ||
86 | return quote! { | ||
87 | fn name(&self) -> &'static str { | ||
88 | #name_str | ||
89 | } | ||
90 | }; | ||
91 | } | ||
92 | |||
93 | fn generate_note_fn(&self) -> TokenStream2 { | ||
94 | let note_str = self.note; | ||
95 | return quote! { | ||
96 | fn note(&self) -> &'static str { | ||
97 | #note_str | ||
98 | } | ||
99 | }; | ||
100 | } | ||
101 | |||
102 | fn generate_code_fn(&self) -> TokenStream2 { | ||
103 | let code_int = self.code; | ||
104 | return quote! { | ||
105 | fn code(&self) -> u32 { | ||
106 | #code_int | ||
107 | } | ||
108 | }; | ||
109 | } | ||
110 | |||
111 | fn generate_match_with_fn(&self) -> TokenStream2 { | ||
112 | match self.match_with { | ||
113 | MatchWith::Path(p) => { | ||
114 | quote! { | ||
115 | fn match_with(&self, with: &SyntaxKind) -> bool { | ||
116 | #p == *with | ||
117 | } | ||
118 | } | ||
119 | } | ||
120 | MatchWith::Array(a) => { | ||
121 | quote! { | ||
122 | fn match_with(&self, with: &SyntaxKind) -> bool { | ||
123 | #a.contains(with) | ||
124 | } | ||
125 | } | ||
126 | } | ||
127 | } | ||
128 | } | ||
129 | |||
130 | fn generate_match_kind_fn(&self) -> TokenStream2 { | ||
131 | match self.match_with { | ||
132 | MatchWith::Path(p) => { | ||
133 | quote! { | ||
134 | fn match_kind(&self) -> Vec<SyntaxKind> { | ||
135 | vec![#p] | ||
136 | } | ||
137 | } | ||
138 | } | ||
139 | MatchWith::Array(a) => { | ||
140 | quote! { | ||
141 | fn match_kind(&self) -> Vec<SyntaxKind> { | ||
142 | #a.to_vec() | ||
143 | } | ||
144 | } | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | |||
149 | fn generate_report_fn(&self) -> TokenStream2 { | ||
150 | quote! { | ||
151 | fn report(&self) -> crate::Report { | ||
152 | crate::Report::new(self.note(), self.code()) | ||
153 | } | ||
154 | } | ||
155 | } | ||
156 | } | ||
157 | |||
158 | pub fn generate_meta_impl(struct_name: &Ident, meta: &RawLintMeta) -> TokenStream2 { | ||
159 | let not_raw = LintMeta::from_raw(&meta); | ||
160 | let name_fn = not_raw.generate_name_fn(); | ||
161 | let note_fn = not_raw.generate_note_fn(); | ||
162 | let code_fn = not_raw.generate_code_fn(); | ||
163 | let match_with_fn = not_raw.generate_match_with_fn(); | ||
164 | let match_kind = not_raw.generate_match_kind_fn(); | ||
165 | let report_fn = not_raw.generate_report_fn(); | ||
166 | |||
167 | quote! { | ||
168 | impl crate::Metadata for #struct_name { | ||
169 | #name_fn | ||
170 | #note_fn | ||
171 | #code_fn | ||
172 | #match_with_fn | ||
173 | #match_kind | ||
174 | #report_fn | ||
175 | } | ||
176 | } | ||
177 | } | ||