1use proc_macro::TokenStream;
4use quote;
5use std::collections::HashMap;
6use syn::{self, Attribute, Block, ItemFn};
7
8#[proc_macro_attribute]
44pub fn deps(args: TokenStream, input: TokenStream) -> TokenStream {
45 let args = proc_macro2::TokenStream::from(args);
46 let input = proc_macro2::TokenStream::from(input);
47
48 let arg_tokens = verify_args_text(args.clone());
51 let (target, prereqs) = verify_args_format(&arg_tokens);
52
53 let mut ast: ItemFn = syn::parse2(input).unwrap();
54
55 let body_orig = ast.block.as_ref();
56 let body_new: Block = syn::parse_quote! {{
57 struct Ticket;
58 impl Drop for Ticket {
59 fn drop(&mut self) {
60 let target = String::from(#target);
61 test_deps::target_completed(&target).unwrap();
62 }
63 }
64 let t = Ticket;
65 {
66 let prereqs: Vec<String> = vec![#(String::from(#prereqs)),*];
67 test_deps::ensure_prereqs(&prereqs).unwrap();
68 }
69 #body_orig
70 }};
71 *ast.block = body_new;
72
73 let mut gen = quote::quote! {
74 #ast
75 };
76
77 if is_ignored_test(&ast.attrs) {
78 let dummy_fn = format!("__dummy__{}", ast.sig.ident);
79 let dummy_fn_ident = proc_macro2::Ident::new(&dummy_fn, proc_macro2::Span::call_site());
80 gen = quote::quote! {
81 #[deps(#args)]
82 #[test]
83 fn #dummy_fn_ident(){}
84
85 #gen
86 };
87 }
88
89 gen.into()
90}
91
92fn verify_args_text(args: proc_macro2::TokenStream) -> Vec<String> {
98 let mut tokens = Vec::new();
99 let mut illegal_str = None;
100 for arg in args.into_iter() {
101 tokens.push(arg.to_string());
102 match arg {
103 proc_macro2::TokenTree::Ident(_) => {}
104 proc_macro2::TokenTree::Punct(pt) => {
105 if pt.as_char() != ':' {
106 if illegal_str.is_none() {
107 illegal_str = Some(pt.to_string());
108 }
109 }
110 }
111 _ => {
112 if illegal_str.is_none() {
113 illegal_str = Some(arg.to_string());
114 }
115 }
116 }
117 }
118 if let Some(x) = illegal_str {
119 panic!("Illegal string: {}", x);
120 }
121
122 tokens
123}
124
125fn verify_args_format(tokens: &Vec<String>) -> (&String, &[String]) {
126 if tokens.len() == 0 {
127 panic!("Illegal format: Missing target name");
128 } else {
129 let mut tokiter = tokens.iter();
130 let icolon = tokiter.position(|x| x == ":");
131 if let Some(i) = icolon {
132 if tokiter.position(|x| x == ":").is_some() {
133 panic!("Illegal format: Separator ':' should appear at most once");
134 }
135 if i == 0 {
136 panic!("Illegal format: Missing target name");
137 } else if i == tokens.len() - 1 {
138 panic!("Illegal format: Missing prereq names");
139 } else if i != 1 {
140 panic!("Illegal format: Target name should appear only once");
141 }
142 } else {
143 if tokens.len() != 1 {
144 panic!("Illegal format: Target name should appear only once");
145 }
146 }
147 }
148 let mut counts = HashMap::new();
149 for i in 1..tokens.len() {
150 if counts.insert(&tokens[i], ()).is_some() {
151 panic!("Illegal format: Duplicated prereq {}", tokens[i]);
152 }
153 }
154 if counts.contains_key(&tokens[0]) {
155 panic!(
156 "Illegal format: {} appears in both target and prereq",
157 tokens[0]
158 );
159 }
160
161 let mut sepiter = tokens.split(|s| s == ":");
162 let target = &sepiter.next().unwrap()[0];
163 let prereqs = sepiter.next().unwrap_or(&[]);
164 (target, prereqs)
165}
166
167fn is_ignored_test(attrs: &Vec<Attribute>) -> bool {
168 for attr in attrs {
169 if let Ok(m) = attr.parse_meta() {
170 if m.path().is_ident("ignore") {
171 return true;
172 }
173 }
174 }
175 false
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use std::str::FromStr;
182
183 #[test]
184 fn valid_names() {
185 let names = [
186 "a",
187 "ab",
188 "_",
189 "__",
190 "a_",
191 "_a",
192 "_a_",
193 "a_a",
194 "a0",
195 "a0a",
196 "_0",
197 "_0_",
198 "a0_",
199 "a_0",
200 "_a0",
201 "_0a",
202 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_",
203 ];
204 for name in &names {
205 assert_eq!(
206 vec![String::from(*name)],
207 verify_args_text(proc_macro2::TokenStream::from_str(name).unwrap())
208 );
209 }
210 }
211
212 #[test]
213 #[should_panic(expected = "Illegal string: 0a")]
214 fn invalid_name_starts_with_digit() {
215 verify_args_text(proc_macro2::TokenStream::from_str("0a").unwrap());
216 }
217
218 #[test]
219 #[should_panic(expected = "Illegal string: !")]
220 fn invalid_name_contains_special_char() {
221 verify_args_text(proc_macro2::TokenStream::from_str("a!").unwrap());
222 }
223
224 #[test]
225 fn valid_tokens() {
226 let one_tgt = vec![String::from("a")];
227 let one_tgt_one_prq = vec![String::from("a"), String::from(":"), String::from("b")];
228 let one_tgt_two_prqs = vec![
229 String::from("a"),
230 String::from(":"),
231 String::from("b"),
232 String::from("c"),
233 ];
234 assert_eq!(verify_args_format(&one_tgt), (&one_tgt[0], &one_tgt[1..]));
235 assert_eq!(
236 verify_args_format(&one_tgt_one_prq),
237 (&one_tgt_one_prq[0], &one_tgt_one_prq[2..])
238 );
239 assert_eq!(
240 verify_args_format(&one_tgt_two_prqs),
241 (&one_tgt_two_prqs[0], &one_tgt_two_prqs[2..])
242 );
243 }
244
245 #[test]
246 #[should_panic(expected = "Illegal format: Missing target name")]
247 fn invalid_tokens_empty() {
248 verify_args_format(&vec![]);
249 }
250
251 #[test]
252 #[should_panic(expected = "Illegal format: Missing target name")]
253 fn invalid_tokens_single_colon() {
254 verify_args_format(&vec![String::from(":")]);
255 }
256
257 #[test]
258 #[should_panic(expected = "Illegal format: Missing prereq names")]
259 fn invalid_tokens_no_prereq() {
260 verify_args_format(&vec![String::from("a"), String::from(":")]);
261 }
262
263 #[test]
264 #[should_panic(expected = "Illegal format: Missing target name")]
265 fn invalid_tokens_no_target() {
266 verify_args_format(&vec![String::from(":"), String::from("a")]);
267 }
268
269 #[test]
270 #[should_panic(expected = "Illegal format: Target name should appear only once")]
271 fn invalid_tokens_double_targets() {
272 verify_args_format(&vec![String::from("a"), String::from("b")]);
273 }
274
275 #[test]
276 #[should_panic(expected = "Illegal format: Separator ':' should appear at most once")]
277 fn invalid_tokens_double_colons() {
278 verify_args_format(&vec![String::from(":"), String::from(":")]);
279 }
280
281 #[test]
282 #[should_panic(expected = "Illegal format: Target name should appear only once")]
283 fn invalid_tokens_double_targets_with_prereq() {
284 verify_args_format(&vec![
285 String::from("a"),
286 String::from("b"),
287 String::from(":"),
288 String::from("c"),
289 ]);
290 }
291
292 #[test]
293 #[should_panic(expected = "Illegal format: Separator ':' should appear at most once")]
294 fn invalid_tokens_two_colons() {
295 verify_args_format(&vec![
296 String::from("a"),
297 String::from(":"),
298 String::from("b"),
299 String::from(":"),
300 ]);
301 }
302
303 #[test]
304 #[should_panic(expected = "Illegal format: Duplicated prereq b")]
305 fn invalid_tokens_dup_prereqs() {
306 verify_args_format(&vec![
307 String::from("a"),
308 String::from(":"),
309 String::from("b"),
310 String::from("b"),
311 ]);
312 }
313
314 #[test]
315 #[should_panic(expected = "Illegal format: a appears in both target and prereq")]
316 fn invalid_tokens_deps_loop() {
317 verify_args_format(&vec![
318 String::from("a"),
319 String::from(":"),
320 String::from("b"),
321 String::from("a"),
322 ]);
323 }
324}