Skip to main content

bitwarden_ffi_macro/
wasm_object.rs

1use proc_macro2::TokenStream;
2use quote::{ToTokens, quote};
3use syn::{Item, parse2};
4
5use crate::attrs;
6
7/// Declares a `#[wasm_bindgen]` handle type, forwarding this macro's arguments to it, and
8/// implements the wire traits so the type crosses as itself.
9pub(crate) fn wasm_object(attr: TokenStream, item: TokenStream) -> TokenStream {
10    let forwarded = match attrs::parse_args(attr) {
11        Ok(args) => args,
12        Err(err) => return err.to_compile_error(),
13    };
14
15    let item = match parse2::<Item>(item) {
16        Ok(item) => item,
17        Err(err) => return err.to_compile_error(),
18    };
19
20    if let Err(err) = attrs::check_exportable_type(&item, "wasm_object") {
21        return err.to_compile_error();
22    }
23
24    let ident = match &item {
25        Item::Struct(item) => &item.ident,
26        Item::Enum(item) => &item.ident,
27        _ => unreachable!("check_exportable_type rejects every other item"),
28    };
29
30    let bindgen = attrs::wasm_bindgen_attr(&forwarded);
31    let body = item.to_token_stream();
32    quote! {
33        #bindgen
34        #body
35
36        ::bitwarden_ffi::impl_wire_object!(#ident);
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    fn expand(attr: TokenStream, item: TokenStream) -> String {
45        wasm_object(attr, item).to_string().replace(' ', "")
46    }
47
48    #[test]
49    fn applies_wasm_bindgen_under_the_wasm_feature() {
50        let out = expand(
51            TokenStream::new(),
52            quote! {
53                pub struct CiphersClient { client: Client }
54            },
55        );
56
57        assert!(!out.contains("compile_error!"), "{out}");
58        assert!(
59            out.contains("#[cfg_attr(feature=\"wasm\",::bitwarden_ffi::_macro::wasm_bindgen)]"),
60            "{out}"
61        );
62        assert!(
63            out.contains("::bitwarden_ffi::impl_wire_object!(CiphersClient);"),
64            "{out}"
65        );
66    }
67
68    #[test]
69    fn forwards_its_arguments_to_wasm_bindgen() {
70        let out = expand(
71            quote!(js_name = Ciphers),
72            quote! {
73                pub struct CiphersClient;
74            },
75        );
76
77        assert!(
78            out.contains("::bitwarden_ffi::_macro::wasm_bindgen(js_name=Ciphers)"),
79            "{out}"
80        );
81    }
82
83    #[test]
84    fn leaves_field_attributes_in_place() {
85        let out = expand(
86            TokenStream::new(),
87            quote! {
88                pub struct JsIpcClient {
89                    #[wasm_bindgen(skip)]
90                    pub client: Arc<dyn IpcClient>,
91                }
92            },
93        );
94
95        assert!(out.contains("#[wasm_bindgen(skip)]"), "{out}");
96    }
97
98    #[test]
99    fn rejects_an_unparseable_argument_list() {
100        let out = expand(quote!(js_name =), quote! { pub struct CiphersClient; });
101
102        assert!(out.contains("compile_error!"), "{out}");
103    }
104
105    #[test]
106    fn rejects_generics() {
107        let out = expand(
108            TokenStream::new(),
109            quote! {
110                pub struct Holder<T> { inner: T }
111            },
112        );
113
114        assert!(out.contains("compile_error!"), "{out}");
115        assert!(out.contains("doesnotsupportgenerics"), "{out}");
116    }
117}