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