1 // Copyright (c) 2020 Google LLC All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 
5 use {
6     crate::errors::Errors,
7     proc_macro2::Span,
8     std::collections::hash_map::{Entry, HashMap},
9 };
10 
11 /// Attributes applied to a field of a `#![derive(FromArgs)]` struct.
12 #[derive(Default)]
13 pub struct FieldAttrs {
14     pub default: Option<syn::LitStr>,
15     pub description: Option<Description>,
16     pub from_str_fn: Option<syn::ExprPath>,
17     pub field_type: Option<FieldType>,
18     pub long: Option<syn::LitStr>,
19     pub short: Option<syn::LitChar>,
20     pub arg_name: Option<syn::LitStr>,
21     pub greedy: Option<syn::Path>,
22     pub hidden_help: bool,
23 }
24 
25 /// The purpose of a particular field on a `#![derive(FromArgs)]` struct.
26 #[derive(Copy, Clone, Eq, PartialEq)]
27 pub enum FieldKind {
28     /// Switches are booleans that are set to "true" by passing the flag.
29     Switch,
30     /// Options are `--key value`. They may be optional (using `Option`),
31     /// or repeating (using `Vec`), or required (neither `Option` nor `Vec`)
32     Option,
33     /// Subcommand fields (of which there can be at most one) refer to enums
34     /// containing one of several potential subcommands. They may be optional
35     /// (using `Option`) or required (no `Option`).
36     SubCommand,
37     /// Positional arguments are parsed literally if the input
38     /// does not begin with `-` or `--` and is not a subcommand.
39     /// They are parsed in declaration order, and only the last positional
40     /// argument in a type may be an `Option`, `Vec`, or have a default value.
41     Positional,
42 }
43 
44 /// The type of a field on a `#![derive(FromArgs)]` struct.
45 ///
46 /// This is a simple wrapper around `FieldKind` which includes the `syn::Ident`
47 /// of the attribute containing the field kind.
48 pub struct FieldType {
49     pub kind: FieldKind,
50     pub ident: syn::Ident,
51 }
52 
53 /// A description of a `#![derive(FromArgs)]` struct.
54 ///
55 /// Defaults to the docstring if one is present, or `#[argh(description = "...")]`
56 /// if one is provided.
57 pub struct Description {
58     /// Whether the description was an explicit annotation or whether it was a doc string.
59     pub explicit: bool,
60     pub content: syn::LitStr,
61 }
62 
63 impl FieldAttrs {
parse(errors: &Errors, field: &syn::Field) -> Self64     pub fn parse(errors: &Errors, field: &syn::Field) -> Self {
65         let mut this = Self::default();
66 
67         for attr in &field.attrs {
68             if is_doc_attr(attr) {
69                 parse_attr_doc(errors, attr, &mut this.description);
70                 continue;
71             }
72 
73             let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
74                 ml
75             } else {
76                 continue;
77             };
78 
79             for meta in ml {
80                 let name = meta.path();
81                 if name.is_ident("arg_name") {
82                     if let Some(m) = errors.expect_meta_name_value(&meta) {
83                         this.parse_attr_arg_name(errors, m);
84                     }
85                 } else if name.is_ident("default") {
86                     if let Some(m) = errors.expect_meta_name_value(&meta) {
87                         this.parse_attr_default(errors, m);
88                     }
89                 } else if name.is_ident("description") {
90                     if let Some(m) = errors.expect_meta_name_value(&meta) {
91                         parse_attr_description(errors, m, &mut this.description);
92                     }
93                 } else if name.is_ident("from_str_fn") {
94                     if let Some(m) = errors.expect_meta_list(&meta) {
95                         this.parse_attr_from_str_fn(errors, m);
96                     }
97                 } else if name.is_ident("long") {
98                     if let Some(m) = errors.expect_meta_name_value(&meta) {
99                         this.parse_attr_long(errors, m);
100                     }
101                 } else if name.is_ident("option") {
102                     parse_attr_field_type(errors, &meta, FieldKind::Option, &mut this.field_type);
103                 } else if name.is_ident("short") {
104                     if let Some(m) = errors.expect_meta_name_value(&meta) {
105                         this.parse_attr_short(errors, m);
106                     }
107                 } else if name.is_ident("subcommand") {
108                     parse_attr_field_type(
109                         errors,
110                         &meta,
111                         FieldKind::SubCommand,
112                         &mut this.field_type,
113                     );
114                 } else if name.is_ident("switch") {
115                     parse_attr_field_type(errors, &meta, FieldKind::Switch, &mut this.field_type);
116                 } else if name.is_ident("positional") {
117                     parse_attr_field_type(
118                         errors,
119                         &meta,
120                         FieldKind::Positional,
121                         &mut this.field_type,
122                     );
123                 } else if name.is_ident("greedy") {
124                     this.greedy = Some(name.clone());
125                 } else if name.is_ident("hidden_help") {
126                     this.hidden_help = true;
127                 } else {
128                     errors.err(
129                         &meta,
130                         concat!(
131                             "Invalid field-level `argh` attribute\n",
132                             "Expected one of: `arg_name`, `default`, `description`, `from_str_fn`, `greedy`, ",
133                             "`long`, `option`, `short`, `subcommand`, `switch`, `hidden_help`",
134                         ),
135                     );
136                 }
137             }
138         }
139 
140         if let (Some(default), Some(field_type)) = (&this.default, &this.field_type) {
141             match field_type.kind {
142                 FieldKind::Option | FieldKind::Positional => {}
143                 FieldKind::SubCommand | FieldKind::Switch => errors.err(
144                     default,
145                     "`default` may only be specified on `#[argh(option)]` \
146                      or `#[argh(positional)]` fields",
147                 ),
148             }
149         }
150 
151         match (&this.greedy, this.field_type.as_ref().map(|f| f.kind)) {
152             (Some(_), Some(FieldKind::Positional)) => {}
153             (Some(greedy), Some(_)) => errors.err(
154                 &greedy,
155                 "`greedy` may only be specified on `#[argh(positional)]` \
156                     fields",
157             ),
158             _ => {}
159         }
160 
161         if let Some(d) = &this.description {
162             check_option_description(errors, d.content.value().trim(), d.content.span());
163         }
164 
165         this
166     }
167 
parse_attr_from_str_fn(&mut self, errors: &Errors, m: &syn::MetaList)168     fn parse_attr_from_str_fn(&mut self, errors: &Errors, m: &syn::MetaList) {
169         parse_attr_fn_name(errors, m, "from_str_fn", &mut self.from_str_fn)
170     }
171 
parse_attr_default(&mut self, errors: &Errors, m: &syn::MetaNameValue)172     fn parse_attr_default(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
173         parse_attr_single_string(errors, m, "default", &mut self.default);
174     }
175 
parse_attr_arg_name(&mut self, errors: &Errors, m: &syn::MetaNameValue)176     fn parse_attr_arg_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
177         parse_attr_single_string(errors, m, "arg_name", &mut self.arg_name);
178     }
179 
parse_attr_long(&mut self, errors: &Errors, m: &syn::MetaNameValue)180     fn parse_attr_long(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
181         parse_attr_single_string(errors, m, "long", &mut self.long);
182         let long = self.long.as_ref().unwrap();
183         let value = long.value();
184         check_long_name(errors, long, &value);
185     }
186 
parse_attr_short(&mut self, errors: &Errors, m: &syn::MetaNameValue)187     fn parse_attr_short(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
188         if let Some(first) = &self.short {
189             errors.duplicate_attrs("short", first, m);
190         } else if let Some(lit_char) = errors.expect_lit_char(&m.value) {
191             self.short = Some(lit_char.clone());
192             if !lit_char.value().is_ascii() {
193                 errors.err(lit_char, "Short names must be ASCII");
194             }
195         }
196     }
197 }
198 
check_long_name(errors: &Errors, spanned: &impl syn::spanned::Spanned, value: &str)199 pub(crate) fn check_long_name(errors: &Errors, spanned: &impl syn::spanned::Spanned, value: &str) {
200     if !value.is_ascii() {
201         errors.err(spanned, "Long names must be ASCII");
202     }
203     if !value.chars().all(|c| c.is_lowercase() || c == '-' || c.is_ascii_digit()) {
204         errors.err(spanned, "Long names must be lowercase");
205     }
206 }
207 
parse_attr_fn_name( errors: &Errors, m: &syn::MetaList, attr_name: &str, slot: &mut Option<syn::ExprPath>, )208 fn parse_attr_fn_name(
209     errors: &Errors,
210     m: &syn::MetaList,
211     attr_name: &str,
212     slot: &mut Option<syn::ExprPath>,
213 ) {
214     if let Some(first) = slot {
215         errors.duplicate_attrs(attr_name, first, m);
216     }
217 
218     *slot = errors.ok(m.parse_args());
219 }
220 
parse_attr_field_type( errors: &Errors, meta: &syn::Meta, kind: FieldKind, slot: &mut Option<FieldType>, )221 fn parse_attr_field_type(
222     errors: &Errors,
223     meta: &syn::Meta,
224     kind: FieldKind,
225     slot: &mut Option<FieldType>,
226 ) {
227     if let Some(path) = errors.expect_meta_word(meta) {
228         if let Some(first) = slot {
229             errors.duplicate_attrs("field kind", &first.ident, path);
230         } else if let Some(word) = path.get_ident() {
231             *slot = Some(FieldType { kind, ident: word.clone() });
232         }
233     }
234 }
235 
236 // Whether the attribute is one like `#[<name> ...]`
is_matching_attr(name: &str, attr: &syn::Attribute) -> bool237 fn is_matching_attr(name: &str, attr: &syn::Attribute) -> bool {
238     attr.path().segments.len() == 1 && attr.path().segments[0].ident == name
239 }
240 
241 /// Checks for `#[doc ...]`, which is generated by doc comments.
is_doc_attr(attr: &syn::Attribute) -> bool242 fn is_doc_attr(attr: &syn::Attribute) -> bool {
243     is_matching_attr("doc", attr)
244 }
245 
246 /// Checks for `#[argh ...]`
is_argh_attr(attr: &syn::Attribute) -> bool247 fn is_argh_attr(attr: &syn::Attribute) -> bool {
248     is_matching_attr("argh", attr)
249 }
250 
251 /// Filters out non-`#[argh(...)]` attributes and converts to a sequence of `syn::Meta`.
argh_attr_to_meta_list( errors: &Errors, attr: &syn::Attribute, ) -> Option<impl IntoIterator<Item = syn::Meta>>252 fn argh_attr_to_meta_list(
253     errors: &Errors,
254     attr: &syn::Attribute,
255 ) -> Option<impl IntoIterator<Item = syn::Meta>> {
256     if !is_argh_attr(attr) {
257         return None;
258     }
259     let ml = errors.expect_meta_list(&attr.meta)?;
260     errors.ok(ml.parse_args_with(
261         syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
262     ))
263 }
264 
265 /// Represents a `#[derive(FromArgs)]` type's top-level attributes.
266 #[derive(Default)]
267 pub struct TypeAttrs {
268     pub is_subcommand: Option<syn::Ident>,
269     pub name: Option<syn::LitStr>,
270     pub description: Option<Description>,
271     pub examples: Vec<syn::LitStr>,
272     pub notes: Vec<syn::LitStr>,
273     pub error_codes: Vec<(syn::LitInt, syn::LitStr)>,
274 }
275 
276 impl TypeAttrs {
277     /// Parse top-level `#[argh(...)]` attributes
parse(errors: &Errors, derive_input: &syn::DeriveInput) -> Self278     pub fn parse(errors: &Errors, derive_input: &syn::DeriveInput) -> Self {
279         let mut this = TypeAttrs::default();
280 
281         for attr in &derive_input.attrs {
282             if is_doc_attr(attr) {
283                 parse_attr_doc(errors, attr, &mut this.description);
284                 continue;
285             }
286 
287             let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
288                 ml
289             } else {
290                 continue;
291             };
292 
293             for meta in ml {
294                 let name = meta.path();
295                 if name.is_ident("description") {
296                     if let Some(m) = errors.expect_meta_name_value(&meta) {
297                         parse_attr_description(errors, m, &mut this.description);
298                     }
299                 } else if name.is_ident("error_code") {
300                     if let Some(m) = errors.expect_meta_list(&meta) {
301                         this.parse_attr_error_code(errors, m);
302                     }
303                 } else if name.is_ident("example") {
304                     if let Some(m) = errors.expect_meta_name_value(&meta) {
305                         this.parse_attr_example(errors, m);
306                     }
307                 } else if name.is_ident("name") {
308                     if let Some(m) = errors.expect_meta_name_value(&meta) {
309                         this.parse_attr_name(errors, m);
310                     }
311                 } else if name.is_ident("note") {
312                     if let Some(m) = errors.expect_meta_name_value(&meta) {
313                         this.parse_attr_note(errors, m);
314                     }
315                 } else if name.is_ident("subcommand") {
316                     if let Some(ident) = errors.expect_meta_word(&meta).and_then(|p| p.get_ident())
317                     {
318                         this.parse_attr_subcommand(errors, ident);
319                     }
320                 } else {
321                     errors.err(
322                         &meta,
323                         concat!(
324                             "Invalid type-level `argh` attribute\n",
325                             "Expected one of: `description`, `error_code`, `example`, `name`, ",
326                             "`note`, `subcommand`",
327                         ),
328                     );
329                 }
330             }
331         }
332 
333         this.check_error_codes(errors);
334         this
335     }
336 
337     /// Checks that error codes are within range for `i32` and that they are
338     /// never duplicated.
check_error_codes(&self, errors: &Errors)339     fn check_error_codes(&self, errors: &Errors) {
340         // map from error code to index
341         let mut map: HashMap<u64, usize> = HashMap::new();
342         for (index, (lit_int, _lit_str)) in self.error_codes.iter().enumerate() {
343             let value = match lit_int.base10_parse::<u64>() {
344                 Ok(v) => v,
345                 Err(e) => {
346                     errors.push(e);
347                     continue;
348                 }
349             };
350             if value > (std::i32::MAX as u64) {
351                 errors.err(lit_int, "Error code out of range for `i32`");
352             }
353             match map.entry(value) {
354                 Entry::Occupied(previous) => {
355                     let previous_index = *previous.get();
356                     let (previous_lit_int, _previous_lit_str) = &self.error_codes[previous_index];
357                     errors.err(lit_int, &format!("Duplicate error code {}", value));
358                     errors.err(
359                         previous_lit_int,
360                         &format!("Error code {} previously defined here", value),
361                     );
362                 }
363                 Entry::Vacant(slot) => {
364                     slot.insert(index);
365                 }
366             }
367         }
368     }
369 
parse_attr_error_code(&mut self, errors: &Errors, ml: &syn::MetaList)370     fn parse_attr_error_code(&mut self, errors: &Errors, ml: &syn::MetaList) {
371         errors.ok(ml.parse_args_with(|input: syn::parse::ParseStream| {
372             let err_code = input.parse()?;
373             input.parse::<syn::Token![,]>()?;
374             let err_msg = input.parse()?;
375             if let (Some(err_code), Some(err_msg)) =
376                 (errors.expect_lit_int(&err_code), errors.expect_lit_str(&err_msg))
377             {
378                 self.error_codes.push((err_code.clone(), err_msg.clone()));
379             }
380             Ok(())
381         }));
382     }
383 
parse_attr_example(&mut self, errors: &Errors, m: &syn::MetaNameValue)384     fn parse_attr_example(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
385         parse_attr_multi_string(errors, m, &mut self.examples)
386     }
387 
parse_attr_name(&mut self, errors: &Errors, m: &syn::MetaNameValue)388     fn parse_attr_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
389         parse_attr_single_string(errors, m, "name", &mut self.name);
390         if let Some(name) = &self.name {
391             if name.value() == "help" {
392                 errors.err(name, "Custom `help` commands are not supported.");
393             }
394         }
395     }
396 
parse_attr_note(&mut self, errors: &Errors, m: &syn::MetaNameValue)397     fn parse_attr_note(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
398         parse_attr_multi_string(errors, m, &mut self.notes)
399     }
400 
parse_attr_subcommand(&mut self, errors: &Errors, ident: &syn::Ident)401     fn parse_attr_subcommand(&mut self, errors: &Errors, ident: &syn::Ident) {
402         if let Some(first) = &self.is_subcommand {
403             errors.duplicate_attrs("subcommand", first, ident);
404         } else {
405             self.is_subcommand = Some(ident.clone());
406         }
407     }
408 }
409 
410 /// Represents an enum variant's attributes.
411 #[derive(Default)]
412 pub struct VariantAttrs {
413     pub is_dynamic: Option<syn::Path>,
414 }
415 
416 impl VariantAttrs {
417     /// Parse enum variant `#[argh(...)]` attributes
parse(errors: &Errors, variant: &syn::Variant) -> Self418     pub fn parse(errors: &Errors, variant: &syn::Variant) -> Self {
419         let mut this = VariantAttrs::default();
420 
421         let fields = match &variant.fields {
422             syn::Fields::Named(fields) => Some(&fields.named),
423             syn::Fields::Unnamed(fields) => Some(&fields.unnamed),
424             syn::Fields::Unit => None,
425         };
426 
427         for field in fields.into_iter().flatten() {
428             for attr in &field.attrs {
429                 if is_argh_attr(attr) {
430                     err_unused_enum_attr(errors, attr);
431                 }
432             }
433         }
434 
435         for attr in &variant.attrs {
436             let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
437                 ml
438             } else {
439                 continue;
440             };
441 
442             for meta in ml {
443                 let name = meta.path();
444                 if name.is_ident("dynamic") {
445                     if let Some(prev) = this.is_dynamic.as_ref() {
446                         errors.duplicate_attrs("dynamic", prev, &meta);
447                     } else {
448                         this.is_dynamic = errors.expect_meta_word(&meta).cloned();
449                     }
450                 } else {
451                     errors.err(
452                         &meta,
453                         "Invalid variant-level `argh` attribute\n\
454                          Variants can only have the #[argh(dynamic)] attribute.",
455                     );
456                 }
457             }
458         }
459 
460         this
461     }
462 }
463 
check_option_description(errors: &Errors, desc: &str, span: Span)464 fn check_option_description(errors: &Errors, desc: &str, span: Span) {
465     let chars = &mut desc.trim().chars();
466     match (chars.next(), chars.next()) {
467         (Some(x), _) if x.is_lowercase() => {}
468         // If both the first and second letter are not lowercase,
469         // this is likely an initialism which should be allowed.
470         (Some(x), Some(y)) if !x.is_lowercase() && !y.is_lowercase() => {}
471         _ => {
472             errors.err_span(span, "Descriptions must begin with a lowercase letter");
473         }
474     }
475 }
476 
parse_attr_single_string( errors: &Errors, m: &syn::MetaNameValue, name: &str, slot: &mut Option<syn::LitStr>, )477 fn parse_attr_single_string(
478     errors: &Errors,
479     m: &syn::MetaNameValue,
480     name: &str,
481     slot: &mut Option<syn::LitStr>,
482 ) {
483     if let Some(first) = slot {
484         errors.duplicate_attrs(name, first, m);
485     } else if let Some(lit_str) = errors.expect_lit_str(&m.value) {
486         *slot = Some(lit_str.clone());
487     }
488 }
489 
parse_attr_multi_string(errors: &Errors, m: &syn::MetaNameValue, list: &mut Vec<syn::LitStr>)490 fn parse_attr_multi_string(errors: &Errors, m: &syn::MetaNameValue, list: &mut Vec<syn::LitStr>) {
491     if let Some(lit_str) = errors.expect_lit_str(&m.value) {
492         list.push(lit_str.clone());
493     }
494 }
495 
parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Description>)496 fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Description>) {
497     let nv = if let Some(nv) = errors.expect_meta_name_value(&attr.meta) {
498         nv
499     } else {
500         return;
501     };
502 
503     // Don't replace an existing description.
504     if slot.as_ref().map(|d| d.explicit).unwrap_or(false) {
505         return;
506     }
507 
508     if let Some(lit_str) = errors.expect_lit_str(&nv.value) {
509         let lit_str = if let Some(previous) = slot {
510             let previous = &previous.content;
511             let previous_span = previous.span();
512             syn::LitStr::new(&(previous.value() + &*lit_str.value()), previous_span)
513         } else {
514             lit_str.clone()
515         };
516         *slot = Some(Description { explicit: false, content: lit_str });
517     }
518 }
519 
parse_attr_description(errors: &Errors, m: &syn::MetaNameValue, slot: &mut Option<Description>)520 fn parse_attr_description(errors: &Errors, m: &syn::MetaNameValue, slot: &mut Option<Description>) {
521     let lit_str =
522         if let Some(lit_str) = errors.expect_lit_str(&m.value) { lit_str } else { return };
523 
524     // Don't allow multiple explicit (non doc-comment) descriptions
525     if let Some(description) = slot {
526         if description.explicit {
527             errors.duplicate_attrs("description", &description.content, lit_str);
528         }
529     }
530 
531     *slot = Some(Description { explicit: true, content: lit_str.clone() });
532 }
533 
534 /// Checks that a `#![derive(FromArgs)]` enum has an `#[argh(subcommand)]`
535 /// attribute and that it does not have any other type-level `#[argh(...)]` attributes.
check_enum_type_attrs(errors: &Errors, type_attrs: &TypeAttrs, type_span: &Span)536 pub fn check_enum_type_attrs(errors: &Errors, type_attrs: &TypeAttrs, type_span: &Span) {
537     let TypeAttrs { is_subcommand, name, description, examples, notes, error_codes } = type_attrs;
538 
539     // Ensure that `#[argh(subcommand)]` is present.
540     if is_subcommand.is_none() {
541         errors.err_span(
542             *type_span,
543             concat!(
544                 "`#![derive(FromArgs)]` on `enum`s can only be used to enumerate subcommands.\n",
545                 "Consider adding `#[argh(subcommand)]` to the `enum` declaration.",
546             ),
547         );
548     }
549 
550     // Error on all other type-level attributes.
551     if let Some(name) = name {
552         err_unused_enum_attr(errors, name);
553     }
554     if let Some(description) = description {
555         if description.explicit {
556             err_unused_enum_attr(errors, &description.content);
557         }
558     }
559     if let Some(example) = examples.first() {
560         err_unused_enum_attr(errors, example);
561     }
562     if let Some(note) = notes.first() {
563         err_unused_enum_attr(errors, note);
564     }
565     if let Some(err_code) = error_codes.first() {
566         err_unused_enum_attr(errors, &err_code.0);
567     }
568 }
569 
err_unused_enum_attr(errors: &Errors, location: &impl syn::spanned::Spanned)570 fn err_unused_enum_attr(errors: &Errors, location: &impl syn::spanned::Spanned) {
571     errors.err(
572         location,
573         concat!(
574             "Unused `argh` attribute on `#![derive(FromArgs)]` enum. ",
575             "Such `enum`s can only be used to dispatch to subcommands, ",
576             "and should only contain the #[argh(subcommand)] attribute.",
577         ),
578     );
579 }
580