bitwarden_ffi_macro/
wasm_object.rs1use proc_macro2::TokenStream;
2use quote::{ToTokens, quote};
3use syn::{Item, parse2};
4
5use crate::attrs;
6
7pub(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}