1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//! Defines the `#[typeshare]` attribute.

extern crate proc_macro;
use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{parse, Attribute, Data, DeriveInput, Fields};

/// Marks a type as a type shared across the FFI boundary using typeshare.
///
/// The `typeshare` program will process all the types with this attribute. It will ignore all types
/// that do not have this attribute.
///
/// # Example
///
/// ```ignore
///  use typeshare::typeshare;
///
///  #[typeshare]
///  pub struct Person {
///      name: String,
///      email: String,
///  }
/// ```
///
/// If the file above is `src/person.rs`, then `typeshare --lang=typescript src/person.rs` would
/// generate the typescript bindings for the `Person` type.
///
/// # Ignoring enum variants
/// If you don't want to typeshare a certain enum variant, just put a `#[typeshare(skip)]` attribute on
/// that variant.
/// ```ignore
/// use typeshare::typeshare;
///
/// #[typeshare]
/// pub enum SomeEnum {
///     A,
///     #[typeshare(skip)]
///     B, // <- this variant will not be included in the typeshared file(s)
///     C,
/// }
/// ```
#[proc_macro_attribute]
pub fn typeshare(_attr: TokenStream, item: TokenStream) -> TokenStream {
    if let Ok(mut item) = parse::<DeriveInput>(item.clone()) {
        // We need to remove the #[typeshare] attribute from all data members so the compiler doesn't throw an error.
        strip_configuration_attribute(&mut item);
        TokenStream::from(item.to_token_stream())
    } else {
        item
    }
}

fn strip_configuration_attribute(item: &mut DeriveInput) {
    fn remove_configuration_from_attributes(attributes: &mut Vec<Attribute>) {
        const CONFIG_ATTRIBUTE_NAME: &str = "typeshare";

        attributes.retain(|x| x.path().to_token_stream().to_string() != CONFIG_ATTRIBUTE_NAME);
    }

    fn remove_configuration_from_fields(fields: &mut Fields) {
        for field in fields.iter_mut() {
            remove_configuration_from_attributes(&mut field.attrs);
        }
    }

    match item.data {
        Data::Enum(ref mut data_enum) => {
            for variant in data_enum.variants.iter_mut() {
                remove_configuration_from_attributes(&mut variant.attrs);
                remove_configuration_from_fields(&mut variant.fields);
            }
        }
        Data::Struct(ref mut data_struct) => {
            remove_configuration_from_fields(&mut data_struct.fields);
        }
        Data::Union(ref mut data_union) => {
            for field in data_union.fields.named.iter_mut() {
                remove_configuration_from_attributes(&mut field.attrs);
            }
        }
    };
}