aboutsummaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
Diffstat (limited to 'macros')
-rw-r--r--macros/src/explain.rs28
-rw-r--r--macros/src/lib.rs173
-rw-r--r--macros/src/metadata.rs177
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 @@
1use proc_macro2::TokenStream as TokenStream2;
2use quote::quote;
3use syn::{ItemStruct, Lit, Meta, MetaNameValue};
4
5pub 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 @@
1use std::collections::HashMap; 1mod explain;
2mod metadata;
2 3
4use explain::generate_explain_impl;
5use metadata::{generate_meta_impl, RawLintMeta};
3use proc_macro::TokenStream; 6use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2; 7use proc_macro2::TokenStream as TokenStream2;
5 8use quote::quote;
6use quote::{format_ident, quote}; 9use syn::{parse_macro_input, Ident, ItemStruct};
7use syn::{
8 parse::{Parse, ParseStream, Result as ParseResult},
9 parse_macro_input,
10 punctuated::Punctuated,
11 Expr, Ident, ItemStruct, Lit, Token,
12};
13
14struct KeyValue {
15 key: Ident,
16 _eq: Token![=],
17 value: Expr,
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, Expr>);
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 10
43fn generate_self_impl(struct_name: &Ident) -> TokenStream2 { 11fn 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
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 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
72fn 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
89fn 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) = &note_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
106fn 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
123fn generate_report_fn() -> TokenStream2 {
124 quote! {
125 fn report() -> Report {
126 Report::new(Self::note(), Self::code())
127 }
128 }
129}
130
131fn 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
153fn 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]
176pub fn lint(attr: TokenStream, item: TokenStream) -> TokenStream { 22pub 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 @@
1use std::collections::HashMap;
2
3use proc_macro2::TokenStream as TokenStream2;
4use quote::{format_ident, quote};
5use syn::{
6 parse::{Parse, ParseStream, Result},
7 punctuated::Punctuated,
8 Expr, ExprArray, Ident, Lit, Path, Token,
9};
10
11struct KeyValue {
12 key: Ident,
13 _eq: Token![=],
14 value: Expr,
15}
16
17impl 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
27pub struct RawLintMeta(HashMap<Ident, Expr>);
28
29impl 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
40pub struct LintMeta<'μ> {
41 name: &'μ Lit,
42 note: &'μ Lit,
43 code: &'μ Lit,
44 match_with: MatchWith<'μ>,
45}
46
47enum MatchWith<'π> {
48 Path(&'π Path),
49 Array(&'π ExprArray),
50}
51
52fn extract<'λ>(id: &str, raw: &'λ RawLintMeta) -> &'λ Expr {
53 raw.0
54 .get(&format_ident!("{}", id))
55 .unwrap_or_else(|| panic!("`{}` not present", id))
56}
57
58fn as_lit(e: &Expr) -> &Lit {
59 match e {
60 Expr::Lit(l) => &l.lit,
61 _ => panic!("expected a literal"),
62 }
63}
64
65impl<'μ> 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
158pub 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}