1use std::env;
2use std::fs;
3use std::io::BufWriter;
4use std::io::Write;
5use std::sync::atomic::{AtomicBool, Ordering};
6
7use crate::parser::{attrs::BindgenAttrs, ParseNapi};
8#[cfg(feature = "type-def")]
9use napi_derive_backend::ToTypeDef;
10use napi_derive_backend::{BindgenResult, Napi, TryToTokens};
11use proc_macro2::TokenStream;
12use quote::ToTokens;
13use syn::{Attribute, Item};
14
15/// a flag indicate whether or never at least one `napi` macro has been expanded.
16/// ```ignore
17/// if BUILT_FLAG
18/// .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
19/// .is_ok() {
20/// // logic on first macro expansion
21/// }
22///
23/// ```
24static BUILT_FLAG: AtomicBool = AtomicBool::new(false);
25
26pub fn expand(attr: TokenStream, input: TokenStream) -> BindgenResult<TokenStream> {
27 if BUILT_FLAG
28 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
29 .is_ok()
30 {
31 // logic on first macro expansion
32 #[cfg(feature = "type-def")]
33 prepare_type_def_file();
34
35 if let Ok(wasi_register_file) = env::var("WASI_REGISTER_TMP_PATH") {
36 if let Err(_e) = remove_existed_def_file(&wasi_register_file) {
37 #[cfg(debug_assertions)]
38 {
39 println!("Failed to manipulate wasi register file: {:?}", _e);
40 }
41 }
42 }
43 }
44
45 let mut item = syn::parse2::<Item>(input)?;
46 let opts: BindgenAttrs = syn::parse2(attr)?;
47 let mut tokens = proc_macro2::TokenStream::new();
48 if let Item::Mod(mut js_mod) = item {
49 let js_name = opts.js_name().map_or_else(
50 || js_mod.ident.to_string(),
51 |(js_name, _)| js_name.to_owned(),
52 );
53 if let Some((_, mut items)) = js_mod.content.clone() {
54 for item in items.iter_mut() {
55 let mut empty_attrs = vec![];
56 if let Some(item_opts) = replace_napi_attr_in_mod(
57 js_name.clone(),
58 match item {
59 Item::Fn(ref mut function) => &mut function.attrs,
60 Item::Struct(ref mut struct_) => &mut struct_.attrs,
61 Item::Enum(ref mut enum_) => &mut enum_.attrs,
62 Item::Const(ref mut const_) => &mut const_.attrs,
63 Item::Impl(ref mut impl_) => &mut impl_.attrs,
64 Item::Mod(mod_) => {
65 let mod_in_mod = mod_
66 .attrs
67 .iter()
68 .enumerate()
69 .find(|(_, m)| m.path().is_ident("napi"));
70 if mod_in_mod.is_some() {
71 bail_span!(
72 mod_,
73 "napi module cannot be nested under another napi module"
74 );
75 } else {
76 &mut empty_attrs
77 }
78 }
79 _ => &mut empty_attrs,
80 },
81 ) {
82 let napi = item.parse_napi(&mut tokens, &item_opts)?;
83 item_opts.check_used()?;
84 napi.try_to_tokens(&mut tokens)?;
85
86 #[cfg(feature = "type-def")]
87 output_type_def(&napi);
88 output_wasi_register_def(&napi);
89 } else {
90 item.to_tokens(&mut tokens);
91 };
92 }
93 js_mod.content = None;
94 };
95
96 let js_mod_attrs: Vec<Attribute> = js_mod
97 .attrs
98 .clone()
99 .into_iter()
100 .filter(|attr| attr.path().is_ident("napi"))
101 .collect();
102 let mod_name = js_mod.ident;
103 let visible = js_mod.vis;
104 let mod_tokens = quote! { #(#js_mod_attrs)* #visible mod #mod_name { #tokens } };
105 Ok(mod_tokens)
106 } else {
107 let napi = item.parse_napi(&mut tokens, &opts)?;
108 opts.check_used()?;
109 napi.try_to_tokens(&mut tokens)?;
110
111 #[cfg(feature = "type-def")]
112 output_type_def(&napi);
113 output_wasi_register_def(&napi);
114 Ok(tokens)
115 }
116}
117
118fn output_wasi_register_def(napi: &Napi) {
119 if let Ok(wasi_register_file: String) = env::var(key:"WASI_REGISTER_TMP_PATH") {
120 fs::OpenOptions::new()
121 .append(true)
122 .create(true)
123 .open(wasi_register_file)
124 .and_then(|file| {
125 let mut writer = BufWriter::<fs::File>::new(file);
126 let pkg_name: String = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME is not set");
127 writer.write_all(format!("{pkg_name}: {}", napi.register_name()).as_bytes())?;
128 writer.write_all("\n".as_bytes())?;
129 writer.flush()
130 })
131 .unwrap_or_else(|e: Error| {
132 println!("Failed to write wasi register file: {:?}", e);
133 });
134 }
135}
136
137#[cfg(feature = "type-def")]
138fn output_type_def(napi: &Napi) {
139 if let Ok(type_def_file: String) = env::var(key:"TYPE_DEF_TMP_PATH") {
140 if let Some(type_def: TypeDef) = napi.to_type_def() {
141 fs::OpenOptions::new()
142 .append(true)
143 .create(true)
144 .open(type_def_file)
145 .and_then(|file| {
146 let mut writer = BufWriter::<fs::File>::new(file);
147 writer.write_all(type_def.to_string().as_bytes())?;
148 writer.write_all("\n".as_bytes())?;
149 writer.flush()
150 })
151 .unwrap_or_else(|e: Error| {
152 println!("Failed to write type def file: {:?}", e);
153 });
154 }
155 }
156}
157
158fn replace_napi_attr_in_mod(
159 js_namespace: String,
160 attrs: &mut Vec<syn::Attribute>,
161) -> Option<BindgenAttrs> {
162 let napi_attr = attrs
163 .iter()
164 .enumerate()
165 .find(|(_, m)| m.path().is_ident("napi"));
166
167 if let Some((index, napi_attr)) = napi_attr {
168 // adds `namespace = #js_namespace` into `#[napi]` attribute
169 let new_attr = match &napi_attr.meta {
170 syn::Meta::Path(_) => {
171 syn::parse_quote!(#[napi(namespace = #js_namespace)])
172 }
173 syn::Meta::List(list) => {
174 let existing = list.tokens.clone();
175 syn::parse_quote!(#[napi(#existing, namespace = #js_namespace)])
176 }
177 syn::Meta::NameValue(name_value) => {
178 let existing = &name_value.value;
179 syn::parse_quote!(#[napi(#existing, namespace = #js_namespace)])
180 }
181 };
182
183 let struct_opts = BindgenAttrs::try_from(&new_attr).unwrap();
184 attrs.remove(index);
185 Some(struct_opts)
186 } else {
187 None
188 }
189}
190
191#[cfg(feature = "type-def")]
192fn prepare_type_def_file() {
193 if let Ok(ref type_def_file: &String) = env::var(key:"TYPE_DEF_TMP_PATH") {
194 use napi_derive_backend::{NAPI_RS_CLI_VERSION, NAPI_RS_CLI_VERSION_WITH_SHARED_CRATES_FIX};
195 if let Err(_e: Error) = if *NAPI_RS_CLI_VERSION >= *NAPI_RS_CLI_VERSION_WITH_SHARED_CRATES_FIX {
196 remove_existed_def_file(type_def_file)
197 } else {
198 fs::remove_file(path:type_def_file)
199 } {
200 #[cfg(debug_assertions)]
201 {
202 println!("Failed to manipulate type def file: {:?}", _e);
203 }
204 }
205 }
206}
207
208fn remove_existed_def_file(def_file: &str) -> std::io::Result<()> {
209 use std::io::{BufRead, BufReader};
210
211 let pkg_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME is not set");
212 if let Ok(content) = std::fs::File::open(def_file) {
213 let reader = BufReader::new(content);
214 let cleaned_content = reader
215 .lines()
216 .filter_map(|line| {
217 if let Ok(line) = line {
218 if let Some((package_name, _)) = line.split_once(':') {
219 if pkg_name == package_name {
220 return None;
221 }
222 }
223 Some(line)
224 } else {
225 None
226 }
227 })
228 .collect::<Vec<String>>()
229 .join("\n");
230 std::fs::write(def_file, format!("{cleaned_content}\n"))?;
231 }
232 Ok(())
233}
234