bitwarden_ffi_macro/
wasm_import.rs1use proc_macro2::TokenStream;
2use quote::{ToTokens, quote};
3use syn::{ForeignItem, ItemForeignMod, parse2};
4
5use crate::attrs;
6
7pub(crate) fn wasm_import(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 block = match parse2::<ItemForeignMod>(item.clone()) {
16 Ok(block) => block,
17 Err(_) => {
18 return syn::Error::new_spanned(
19 item,
20 "#[wasm_import] applies to an `extern \"C\"` block",
21 )
22 .to_compile_error();
23 }
24 };
25
26 if let Some(attr) = attrs::find_wasm_bindgen(&block.attrs) {
27 return syn::Error::new_spanned(attr, "#[wasm_import] replaces #[wasm_bindgen]; remove it")
28 .to_compile_error();
29 }
30
31 let cfgs: Vec<_> = block
34 .attrs
35 .iter()
36 .filter(|attr| attr.path().is_ident("cfg"))
37 .collect();
38 let wire_impls = block.items.iter().filter_map(|item| match item {
39 ForeignItem::Type(ty) => {
40 let ident = &ty.ident;
41 Some(quote! {
42 #(#cfgs)*
43 ::bitwarden_ffi::impl_wire_object!(#ident);
44 })
45 }
46 _ => None,
47 });
48
49 let call = if forwarded.is_empty() {
50 quote!(wasm_bindgen)
51 } else {
52 quote!(wasm_bindgen(#forwarded))
53 };
54 let body = block.to_token_stream();
55 quote! {
58 #[::wasm_bindgen::prelude::#call]
59 #body
60
61 #(#wire_impls)*
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 fn expand(attr: TokenStream, item: TokenStream) -> String {
70 wasm_import(attr, item).to_string().replace(' ', "")
71 }
72
73 #[test]
74 fn applies_wasm_bindgen_and_implements_the_wire_traits_per_type() {
75 let out = expand(
76 TokenStream::new(),
77 quote! {
78 extern "C" {
79 pub type Driver;
80 pub type Signal;
81
82 #[wasm_bindgen(method)]
83 pub fn lock(this: &Driver);
84 }
85 },
86 );
87
88 assert!(!out.contains("compile_error!"), "{out}");
89 assert!(
90 out.contains("#[::wasm_bindgen::prelude::wasm_bindgen]"),
91 "{out}"
92 );
93 assert!(
94 out.contains("::bitwarden_ffi::impl_wire_object!(Driver);"),
95 "{out}"
96 );
97 assert!(
98 out.contains("::bitwarden_ffi::impl_wire_object!(Signal);"),
99 "{out}"
100 );
101 assert!(!out.contains("impl_wire_object!(lock)"), "{out}");
102 }
103
104 #[test]
105 fn forwards_arguments_to_wasm_bindgen() {
106 let out = expand(
107 quote!(module = "/js/driver.js"),
108 quote! {
109 extern "C" {
110 pub type Driver;
111 }
112 },
113 );
114
115 assert!(
116 out.contains("#[::wasm_bindgen::prelude::wasm_bindgen(module=\"/js/driver.js\")]"),
117 "{out}"
118 );
119 }
120
121 #[test]
122 fn gates_the_wire_impls_on_the_blocks_cfg() {
123 let out = expand(
124 TokenStream::new(),
125 quote! {
126 #[cfg(target_arch = "wasm32")]
127 extern "C" {
128 pub type Driver;
129 }
130 },
131 );
132
133 assert!(
134 out.contains(
135 "#[cfg(target_arch=\"wasm32\")]::bitwarden_ffi::impl_wire_object!(Driver);"
136 ),
137 "{out}"
138 );
139 }
140
141 #[test]
142 fn a_block_without_types_implements_nothing() {
143 let out = expand(
144 TokenStream::new(),
145 quote! {
146 extern "C" {
147 pub fn log(message: &str);
148 }
149 },
150 );
151
152 assert!(!out.contains("impl_wire_object"), "{out}");
153 }
154
155 #[test]
156 fn rejects_an_item_that_is_not_an_extern_block() {
157 let out = expand(TokenStream::new(), quote! { pub struct Driver; });
158
159 assert!(out.contains("compile_error!"), "{out}");
160 assert!(out.contains("appliestoan`extern\\\"C\\\"`block"), "{out}");
161 }
162
163 #[test]
164 fn rejects_a_block_that_already_has_wasm_bindgen() {
165 let out = expand(
166 TokenStream::new(),
167 quote! {
168 #[wasm_bindgen]
169 extern "C" {
170 pub type Driver;
171 }
172 },
173 );
174
175 assert!(out.contains("compile_error!"), "{out}");
176 assert!(out.contains("replaces#[wasm_bindgen]"), "{out}");
177 }
178}