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