diff options
Diffstat (limited to 'macros')
-rw-r--r-- | macros/src/explain.rs | 28 | ||||
-rw-r--r-- | macros/src/lib.rs | 173 | ||||
-rw-r--r-- | macros/src/metadata.rs | 177 |
3 files changed, 216 insertions, 162 deletions
diff --git a/macros/src/explain.rs b/macros/src/explain.rs new file mode 100644 index 0000000..41dc5d4 --- /dev/null +++ b/macros/src/explain.rs | |||
@@ -0,0 +1,28 @@ | |||
1 | use proc_macro2::TokenStream as TokenStream2; | ||
2 | use quote::quote; | ||
3 | use syn::{ItemStruct, Lit, Meta, MetaNameValue}; | ||
4 | |||
5 | pub fn generate_explain_impl(struct_item: &ItemStruct) -> TokenStream2 { | ||
6 | let struct_name = &struct_item.ident; | ||
7 | let explain = struct_item | ||
8 | .attrs | ||
9 | .iter() | ||
10 | .filter_map(|attr| match attr.parse_meta().ok() { | ||
11 | Some(Meta::NameValue(MetaNameValue { | ||
12 | path, | ||
13 | lit: Lit::Str(str_lit), | ||
14 | .. | ||
15 | })) if path.is_ident("doc") => Some(str_lit.value()), | ||
16 | _ => None, | ||
17 | }) | ||
18 | .map(|s| s.strip_prefix(' ').unwrap_or(&s).to_owned()) | ||
19 | .collect::<Vec<_>>() | ||
20 | .join("\n"); | ||
21 | quote! { | ||
22 | impl crate::Explain for #struct_name { | ||
23 | fn explanation(&self) -> &'static str { | ||
24 | #explain | ||
25 | } | ||
26 | } | ||
27 | } | ||
28 | } | ||
diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 127b4cb..86fa509 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs | |||
@@ -1,44 +1,12 @@ | |||
1 | use std::collections::HashMap; | 1 | mod explain; |
2 | mod metadata; | ||
2 | 3 | ||
4 | use explain::generate_explain_impl; | ||
5 | use metadata::{generate_meta_impl, RawLintMeta}; | ||
3 | use proc_macro::TokenStream; | 6 | use proc_macro::TokenStream; |
4 | use proc_macro2::TokenStream as TokenStream2; | 7 | use proc_macro2::TokenStream as TokenStream2; |
5 | 8 | use quote::quote; | |
6 | use quote::{format_ident, quote}; | 9 | use syn::{parse_macro_input, Ident, ItemStruct}; |
7 | use syn::{ | ||
8 | parse::{Parse, ParseStream, Result as ParseResult}, | ||
9 | parse_macro_input, | ||
10 | punctuated::Punctuated, | ||
11 | Expr, Ident, ItemStruct, Lit, Token, | ||
12 | }; | ||
13 | |||
14 | struct KeyValue { | ||
15 | key: Ident, | ||
16 | _eq: Token![=], | ||
17 | value: Expr, | ||
18 | } | ||
19 | |||
20 | impl 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 | |||
30 | struct LintMeta(HashMap<Ident, Expr>); | ||
31 | |||
32 | impl 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 | 10 | ||
43 | fn generate_self_impl(struct_name: &Ident) -> TokenStream2 { | 11 | fn generate_self_impl(struct_name: &Ident) -> TokenStream2 { |
44 | quote! { | 12 | quote! { |
@@ -50,136 +18,16 @@ fn generate_self_impl(struct_name: &Ident) -> TokenStream2 { | |||
50 | } | 18 | } |
51 | } | 19 | } |
52 | 20 | ||
53 | fn 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 code_fn = generate_code_fn(meta); | ||
57 | let report_fn = generate_report_fn(); | ||
58 | let match_with_fn = generate_match_with_fn(meta); | ||
59 | let match_kind = generate_match_kind(meta); | ||
60 | quote! { | ||
61 | impl Metadata for #struct_name { | ||
62 | #name_fn | ||
63 | #note_fn | ||
64 | #code_fn | ||
65 | #report_fn | ||
66 | #match_with_fn | ||
67 | #match_kind | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | |||
72 | fn generate_name_fn(meta: &LintMeta) -> TokenStream2 { | ||
73 | let name = meta | ||
74 | .0 | ||
75 | .get(&format_ident!("name")) | ||
76 | .unwrap_or_else(|| panic!("`name` not present")); | ||
77 | if let syn::Expr::Lit(name_lit) = name { | ||
78 | if let Lit::Str(name_str) = &name_lit.lit { | ||
79 | return quote! { | ||
80 | fn name() -> &'static str { | ||
81 | #name_str | ||
82 | } | ||
83 | }; | ||
84 | } | ||
85 | } | ||
86 | panic!("Invalid value for `name`"); | ||
87 | } | ||
88 | |||
89 | fn generate_note_fn(meta: &LintMeta) -> TokenStream2 { | ||
90 | let note = meta | ||
91 | .0 | ||
92 | .get(&format_ident!("note")) | ||
93 | .unwrap_or_else(|| panic!("`note` not present")); | ||
94 | if let syn::Expr::Lit(note_lit) = note { | ||
95 | if let Lit::Str(note_str) = ¬e_lit.lit { | ||
96 | return quote! { | ||
97 | fn note() -> &'static str { | ||
98 | #note_str | ||
99 | } | ||
100 | }; | ||
101 | } | ||
102 | } | ||
103 | panic!("Invalid value for `note`"); | ||
104 | } | ||
105 | |||
106 | fn generate_code_fn(meta: &LintMeta) -> TokenStream2 { | ||
107 | let code = meta | ||
108 | .0 | ||
109 | .get(&format_ident!("code")) | ||
110 | .unwrap_or_else(|| panic!("`code` not present")); | ||
111 | if let syn::Expr::Lit(code_lit) = code { | ||
112 | if let Lit::Int(code_int) = &code_lit.lit { | ||
113 | return quote! { | ||
114 | fn code() -> u32 { | ||
115 | #code_int | ||
116 | } | ||
117 | }; | ||
118 | } | ||
119 | } | ||
120 | panic!("Invalid value for `note`"); | ||
121 | } | ||
122 | |||
123 | fn generate_report_fn() -> TokenStream2 { | ||
124 | quote! { | ||
125 | fn report() -> Report { | ||
126 | Report::new(Self::note(), Self::code()) | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | |||
131 | fn generate_match_with_fn(meta: &LintMeta) -> TokenStream2 { | ||
132 | let match_with_lit = meta | ||
133 | .0 | ||
134 | .get(&format_ident!("match_with")) | ||
135 | .unwrap_or_else(|| panic!("`match_with` not present")); | ||
136 | if let syn::Expr::Path(match_path) = match_with_lit { | ||
137 | quote! { | ||
138 | fn match_with(&self, with: &SyntaxKind) -> bool { | ||
139 | #match_path == *with | ||
140 | } | ||
141 | } | ||
142 | } else if let syn::Expr::Array(array_expr) = match_with_lit { | ||
143 | quote! { | ||
144 | fn match_with(&self, with: &SyntaxKind) -> bool { | ||
145 | #array_expr.contains(with) | ||
146 | } | ||
147 | } | ||
148 | } else { | ||
149 | panic!("`match_with` has non-path value") | ||
150 | } | ||
151 | } | ||
152 | |||
153 | fn generate_match_kind(meta: &LintMeta) -> TokenStream2 { | ||
154 | let match_with_lit = meta | ||
155 | .0 | ||
156 | .get(&format_ident!("match_with")) | ||
157 | .unwrap_or_else(|| panic!("`match_with` not present")); | ||
158 | if let syn::Expr::Path(match_path) = match_with_lit { | ||
159 | quote! { | ||
160 | fn match_kind(&self) -> Vec<SyntaxKind> { | ||
161 | vec![#match_path] | ||
162 | } | ||
163 | } | ||
164 | } else if let syn::Expr::Array(array_expr) = match_with_lit { | ||
165 | quote! { | ||
166 | fn match_kind(&self) -> Vec<SyntaxKind> { | ||
167 | #array_expr.to_vec() | ||
168 | } | ||
169 | } | ||
170 | } else { | ||
171 | panic!("`match_with` has non-path value") | ||
172 | } | ||
173 | } | ||
174 | |||
175 | #[proc_macro_attribute] | 21 | #[proc_macro_attribute] |
176 | pub fn lint(attr: TokenStream, item: TokenStream) -> TokenStream { | 22 | pub fn lint(attr: TokenStream, item: TokenStream) -> TokenStream { |
177 | let struct_item = parse_macro_input!(item as ItemStruct); | 23 | let struct_item = parse_macro_input!(item as ItemStruct); |
178 | let meta = parse_macro_input!(attr as LintMeta); | 24 | let meta = parse_macro_input!(attr as RawLintMeta); |
179 | 25 | ||
180 | let struct_name = &struct_item.ident; | 26 | let struct_name = &struct_item.ident; |
181 | let self_impl = generate_self_impl(struct_name); | 27 | let self_impl = generate_self_impl(struct_name); |
182 | let meta_impl = generate_meta_impl(struct_name, &meta); | 28 | let meta_impl = generate_meta_impl(struct_name, &meta); |
29 | let explain_impl = generate_explain_impl(&struct_item); | ||
30 | |||
183 | (quote! { | 31 | (quote! { |
184 | #struct_item | 32 | #struct_item |
185 | 33 | ||
@@ -189,8 +37,9 @@ pub fn lint(attr: TokenStream, item: TokenStream) -> TokenStream { | |||
189 | 37 | ||
190 | #self_impl | 38 | #self_impl |
191 | #meta_impl | 39 | #meta_impl |
40 | #explain_impl | ||
192 | 41 | ||
193 | impl Lint for #struct_name {} | 42 | impl crate::Lint for #struct_name {} |
194 | }) | 43 | }) |
195 | .into() | 44 | .into() |
196 | } | 45 | } |
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 | } | ||