typeshare_annotation/
lib.rs

1//! Defines the `#[typeshare]` attribute.
2
3extern crate proc_macro;
4use proc_macro::TokenStream;
5use quote::ToTokens;
6use syn::{parse, punctuated::Punctuated, Attribute, Data, DeriveInput, Fields, Meta, Token};
7
8/// Marks a type as a type shared across the FFI boundary using typeshare.
9///
10/// The `typeshare` program will process all the types with this attribute. It will ignore all types
11/// that do not have this attribute.
12///
13/// # Example
14///
15/// ```ignore
16///  use typeshare::typeshare;
17///
18///  #[typeshare]
19///  pub struct Person {
20///      name: String,
21///      email: String,
22///  }
23/// ```
24///
25/// If the file above is `src/person.rs`, then `typeshare --lang=typescript src/person.rs` would
26/// generate the typescript bindings for the `Person` type.
27///
28/// # Ignoring enum variants
29/// If you don't want to typeshare a certain enum variant, just put a `#[typeshare(skip)]` attribute on
30/// that variant.
31/// ```ignore
32/// use typeshare::typeshare;
33///
34/// #[typeshare]
35/// pub enum SomeEnum {
36///     A,
37///     #[typeshare(skip)]
38///     B, // <- this variant will not be included in the typeshared file(s)
39///     C,
40/// }
41/// ```
42#[proc_macro_attribute]
43pub fn typeshare(_attr: TokenStream, item: TokenStream) -> TokenStream {
44    if let Ok(mut item) = parse::<DeriveInput>(item.clone()) {
45        // We need to remove the #[typeshare] attribute from all data members so the compiler doesn't throw an error.
46        strip_configuration_attribute(&mut item);
47        TokenStream::from(item.to_token_stream())
48    } else {
49        item
50    }
51}
52
53const CONFIG_ATTRIBUTE_NAME: &str = "typeshare";
54
55fn is_typeshare_attribute(attribute: &Attribute) -> bool {
56    let has_cfg_attr = || {
57        if attribute.path().is_ident("cfg_attr") {
58            if let Ok(meta) =
59                attribute.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
60            {
61                return meta.into_iter().any(
62                    |meta| matches!(meta, Meta::List(meta_list) if meta_list.path.is_ident(CONFIG_ATTRIBUTE_NAME)),
63                );
64            }
65        }
66        false
67    };
68    attribute.path().is_ident(CONFIG_ATTRIBUTE_NAME) || has_cfg_attr()
69}
70
71fn strip_configuration_attribute(item: &mut DeriveInput) {
72    fn remove_configuration_from_attributes(attributes: &mut Vec<Attribute>) {
73        attributes.retain(|attribute| !is_typeshare_attribute(attribute));
74    }
75
76    fn remove_configuration_from_fields(fields: &mut Fields) {
77        for field in fields.iter_mut() {
78            remove_configuration_from_attributes(&mut field.attrs);
79        }
80    }
81
82    match item.data {
83        Data::Enum(ref mut data_enum) => {
84            for variant in data_enum.variants.iter_mut() {
85                remove_configuration_from_attributes(&mut variant.attrs);
86                remove_configuration_from_fields(&mut variant.fields);
87            }
88        }
89        Data::Struct(ref mut data_struct) => {
90            remove_configuration_from_fields(&mut data_struct.fields);
91        }
92        Data::Union(ref mut data_union) => {
93            for field in data_union.fields.named.iter_mut() {
94                remove_configuration_from_attributes(&mut field.attrs);
95            }
96        }
97    };
98}