diff --git a/packages/macro-rs/src/lib.rs b/packages/macro-rs/src/lib.rs index 4478b80179..28e128e068 100644 --- a/packages/macro-rs/src/lib.rs +++ b/packages/macro-rs/src/lib.rs @@ -14,6 +14,12 @@ pub fn dummy_macro( /// Creates extra wrapper function for napi. /// +/// The macro is simply converted into `napi_derive::napi(...)` +/// if it is not applied to a function. +/// +/// If `js_name` is not specified, +/// `js_name` will be set to the camelCase version of the original function name. +/// /// The types of the function arguments is converted with following rules: /// - `&str` and `&mut str` are converted to `String` /// - `&[T]` and `&mut [T]` are converted to `Vec` @@ -24,7 +30,46 @@ pub fn dummy_macro( /// Note that `E` must implement `std::string::ToString` trait. /// /// # Examples -/// ## Example with `i32` argument +/// ## Applying the macro to a struct +/// ``` +/// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME +/// #[macro_rs::napi] +/// struct Person { +/// id: i32, +/// name: String, +/// } +/// ``` +/// simply becomes +/// ``` +/// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME +/// #[napi_derive::napi] +/// struct Person { +/// id: i32, +/// name: String, +/// } +/// ``` +/// +/// ## Function with explicitly specified `js_name` +/// ``` +/// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME +/// #[macro_rs::napi(js_name = "add1")] +/// pub fn add_one(x: i32) -> i32 { +/// x + 1 +/// } +/// ``` +/// generates +/// ``` +/// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME +/// # pub fn add_one(x: i32) -> i32 { +/// # x + 1 +/// # } +/// #[napi_derive::napi(js_name = "add1")] +/// pub fn add_one_napi(x: i32) -> i32 { +/// add_one(x) +/// } +/// ``` +/// +/// ## Function with `i32` argument /// ``` /// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME /// #[macro_rs::napi] @@ -32,9 +77,7 @@ pub fn dummy_macro( /// x + 1 /// } /// ``` -/// /// generates -/// /// ``` /// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME /// # pub fn add_one(x: i32) -> i32 { @@ -46,7 +89,7 @@ pub fn dummy_macro( /// } /// ``` /// -/// ## Example with `&str` argument +/// ## Function with `&str` argument /// ``` /// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME /// #[macro_rs::napi] @@ -54,9 +97,7 @@ pub fn dummy_macro( /// str1.to_owned() + str2 /// } /// ``` -/// /// generates -/// /// ``` /// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME /// # pub fn concatenate_string(str1: &str, str2: &str) -> String { @@ -68,7 +109,7 @@ pub fn dummy_macro( /// } /// ``` /// -/// ## Example with `&[String]` argument +/// ## Function with `&[String]` argument /// ``` /// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME /// #[macro_rs::napi] @@ -76,9 +117,7 @@ pub fn dummy_macro( /// array.len() as u32 /// } /// ``` -/// /// generates -/// /// ``` /// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME /// # pub fn string_array_length(array: &[String]) -> u32 { @@ -90,7 +129,7 @@ pub fn dummy_macro( /// } /// ``` /// -/// ## Example with `Result` return type +/// ## Function with `Result` return type /// ``` /// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME /// #[derive(thiserror::Error, Debug)] @@ -112,9 +151,7 @@ pub fn dummy_macro( /// } /// } /// ``` -/// /// generates -/// /// ``` /// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME /// # #[derive(thiserror::Error, Debug)] @@ -147,20 +184,18 @@ pub fn napi( ) -> proc_macro::TokenStream { napi_impl(attr.into(), item.into()).into() } -fn napi_impl(attr: TokenStream, item: TokenStream) -> TokenStream { +fn napi_impl(mut macro_attr: TokenStream, item: TokenStream) -> TokenStream { let item: syn::Item = syn::parse2(item).expect("Failed to parse TokenStream to syn::Item"); // handle functions only let syn::Item::Fn(item_fn) = item else { // fallback to use napi_derive return quote! { - #[napi_derive::napi(#attr)] + #[napi_derive::napi(#macro_attr)] #item }; }; let ident = &item_fn.sig.ident; - let js_name = ident.to_string().to_case(Case::Camel); - let item_fn_attrs = &item_fn.attrs; let item_fn_vis = &item_fn.vis; let mut item_fn_sig = item_fn.sig.clone(); @@ -272,11 +307,26 @@ fn napi_impl(attr: TokenStream, item: TokenStream) -> TokenStream { }) .collect(); - // TODO handle macro attr + // handle macro attr + // append js_name if not specified + if !macro_attr + .clone() + .into_iter() + .any(|token| matches!(token, proc_macro2::TokenTree::Ident(ident) if ident == "js_name")) + { + // append "," first if other macro attr exists + if !macro_attr.is_empty() { + quote! { , }.to_tokens(&mut macro_attr); + } + // append js_name + let js_name = ident.to_string().to_case(Case::Camel); + quote! { js_name = #js_name }.to_tokens(&mut macro_attr); + } + quote! { #item_fn - #[napi_derive::napi(js_name = #js_name)] + #[napi_derive::napi(#macro_attr)] #(#item_fn_attrs)* #item_fn_vis #item_fn_sig { #ident(#(#called_args),*) @@ -290,10 +340,31 @@ mod tests { use proc_macro2::TokenStream; use quote::quote; - macro_rules! test_macro { + macro_rules! test_macro_becomes { ($source:expr, $generated:expr) => { assert_eq!( super::napi_impl(TokenStream::new(), $source).to_string(), + $generated.to_string(), + ) + }; + ($macro_attr:expr, $source:expr, $generated:expr) => { + assert_eq!( + super::napi_impl($macro_attr, $source).to_string(), + $generated.to_string(), + ) + }; + } + + macro_rules! test_macro_generates { + ($source:expr, $generated:expr) => { + assert_eq!( + super::napi_impl(TokenStream::new(), $source).to_string(), + format!("{} {}", $source, $generated), + ) + }; + ($macro_attr:expr, $source:expr, $generated:expr) => { + assert_eq!( + super::napi_impl($macro_attr, $source).to_string(), format!("{} {}", $source, $generated), ) }; @@ -301,7 +372,7 @@ mod tests { #[test] fn primitive_argument() { - test_macro!( + test_macro_generates!( quote! { pub fn add_one(x: i32) -> i32 { x + 1 @@ -318,7 +389,7 @@ mod tests { #[test] fn str_ref_argument() { - test_macro!( + test_macro_generates!( quote! { pub fn concatenate_string(str1: &str, str2: &str) -> String { str1.to_owned() + str2 @@ -335,7 +406,7 @@ mod tests { #[test] fn mut_ref_argument() { - test_macro!( + test_macro_generates!( quote! { pub fn append_string_and_clone( base_str: &mut String, @@ -359,7 +430,7 @@ mod tests { #[test] fn result_return_type() { - test_macro!( + test_macro_generates!( quote! { pub fn integer_divide( dividend: i64, @@ -389,7 +460,7 @@ mod tests { #[test] fn async_function() { - test_macro!( + test_macro_generates!( quote! { pub async fn async_add_one(x: i32) -> i32 { x + 1 @@ -407,7 +478,7 @@ mod tests { #[test] fn slice_type() { - test_macro!( + test_macro_generates!( quote! { pub fn string_array_length(array: &[String]) -> u32 { array.len() as u32 @@ -421,4 +492,82 @@ mod tests { } ) } + + #[test] + fn non_fn() { + test_macro_becomes!( + quote! { object }, + quote! { + struct Person { + id: i32, + name: String, + } + }, + quote! { + #[napi_derive::napi(object)] + struct Person { + id: i32, + name: String, + } + } + ) + } + + #[test] + fn macro_attr() { + test_macro_generates!( + quote! { + ts_return_type = "number" + }, + quote! { + pub fn add_one(x: i32) -> i32 { + x + 1 + } + }, + quote! { + #[napi_derive::napi(ts_return_type = "number", js_name = "addOne")] + pub fn add_one_napi(x: i32) -> i32 { + add_one(x) + } + } + ) + } + + #[test] + fn explicitly_specified_js_name() { + test_macro_generates!( + quote! { + js_name = "add1" + }, + quote! { + pub fn add_one(x: i32) -> i32 { + x + 1 + } + }, + quote! { + #[napi_derive::napi(js_name = "add1")] + pub fn add_one_napi(x: i32) -> i32 { + add_one(x) + } + } + ) + } + + #[test] + fn explicitly_specified_js_name_and_other_macro_attr() { + test_macro_generates!( + quote! { ts_return_type = "number", js_name = "add1" }, + quote! { + pub fn add_one(x: i32) -> i32 { + x + 1 + } + }, + quote! { + #[napi_derive::napi(ts_return_type = "number", js_name = "add1")] + pub fn add_one_napi(x: i32) -> i32 { + add_one(x) + } + } + ) + } }