1#[cfg(feature = "compat-mode")]
2mod compat_macro;
3mod expand;
4#[cfg(not(feature = "noop"))]
5mod parser;
6
7#[macro_use]
8extern crate syn;
9#[cfg(not(feature = "noop"))]
10#[macro_use]
11extern crate napi_derive_backend;
12#[macro_use]
13extern crate quote;
14
15use std::env;
16
17use proc_macro::TokenStream;
18use syn::ItemFn;
19#[cfg(feature = "compat-mode")]
20use syn::{fold::Fold, parse_macro_input};
21
22#[proc_macro_attribute]
29pub fn napi(attr: TokenStream, input: TokenStream) -> TokenStream {
30 match expand::expand(attr.into(), input.into()) {
31 Ok(tokens) => {
32 if env::var("NAPI_DEBUG_GENERATED_CODE").is_ok() {
33 println!("{tokens}");
34 }
35 tokens.into()
36 }
37 Err(diagnostic) => {
38 println!("`napi` macro expand failed.");
39
40 (quote! { #diagnostic }).into()
41 }
42 }
43}
44
45#[cfg(feature = "compat-mode")]
46#[proc_macro_attribute]
47pub fn contextless_function(_attr: TokenStream, input: TokenStream) -> TokenStream {
48 let input = parse_macro_input!(input as ItemFn);
49 let mut js_fn = compat_macro::JsFunction::new();
50 js_fn.fold_item_fn(input);
51 let fn_name = js_fn.name.unwrap();
52 let fn_block = js_fn.block;
53 let signature = js_fn.signature.unwrap();
54 let visibility = js_fn.visibility;
55 let new_fn_name = signature.ident.clone();
56 let execute_js_function =
57 compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::Contextless);
58
59 let expanded = quote! {
60 #[inline(always)]
61 #signature #(#fn_block)*
62
63 #visibility extern "C" fn #fn_name(
64 raw_env: napi::sys::napi_env,
65 cb_info: napi::sys::napi_callback_info,
66 ) -> napi::sys::napi_value {
67 use std::ptr;
68 use std::panic::{self, AssertUnwindSafe};
69 use std::ffi::CString;
70 use napi::{Env, NapiValue, NapiRaw, Error, Status};
71
72 let ctx = unsafe { Env::from_raw(raw_env) };
73 #execute_js_function
74 }
75 };
76 TokenStream::from(expanded)
78}
79
80#[cfg(feature = "compat-mode")]
81#[proc_macro_attribute]
82pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
83 let arg_len = parse_macro_input!(attr as compat_macro::ArgLength);
84 let arg_len_span = arg_len.length;
85 let input = parse_macro_input!(input as ItemFn);
86 let mut js_fn = compat_macro::JsFunction::new();
87 js_fn.fold_item_fn(input);
88 let fn_name = js_fn.name.unwrap();
89 let fn_block = js_fn.block;
90 let signature = js_fn.signature.unwrap();
91 let visibility = js_fn.visibility;
92 let new_fn_name = signature.ident.clone();
93 let execute_js_function =
94 compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::JsFunction);
95 let expanded = quote! {
96 #[inline(always)]
97 #signature #(#fn_block)*
98
99 #visibility extern "C" fn #fn_name(
100 raw_env: napi::sys::napi_env,
101 cb_info: napi::sys::napi_callback_info,
102 ) -> napi::sys::napi_value {
103 use std::ptr;
104 use std::panic::{self, AssertUnwindSafe};
105 use std::ffi::CString;
106 use napi::{Env, Error, Status, NapiValue, NapiRaw, CallContext};
107 let mut argc = #arg_len_span as usize;
108 #[cfg(all(target_os = "windows", target_arch = "x86"))]
109 let mut raw_args = vec![ptr::null_mut(); #arg_len_span];
110 #[cfg(not(all(target_os = "windows", target_arch = "x86")))]
111 let mut raw_args = [ptr::null_mut(); #arg_len_span];
112 let mut raw_this = ptr::null_mut();
113
114 unsafe {
115 let status = napi::sys::napi_get_cb_info(
116 raw_env,
117 cb_info,
118 &mut argc,
119 raw_args.as_mut_ptr(),
120 &mut raw_this,
121 ptr::null_mut(),
122 );
123 debug_assert!(Status::from(status) == Status::Ok, "napi_get_cb_info failed");
124 }
125
126 let mut env = unsafe { Env::from_raw(raw_env) };
127 #[cfg(all(target_os = "windows", target_arch = "x86"))]
128 let ctx = CallContext::new(&mut env, cb_info, raw_this, raw_args.as_slice(), argc);
129 #[cfg(not(all(target_os = "windows", target_arch = "x86")))]
130 let ctx = CallContext::new(&mut env, cb_info, raw_this, &raw_args, argc);
131 #execute_js_function
132 }
133 };
134 TokenStream::from(expanded)
136}
137
138#[cfg(feature = "compat-mode")]
139#[proc_macro_attribute]
140pub fn module_exports(_attr: TokenStream, input: TokenStream) -> TokenStream {
141 let input = parse_macro_input!(input as ItemFn);
142 let mut js_fn = compat_macro::JsFunction::new();
143 js_fn.fold_item_fn(input);
144 let fn_block = js_fn.block;
145 let fn_name = js_fn.name.unwrap();
146 let signature = js_fn.signature_raw.unwrap();
147 let args_len = js_fn.args.len();
148 let call_expr = if args_len == 1 {
149 quote! { #fn_name(exports) }
150 } else if args_len == 2 {
151 quote! { #fn_name(exports, env) }
152 } else {
153 panic!("Arguments length of #[module_exports] function must be 1 or 2");
154 };
155
156 let register = quote! {
157 #[cfg_attr(not(target_family = "wasm"), napi::ctor::ctor(crate_path=napi::ctor))]
158 fn __napi_explicit_module_register() {
159 unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> {
160 use napi::{Env, JsObject, NapiValue};
161
162 let env = Env::from_raw(raw_env);
163 let exports = JsObject::from_raw_unchecked(raw_env, raw_exports);
164
165 #call_expr
166 }
167
168 napi::bindgen_prelude::register_module_exports(register)
169 }
170 };
171
172 (quote! {
173 #[inline]
174 #signature #(#fn_block)*
175
176 #register
177 })
178 .into()
179}
180
181#[proc_macro_attribute]
182pub fn module_init(_: TokenStream, input: TokenStream) -> TokenStream {
183 let input = parse_macro_input!(input as ItemFn);
184 quote! {
185 #[napi::ctor::ctor(crate_path=napi::ctor)]
186 #input
187 }
188 .into()
189}