diff options
Diffstat (limited to 'macros')
-rw-r--r-- | macros/Cargo.toml | 9 | ||||
-rw-r--r-- | macros/src/lib.rs | 134 |
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] |
9 | quote = "1.0" | ||
10 | proc-macro2 = "1.0.27" | ||
11 | |||
12 | [dependencies.syn] | ||
13 | version = "1.0" | ||
14 | features = [ "full" ] | ||
15 | |||
16 | [lib] | ||
17 | proc-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)] | 1 | use std::collections::HashMap; |
2 | mod tests { | 2 | |
3 | #[test] | 3 | use proc_macro::TokenStream; |
4 | fn it_works() { | 4 | use proc_macro2::TokenStream as TokenStream2; |
5 | assert_eq!(2 + 2, 4); | 5 | |
6 | use quote::{format_ident, quote}; | ||
7 | use syn::{ | ||
8 | parse::{Parse, ParseStream, Result as ParseResult}, | ||
9 | parse_macro_input, | ||
10 | punctuated::Punctuated, | ||
11 | Ident, ItemStruct, Lit, Path, Token, | ||
12 | }; | ||
13 | |||
14 | struct KeyValue { | ||
15 | key: Ident, | ||
16 | _eq: Token![=], | ||
17 | value: Lit, | ||
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, Lit>); | ||
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 | |||
43 | fn 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 | |||
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 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 | |||
66 | fn 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 | |||
78 | fn 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 | |||
90 | fn 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] | ||
111 | pub 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 | } | ||