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
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
extern crate proc_macro;

use proc_macro::TokenStream;

use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, TokenStreamExt};
use syn::{parse_macro_input, spanned::Spanned};

/// Checks if the passed type implements the passed trait.
fn trait_check<'l, L>(lifetimes: L, type_: syn::Type, trait_: TokenStream2) -> TokenStream2
where
    L: Iterator<Item = &'l syn::LifetimeDef>,
{
    quote!(
      const _: fn() = || {
        fn declare_lifetime<#(#lifetimes),*>() {
          fn assert_impl_all<T: ?Sized + #trait_>() {}
          assert_impl_all::<#type_>();
        }
      };
    )
}

/// Automatically derive `Template` from a struct with valid input fields.
///
/// ```no_run,no_compile
/// #[derive(Template)]
/// struct MyTemplate {
///     serializable_field: usize,
///
///     #[raw]
///     raw_field: &'static str
/// }
/// ```
#[proc_macro_derive(Template, attributes(raw))]
pub fn derive_template(item: TokenStream) -> TokenStream {
    let item = parse_macro_input!(item as syn::DeriveInput);
    let item_span = item.span();
    let name = item.ident;
    match item.data {
        syn::Data::Struct(data) => {
            let (impl_generics, ty_generics, _) = item.generics.split_for_impl();
            let mut replacements = TokenStream2::new();
            for field in data.fields {
                match field.ident {
                    Some(ident) => {
                        let templated_field_name;
                        let lifetimes = item.generics.lifetimes();

                        // we expect self, template, and options bindings to exist
                        let data = if field.attrs.iter().any(|attr| attr.path.is_ident("raw")) {
                            templated_field_name = format!("__RAW_{}__", ident);
                            let trait_check = trait_check(lifetimes,field.ty, quote!(::std::fmt::Display));
                            quote!(
                                #trait_check
                                let data: String = self.#ident.to_string();
                            )
                        } else {
                            templated_field_name = format!("__TEMPLATE_{}__", ident);
                            let trait_check = trait_check(lifetimes, field.ty, quote!(::serialize_to_javascript::private::Serialize));
                            quote!(
                                #trait_check

                                use ::std::convert::TryInto;
                                use ::serialize_to_javascript::{
                                    private::{NotYetSerialized, SerializedOnce},
                                    Serialized
                                };

                                let data: SerializedOnce = NotYetSerialized(&self.#ident).try_into()?;
                                let data: Serialized = data.into_javascript_string_literal(options);
                                let data: String = data.into_string();
                            )
                        };

                        replacements.append_all(quote!(
                            let template = {
                                #data
                                template.replace(
                                    #templated_field_name,
                                    &data
                                )
                            };
                        ));
                    }
                    None => {
                        return syn::Error::new(
                            field.span(),
                            "Template fields must all have names",
                        )
                            .to_compile_error()
                            .into();
                    }
                }
            }
            quote!(
                impl #impl_generics ::serialize_to_javascript::private::Sealed for #name #ty_generics {}
                impl #impl_generics ::serialize_to_javascript::Template for #name #ty_generics {
                    fn render(&self, template: &str, options: &::serialize_to_javascript::Options) -> ::serialize_to_javascript::Result<::serialize_to_javascript::Serialized> {
                        #replacements
                        Ok(unsafe {
                            ::serialize_to_javascript::Serialized::from_string_unchecked(template.into())
                        })
                    }
                }
            )
        }
        _ => {
            return syn::Error::new(
                item_span,
                "`Template` currently only supports data structs",
            )
                .to_compile_error()
                .into();
        }
    }
        .into()
}

/// Automatically derive `DefaultTemplate` for a `Template` from the passed path.
///
/// ```no_run,no_compile
/// #[default_template("path/to/my_javascript_file.js")]
/// ```
#[proc_macro_attribute]
pub fn default_template(attr: TokenStream, item: TokenStream) -> TokenStream {
    let path = parse_macro_input!(attr as syn::LitStr);
    let item = parse_macro_input!(item as syn::DeriveInput);
    let name = item.ident.clone();
    let (impl_generics, ty_generics, _) = item.generics.split_for_impl();
    quote!(
        #item
        impl #impl_generics ::serialize_to_javascript::DefaultTemplate for #name #ty_generics {
            const RAW_TEMPLATE: &'static str = include_str!(#path);
        }
    )
    .into()
}