refactor: macro-rs -> macros + macros_impl
Co-authored-by: naskya <m@naskya.net>
This commit is contained in:
parent
4e36d22164
commit
0cb4fbf6b4
16 changed files with 780 additions and 703 deletions
|
@ -290,7 +290,7 @@ cargo:doc:
|
||||||
- cp ci/cargo/config.toml /usr/local/cargo/config.toml
|
- cp ci/cargo/config.toml /usr/local/cargo/config.toml
|
||||||
script:
|
script:
|
||||||
- cargo doc --document-private-items
|
- cargo doc --document-private-items
|
||||||
- printf "window.ALL_CRATES = ['backend_rs', 'macro_rs'];" > target/doc/crates.js
|
- printf 'window.ALL_CRATES = ["backend_rs", "macros", "macros_impl"];' > target/doc/crates.js
|
||||||
- printf '<meta http-equiv="refresh" content="0; url=%s">' 'backend_rs' > target/doc/index.html
|
- printf '<meta http-equiv="refresh" content="0; url=%s">' 'backend_rs' > target/doc/index.html
|
||||||
- cd target/doc
|
- cd target/doc
|
||||||
- npx --yes netlify-cli deploy --prod --site="${CARGO_DOC_SITE_ID}" --dir=.
|
- npx --yes netlify-cli deploy --prod --site="${CARGO_DOC_SITE_ID}" --dir=.
|
||||||
|
|
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -210,7 +210,7 @@ dependencies = [
|
||||||
"idna",
|
"idna",
|
||||||
"image",
|
"image",
|
||||||
"isahc",
|
"isahc",
|
||||||
"macro-rs",
|
"macros",
|
||||||
"napi",
|
"napi",
|
||||||
"napi-build",
|
"napi-build",
|
||||||
"napi-derive",
|
"napi-derive",
|
||||||
|
@ -1718,18 +1718,24 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "macro-rs"
|
name = "macros"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"macros-impl",
|
||||||
"napi",
|
|
||||||
"napi-derive",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "macros-impl"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
"syn 2.0.66",
|
"syn 2.0.66",
|
||||||
"thiserror",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["packages/backend-rs", "packages/macro-rs"]
|
members = ["packages/backend-rs", "packages/macro-rs/macros", "packages/macro-rs/macros-impl"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
macro-rs = { path = "packages/macro-rs" }
|
macros = { path = "packages/macro-rs/macros" }
|
||||||
|
macros-impl = { path = "packages/macro-rs/macros-impl" }
|
||||||
|
|
||||||
napi = { git = "https://github.com/napi-rs/napi-rs.git", rev = "ca2cd5c35a0c39ec4a94e93c6c5695b681046df2" }
|
napi = { git = "https://github.com/napi-rs/napi-rs.git", rev = "ca2cd5c35a0c39ec4a94e93c6c5695b681046df2" }
|
||||||
napi-derive = "2.16.5"
|
napi-derive = "2.16.5"
|
||||||
|
|
|
@ -17,8 +17,7 @@ COPY Cargo.toml Cargo.toml
|
||||||
COPY Cargo.lock Cargo.lock
|
COPY Cargo.lock Cargo.lock
|
||||||
COPY packages/backend-rs/Cargo.toml packages/backend-rs/Cargo.toml
|
COPY packages/backend-rs/Cargo.toml packages/backend-rs/Cargo.toml
|
||||||
COPY packages/backend-rs/src/lib.rs packages/backend-rs/src/
|
COPY packages/backend-rs/src/lib.rs packages/backend-rs/src/
|
||||||
COPY packages/macro-rs/Cargo.toml packages/macro-rs/Cargo.toml
|
COPY packages/macro-rs packages/macro-rs/
|
||||||
COPY packages/macro-rs/src/lib.rs packages/macro-rs/src/
|
|
||||||
|
|
||||||
# Configure pnpm, and install backend-rs dependencies
|
# Configure pnpm, and install backend-rs dependencies
|
||||||
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm --filter backend-rs install
|
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm --filter backend-rs install
|
||||||
|
@ -26,7 +25,6 @@ RUN cargo fetch --locked --manifest-path Cargo.toml
|
||||||
|
|
||||||
# Copy in the rest of the rust files
|
# Copy in the rest of the rust files
|
||||||
COPY packages/backend-rs packages/backend-rs/
|
COPY packages/backend-rs packages/backend-rs/
|
||||||
# COPY packages/macro-rs packages/macro-rs/
|
|
||||||
|
|
||||||
# Compile backend-rs
|
# Compile backend-rs
|
||||||
RUN NODE_ENV='production' pnpm run --filter backend-rs build
|
RUN NODE_ENV='production' pnpm run --filter backend-rs build
|
||||||
|
|
|
@ -12,7 +12,7 @@ napi = ["dep:napi", "dep:napi-derive", "dep:napi-build"]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
macro-rs = { workspace = true }
|
macros = { workspace = true }
|
||||||
|
|
||||||
napi = { workspace = true, optional = true, features = ["chrono_date", "napi4", "serde-json", "tokio_rt"] }
|
napi = { workspace = true, optional = true, features = ["chrono_date", "napi4", "serde-json", "tokio_rt"] }
|
||||||
napi-derive = { workspace = true, optional = true }
|
napi-derive = { workspace = true, optional = true }
|
||||||
|
|
|
@ -4,7 +4,7 @@ use once_cell::sync::Lazy;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
pub const VERSION: &str = macro_rs::read_version_from_package_json!();
|
pub const VERSION: &str = macros::read_version_from_package_json!();
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use macro_rs::{derive_clone_and_export, export, ts_export};
|
use macros::{derive_clone_and_export, export, ts_export};
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
|
|
11
packages/macro-rs/macros-impl/Cargo.toml
Normal file
11
packages/macro-rs/macros-impl/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "macros-impl"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.74"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
convert_case = { workspace = true }
|
||||||
|
proc-macro2 = { workspace = true }
|
||||||
|
quote = { workspace = true }
|
||||||
|
syn = { workspace = true, features = ["clone-impls", "extra-traits", "full", "parsing", "printing"] }
|
2
packages/macro-rs/macros-impl/src/lib.rs
Normal file
2
packages/macro-rs/macros-impl/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod napi;
|
||||||
|
mod util;
|
451
packages/macro-rs/macros-impl/src/napi.rs
Normal file
451
packages/macro-rs/macros-impl/src/napi.rs
Normal file
|
@ -0,0 +1,451 @@
|
||||||
|
//! Napi related macros
|
||||||
|
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use proc_macro2::{TokenStream, TokenTree};
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
|
||||||
|
/// Creates an extra wrapper function for [napi_derive](https://docs.rs/napi-derive/latest/napi_derive/).
|
||||||
|
///
|
||||||
|
/// The macro is simply converted into `napi_derive::napi(...)`
|
||||||
|
/// if it is not applied to a function.
|
||||||
|
///
|
||||||
|
/// The macro sets the following attributes by default if not specified:
|
||||||
|
/// - `use_nullable = true` (if `object` or `constructor` attribute is specified)
|
||||||
|
/// - `js_name` to the camelCase version of the original function name (for functions)
|
||||||
|
///
|
||||||
|
/// 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<T>`]
|
||||||
|
/// - `&T` and `&mut T` are converted to `T`
|
||||||
|
/// - Other `T` remains `T`
|
||||||
|
///
|
||||||
|
/// In addition, return type [`Result<T>`] and [`Result<T, E>`] are converted to [`napi::Result<T>`](https://docs.rs/napi/latest/napi/type.Result.html).
|
||||||
|
/// Note that `E` must implement [std::error::Error] trait,
|
||||||
|
/// and `crate::util::error_chain::format_error(error: &dyn std::error::Error) -> String` function must be present.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ## Applying the macro to a struct
|
||||||
|
/// ```
|
||||||
|
/// # use macros_impl::napi::napi;
|
||||||
|
/// # macros_impl::macro_doctest!({
|
||||||
|
/// #[macros::napi(object)]
|
||||||
|
/// struct Person {
|
||||||
|
/// id: i32,
|
||||||
|
/// name: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # }, {
|
||||||
|
/// /******* the code above expands to *******/
|
||||||
|
///
|
||||||
|
/// #[napi_derive::napi(use_nullable = true, object)]
|
||||||
|
/// struct Person {
|
||||||
|
/// id: i32,
|
||||||
|
/// name: String,
|
||||||
|
/// }
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Function with explicitly specified `js_name`
|
||||||
|
/// ```
|
||||||
|
/// # use macros_impl::napi::napi;
|
||||||
|
/// # macros_impl::macro_doctest!({
|
||||||
|
/// #[macros::napi(js_name = "add1")]
|
||||||
|
/// pub fn add_one(x: i32) -> i32 {
|
||||||
|
/// x + 1
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # }, {
|
||||||
|
/// /******* the code above expands to *******/
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
/// ```
|
||||||
|
/// # use macros_impl::napi::napi;
|
||||||
|
/// # macros_impl::macro_doctest!({
|
||||||
|
/// #[macros::napi]
|
||||||
|
/// pub fn add_one(x: i32) -> i32 {
|
||||||
|
/// x + 1
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # }, {
|
||||||
|
/// /******* the code above expands to *******/
|
||||||
|
///
|
||||||
|
/// pub fn add_one(x: i32) -> i32 {
|
||||||
|
/// x + 1
|
||||||
|
/// }
|
||||||
|
/// #[napi_derive::napi(js_name = "addOne",)]
|
||||||
|
/// pub fn add_one_napi(x: i32) -> i32 {
|
||||||
|
/// add_one(x)
|
||||||
|
/// }
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Function with `&str` argument
|
||||||
|
/// ```
|
||||||
|
/// # use macros_impl::napi::napi;
|
||||||
|
/// # macros_impl::macro_doctest!({
|
||||||
|
/// #[macros::napi]
|
||||||
|
/// pub fn concatenate_string(str1: &str, str2: &str) -> String {
|
||||||
|
/// str1.to_owned() + str2
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # }, {
|
||||||
|
/// /******* the code above expands to *******/
|
||||||
|
///
|
||||||
|
/// pub fn concatenate_string(str1: &str, str2: &str) -> String {
|
||||||
|
/// str1.to_owned() + str2
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[napi_derive::napi(js_name = "concatenateString",)]
|
||||||
|
/// pub fn concatenate_string_napi(str1: String, str2: String) -> String {
|
||||||
|
/// concatenate_string(&str1, &str2)
|
||||||
|
/// }
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Function with `&[String]` argument
|
||||||
|
/// ```
|
||||||
|
/// # use macros_impl::napi::napi;
|
||||||
|
/// # macros_impl::macro_doctest!({
|
||||||
|
/// #[macros::napi]
|
||||||
|
/// pub fn string_array_length(array: &[String]) -> u32 {
|
||||||
|
/// array.len() as u32
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # }, {
|
||||||
|
/// /******* the code above expands to *******/
|
||||||
|
///
|
||||||
|
/// pub fn string_array_length(array: &[String]) -> u32 {
|
||||||
|
/// array.len() as u32
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[napi_derive::napi(js_name = "stringArrayLength",)]
|
||||||
|
/// pub fn string_array_length_napi(array: Vec<String>) -> u32 {
|
||||||
|
/// string_array_length(&array)
|
||||||
|
/// }
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Function with `Result<T, E>` return type
|
||||||
|
/// ```
|
||||||
|
/// # quote::quote! { // prevent compiling the code
|
||||||
|
/// #[derive(thiserror::Error, Debug)]
|
||||||
|
/// pub enum IntegerDivisionError {
|
||||||
|
/// #[error("Divided by zero")]
|
||||||
|
/// DividedByZero,
|
||||||
|
/// #[error("Not divisible with remainder {0}")]
|
||||||
|
/// NotDivisible(i64),
|
||||||
|
/// }
|
||||||
|
/// # };
|
||||||
|
///
|
||||||
|
/// # use macros_impl::napi::napi;
|
||||||
|
/// # macros_impl::macro_doctest!({
|
||||||
|
/// #[macros::napi]
|
||||||
|
/// pub fn integer_divide(dividend: i64, divisor: i64) -> Result<i64, IntegerDivisionError> {
|
||||||
|
/// match divisor {
|
||||||
|
/// 0 => Err(IntegerDivisionError::DividedByZero),
|
||||||
|
/// _ => match dividend % divisor {
|
||||||
|
/// 0 => Ok(dividend / divisor),
|
||||||
|
/// remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
|
||||||
|
/// },
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// # }, {
|
||||||
|
///
|
||||||
|
/// /******* the function above expands to *******/
|
||||||
|
///
|
||||||
|
/// pub fn integer_divide(dividend: i64, divisor: i64) -> Result<i64, IntegerDivisionError> {
|
||||||
|
/// match divisor {
|
||||||
|
/// 0 => Err(IntegerDivisionError::DividedByZero),
|
||||||
|
/// _ => match dividend % divisor {
|
||||||
|
/// 0 => Ok(dividend / divisor),
|
||||||
|
/// remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
|
||||||
|
/// },
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[napi_derive::napi(js_name = "integerDivide",)]
|
||||||
|
/// pub fn integer_divide_napi(dividend: i64, divisor: i64) -> napi::Result<i64> {
|
||||||
|
/// integer_divide(dividend, divisor)
|
||||||
|
/// .map_err(|err| napi::Error::from_reason(crate::util::error_chain::format_error(&err)))
|
||||||
|
/// }
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn napi(macro_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
let macro_attr_tokens: Vec<TokenTree> = macro_attr.clone().into_iter().collect();
|
||||||
|
// generated extra macro attr TokenStream (prepended before original input `macro_attr`)
|
||||||
|
let mut extra_macro_attr = TokenStream::new();
|
||||||
|
|
||||||
|
let item: syn::Item =
|
||||||
|
syn::parse2(item).expect("Failed to parse input TokenStream to syn::Item");
|
||||||
|
|
||||||
|
// handle non-functions
|
||||||
|
let syn::Item::Fn(item_fn) = item else {
|
||||||
|
// set `use_nullable = true` if `object` or `constructor` present but not `use_nullable`
|
||||||
|
if macro_attr_tokens.iter().any(|token| {
|
||||||
|
matches!(token, TokenTree::Ident(ident) if ident == "object" || ident == "constructor")
|
||||||
|
}) && !macro_attr_tokens.iter().any(|token| {
|
||||||
|
matches!(token, TokenTree::Ident(ident) if ident == "use_nullable")
|
||||||
|
}) {
|
||||||
|
quote! { use_nullable = true, }.to_tokens(&mut extra_macro_attr);
|
||||||
|
}
|
||||||
|
return quote! {
|
||||||
|
#[napi_derive::napi(#extra_macro_attr #macro_attr)]
|
||||||
|
#item
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle functions
|
||||||
|
let ident = &item_fn.sig.ident;
|
||||||
|
let item_fn_attrs = &item_fn.attrs;
|
||||||
|
let item_fn_vis = &item_fn.vis;
|
||||||
|
let mut item_fn_sig = item_fn.sig.clone();
|
||||||
|
let mut function_call_modifiers = Vec::<TokenStream>::new();
|
||||||
|
|
||||||
|
// append "_napi" to function name
|
||||||
|
item_fn_sig.ident = syn::parse_str(&format!("{}_napi", &ident)).unwrap();
|
||||||
|
|
||||||
|
// append `.await` to function call in async function
|
||||||
|
if item_fn_sig.asyncness.is_some() {
|
||||||
|
function_call_modifiers.push(quote! {
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert return type `...::Result<T, ...>` to `napi::Result<T>`
|
||||||
|
if let syn::ReturnType::Type(_, ref mut return_type) = item_fn_sig.output {
|
||||||
|
if let Some(result_generic_type) = (|| {
|
||||||
|
let syn::Type::Path(return_type_path) = &**return_type else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
// match a::b::c::Result
|
||||||
|
let last_segment = return_type_path.path.segments.last()?;
|
||||||
|
if last_segment.ident != "Result" {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
// extract <T, ...> from Result<T, ...>
|
||||||
|
let syn::PathArguments::AngleBracketed(generic_arguments) = &last_segment.arguments
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
// return T only
|
||||||
|
generic_arguments.args.first()
|
||||||
|
})() {
|
||||||
|
// modify return type
|
||||||
|
*return_type = syn::parse_quote! {
|
||||||
|
napi::Result<#result_generic_type>
|
||||||
|
};
|
||||||
|
// add modifier to function call result
|
||||||
|
function_call_modifiers.push(quote! {
|
||||||
|
.map_err(|err| napi::Error::from_reason(crate::util::error_chain::format_error(&err)))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// arguments in function call
|
||||||
|
let called_args: Vec<TokenStream> = item_fn_sig
|
||||||
|
.inputs
|
||||||
|
.iter_mut()
|
||||||
|
.map(|input| match input {
|
||||||
|
// self
|
||||||
|
syn::FnArg::Receiver(arg) => {
|
||||||
|
let mut tokens = TokenStream::new();
|
||||||
|
if let Some((ampersand, lifetime)) = &arg.reference {
|
||||||
|
ampersand.to_tokens(&mut tokens);
|
||||||
|
lifetime.to_tokens(&mut tokens);
|
||||||
|
}
|
||||||
|
arg.mutability.to_tokens(&mut tokens);
|
||||||
|
arg.self_token.to_tokens(&mut tokens);
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
// typed argument
|
||||||
|
syn::FnArg::Typed(arg) => {
|
||||||
|
match &mut *arg.pat {
|
||||||
|
syn::Pat::Ident(ident) => {
|
||||||
|
let name = &ident.ident;
|
||||||
|
match &*arg.ty {
|
||||||
|
// reference type argument => move ref from sigature to function call
|
||||||
|
syn::Type::Reference(r) => {
|
||||||
|
// add reference anotations to arguments in function call
|
||||||
|
let mut tokens = TokenStream::new();
|
||||||
|
r.and_token.to_tokens(&mut tokens);
|
||||||
|
if let Some(lifetime) = &r.lifetime {
|
||||||
|
lifetime.to_tokens(&mut tokens);
|
||||||
|
}
|
||||||
|
r.mutability.to_tokens(&mut tokens);
|
||||||
|
name.to_tokens(&mut tokens);
|
||||||
|
|
||||||
|
// modify napi argument types in function sigature
|
||||||
|
// (1) add `mut` token to `&mut` type
|
||||||
|
ident.mutability = r.mutability;
|
||||||
|
// (2) remove reference
|
||||||
|
*arg.ty = syn::Type::Verbatim(match &*r.elem {
|
||||||
|
syn::Type::Slice(slice) => {
|
||||||
|
let ty = &*slice.elem;
|
||||||
|
quote! { Vec<#ty> }
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let elem_tokens = r.elem.to_token_stream();
|
||||||
|
match elem_tokens.to_string().as_str() {
|
||||||
|
// &str => String
|
||||||
|
"str" => quote! { String },
|
||||||
|
// &T => T
|
||||||
|
_ => elem_tokens,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// return arguments in function call
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
// o.w., return it as is
|
||||||
|
_ => quote! { #name },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pat => panic!("Unexpected FnArg: {pat:#?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// handle macro attr
|
||||||
|
// set js_name if not specified
|
||||||
|
if !macro_attr_tokens
|
||||||
|
.iter()
|
||||||
|
.any(|token| matches!(token, TokenTree::Ident(ident) if ident == "js_name"))
|
||||||
|
{
|
||||||
|
let js_name = ident.to_string().to_case(Case::Camel);
|
||||||
|
quote! { js_name = #js_name, }.to_tokens(&mut extra_macro_attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#item_fn
|
||||||
|
|
||||||
|
#[napi_derive::napi(#extra_macro_attr #macro_attr)]
|
||||||
|
#(#item_fn_attrs)*
|
||||||
|
#item_fn_vis #item_fn_sig {
|
||||||
|
#ident(#(#called_args),*)
|
||||||
|
#(#function_call_modifiers)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::macro_unit_tests! {
|
||||||
|
mut_ref_argument: {
|
||||||
|
#[macros::napi]
|
||||||
|
pub fn append_string_and_clone(
|
||||||
|
base_str: &mut String,
|
||||||
|
appended_str: &str,
|
||||||
|
) -> String {
|
||||||
|
base_str.push_str(appended_str);
|
||||||
|
base_str.to_owned()
|
||||||
|
}
|
||||||
|
} generates {
|
||||||
|
#[napi_derive::napi(js_name = "appendStringAndClone", )]
|
||||||
|
pub fn append_string_and_clone_napi(
|
||||||
|
mut base_str: String,
|
||||||
|
appended_str: String,
|
||||||
|
) -> String {
|
||||||
|
append_string_and_clone(&mut base_str, &appended_str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_return_type: {
|
||||||
|
#[macros::napi]
|
||||||
|
pub fn integer_divide(
|
||||||
|
dividend: i64,
|
||||||
|
divisor: i64,
|
||||||
|
) -> Result<i64, IntegerDivisionError> {
|
||||||
|
match divisor {
|
||||||
|
0 => Err(IntegerDivisionError::DividedByZero),
|
||||||
|
_ => match dividend % divisor {
|
||||||
|
0 => Ok(dividend / divisor),
|
||||||
|
remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} generates {
|
||||||
|
#[napi_derive::napi(js_name = "integerDivide", )]
|
||||||
|
pub fn integer_divide_napi(
|
||||||
|
dividend: i64,
|
||||||
|
divisor: i64,
|
||||||
|
) -> napi::Result<i64> {
|
||||||
|
integer_divide(dividend, divisor)
|
||||||
|
.map_err(|err| napi::Error::from_reason(crate::util::error_chain::format_error(&err)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async_function: {
|
||||||
|
#[macros::napi]
|
||||||
|
pub async fn async_add_one(x: i32) -> i32 {
|
||||||
|
x + 1
|
||||||
|
}
|
||||||
|
} generates {
|
||||||
|
#[napi_derive::napi(js_name = "asyncAddOne", )]
|
||||||
|
pub async fn async_add_one_napi(x: i32) -> i32 {
|
||||||
|
async_add_one(x)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slice_type: {
|
||||||
|
#[macros::napi]
|
||||||
|
pub fn string_array_length(array: &[String]) -> u32 {
|
||||||
|
array.len() as u32
|
||||||
|
}
|
||||||
|
} generates {
|
||||||
|
#[napi_derive::napi(js_name = "stringArrayLength", )]
|
||||||
|
pub fn string_array_length_napi(array: Vec<String>) -> u32 {
|
||||||
|
string_array_length(&array)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_with_explicitly_set_use_nullable: {
|
||||||
|
#[macros::napi(object, use_nullable = false)]
|
||||||
|
struct Person {
|
||||||
|
id: i32,
|
||||||
|
name: Option<String>,
|
||||||
|
}
|
||||||
|
} becomes {
|
||||||
|
#[napi_derive::napi(object, use_nullable = false)]
|
||||||
|
struct Person {
|
||||||
|
id: i32,
|
||||||
|
name: Option<String>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_attr: {
|
||||||
|
#[macros::napi(ts_return_type = "number")]
|
||||||
|
pub fn add_one(x: i32) -> i32 {
|
||||||
|
x + 1
|
||||||
|
}
|
||||||
|
} generates {
|
||||||
|
#[napi_derive::napi(js_name = "addOne", ts_return_type = "number")]
|
||||||
|
pub fn add_one_napi(x: i32) -> i32 {
|
||||||
|
add_one(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
explicitly_specified_js_name_and_other_macro_attr: {
|
||||||
|
#[macros::napi(ts_return_type = "number", js_name = "add1")]
|
||||||
|
pub fn add_one(x: i32) -> i32 {
|
||||||
|
x + 1
|
||||||
|
}
|
||||||
|
} generates {
|
||||||
|
#[napi_derive::napi(ts_return_type = "number", js_name = "add1")]
|
||||||
|
pub fn add_one_napi(x: i32) -> i32 {
|
||||||
|
add_one(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
packages/macro-rs/macros-impl/src/util/mod.rs
Normal file
3
packages/macro-rs/macros-impl/src/util/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
//! Utilities for developing procedural macros
|
||||||
|
|
||||||
|
mod tester;
|
121
packages/macro-rs/macros-impl/src/util/tester.rs
Normal file
121
packages/macro-rs/macros-impl/src/util/tester.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
//! Macros for testing procedural macros
|
||||||
|
|
||||||
|
/// Tests if the macro expands correctly.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use macros_impl::napi::napi;
|
||||||
|
///
|
||||||
|
/// macros_impl::macro_doctest!({
|
||||||
|
/// #[macros::napi(object)]
|
||||||
|
/// struct Person {
|
||||||
|
/// id: i32,
|
||||||
|
/// name: String,
|
||||||
|
/// }
|
||||||
|
/// }, {
|
||||||
|
/// #[napi_derive::napi(use_nullable = true, object)]
|
||||||
|
/// struct Person {
|
||||||
|
/// id: i32,
|
||||||
|
/// name: String,
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! macro_doctest {
|
||||||
|
({
|
||||||
|
#[macros :: $macro_name:ident $(( $($attr:tt)* ))?]
|
||||||
|
$($item:tt)*
|
||||||
|
}, {
|
||||||
|
$($expanded:tt)*
|
||||||
|
}) => {
|
||||||
|
assert_eq!(
|
||||||
|
::std::string::ToString::to_string(
|
||||||
|
&$macro_name(
|
||||||
|
::quote::quote!($( $($attr)* )?),
|
||||||
|
::quote::quote!($($item)*),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
::std::string::ToString::to_string(
|
||||||
|
&::quote::quote!($($expanded)*)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates unit tests for macros.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// macros_impl::macro_unit_tests! {
|
||||||
|
/// add1_becomes: {
|
||||||
|
/// #[macros::napi(js_name = "add1")]
|
||||||
|
/// pub fn add_one(x: i32) -> i32 {
|
||||||
|
/// x + 1
|
||||||
|
/// }
|
||||||
|
/// } becomes { // the code above should expand to the following code
|
||||||
|
/// 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)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // this test case is equivalent to `add1_becomes`
|
||||||
|
/// add1_generates: {
|
||||||
|
/// #[macros::napi(js_name = "add1")]
|
||||||
|
/// pub fn add_one(x: i32) -> i32 {
|
||||||
|
/// x + 1
|
||||||
|
/// }
|
||||||
|
/// } generates { // the code above should generate the following code
|
||||||
|
/// #[napi_derive::napi(js_name = "add1")]
|
||||||
|
/// pub fn add_one_napi(x: i32) -> i32 {
|
||||||
|
/// add_one(x)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export] macro_rules! macro_unit_tests {
|
||||||
|
(@test $macro_name:ident($attr:ident, $item:ident) becomes $expanded:ident) => {
|
||||||
|
assert_eq!(
|
||||||
|
::std::format!("{}", $macro_name($attr, $item)),
|
||||||
|
::std::format!("{}", $expanded),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
(@test $macro_name:ident($attr:ident, $item:ident) generates $expanded:ident) => {
|
||||||
|
let item_str = format!("{}", $item);
|
||||||
|
assert_eq!(
|
||||||
|
::std::format!("{}", $macro_name($attr, $item)),
|
||||||
|
::std::format!("{} {}", item_str, $expanded),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
$test_name:ident : {
|
||||||
|
#[macros :: $macro_name:ident $(( $($attr:tt)* ))?]
|
||||||
|
$($item:tt)*
|
||||||
|
} $op:tt {
|
||||||
|
$($expanded:tt)*
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
) => {
|
||||||
|
#[cfg(test)]
|
||||||
|
mod unit_test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $test_name() {
|
||||||
|
let attr = ::quote::quote!($( $($attr)* )?);
|
||||||
|
let item = ::quote::quote!($($item)*);
|
||||||
|
let expanded = ::quote::quote!($($expanded)*);
|
||||||
|
|
||||||
|
$crate::macro_unit_tests!(@test $macro_name(attr, item) $op expanded);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "macro-rs"
|
name = "macros"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.74"
|
rust-version = "1.74"
|
||||||
|
@ -8,14 +8,9 @@ rust-version = "1.74"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
convert_case = { workspace = true }
|
macros-impl = { workspace = true }
|
||||||
|
|
||||||
proc-macro2 = { workspace = true }
|
proc-macro2 = { workspace = true }
|
||||||
quote = { workspace = true }
|
quote = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true, features = ["std"] }
|
serde_json = { workspace = true, features = ["std"] }
|
||||||
syn = { workspace = true, features = ["extra-traits", "full"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
napi = { workspace = true }
|
|
||||||
napi-derive = { workspace = true, features = ["noop"] }
|
|
88
packages/macro-rs/macros/src/helper.rs
Normal file
88
packages/macro-rs/macros/src/helper.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
//! Helper macros for developing procedural macros
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub(crate) use quote::quote;
|
||||||
|
|
||||||
|
/// Defines wrapper #\[proc_macro_attribute]s.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```ignore
|
||||||
|
/// define_wrapper_proc_macro_attributes! {
|
||||||
|
/// // expand `#[export(attr)]` to
|
||||||
|
/// // ```
|
||||||
|
/// // #[cfg_attr(feature = "napi", macros::napi(#attr))]
|
||||||
|
/// // ```
|
||||||
|
/// export(attr, item) {
|
||||||
|
/// #[cfg_attr(feature = "napi", macros::napi(#attr))]
|
||||||
|
/// #item
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // expand `#[ts_export(attr)]` to
|
||||||
|
/// // ```
|
||||||
|
/// // #[cfg(feature = "napi")]
|
||||||
|
/// // #[macros::napi(#attr)]
|
||||||
|
/// // ```
|
||||||
|
/// ts_export(attr, item) {
|
||||||
|
/// #[cfg(feature = "napi")]
|
||||||
|
/// #[macros::napi(#attr)]
|
||||||
|
/// #item
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
macro_rules! define_wrapper_proc_macro_attributes {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
$(#[$meta:meta])*
|
||||||
|
$macro_name:ident ($arg_attr:ident, $arg_item:ident) {
|
||||||
|
$($body:tt)*
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
$(#[$meta])*
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn $macro_name(
|
||||||
|
attr: ::proc_macro::TokenStream,
|
||||||
|
item: ::proc_macro::TokenStream,
|
||||||
|
) -> ::proc_macro::TokenStream {
|
||||||
|
let $arg_attr: ::proc_macro2::TokenStream = attr.into();
|
||||||
|
let $arg_item: ::proc_macro2::TokenStream = item.into();
|
||||||
|
::quote::quote!($($body)*).into()
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) use define_wrapper_proc_macro_attributes;
|
||||||
|
|
||||||
|
/// Wraps and exports #\[proc_macro_attribute] implementation.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```ignore
|
||||||
|
/// reexport_proc_macro_attributes! {
|
||||||
|
/// // wrap and export [macros_impl::napi::napi] as #[macros::napi]
|
||||||
|
/// macros_impl::napi::napi as napi
|
||||||
|
///
|
||||||
|
/// // wrap and export [macros_impl::errors::errors] as #[macros::errors]
|
||||||
|
/// macros_impl::errors::errors as errors
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
macro_rules! reexport_proc_macro_attributes {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
$(#[$meta:meta])*
|
||||||
|
$impl_path:path as $macro_name:ident
|
||||||
|
)*
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
$(#[$meta])*
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn $macro_name(
|
||||||
|
attr: ::proc_macro::TokenStream,
|
||||||
|
item: ::proc_macro::TokenStream,
|
||||||
|
) -> ::proc_macro::TokenStream {
|
||||||
|
$impl_path(attr.into(), item.into()).into()
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) use reexport_proc_macro_attributes;
|
81
packages/macro-rs/macros/src/lib.rs
Normal file
81
packages/macro-rs/macros/src/lib.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
mod helper;
|
||||||
|
use helper::*;
|
||||||
|
|
||||||
|
/// Reads the version field in the project root package.json at compile time.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// You can get a compile-time constant version number using this macro:
|
||||||
|
/// ```
|
||||||
|
/// # use macros::read_version_from_package_json;
|
||||||
|
/// // VERSION == "YYYYMMDD" (or "YYYYMMDD-X")
|
||||||
|
/// const VERSION: &str = read_version_from_package_json!();
|
||||||
|
/// ```
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn read_version_from_package_json(_item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct PackageJson {
|
||||||
|
version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = std::fs::File::open("package.json").expect("Failed to open package.json");
|
||||||
|
let json: PackageJson = serde_json::from_reader(file).unwrap();
|
||||||
|
let version = &json.version;
|
||||||
|
|
||||||
|
quote!(#version).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
define_wrapper_proc_macro_attributes! {
|
||||||
|
/// Exports an enum to TypeScript, and derive [Clone].
|
||||||
|
///
|
||||||
|
/// You need this macro because [`napi_derive::napi`](https://docs.rs/napi-derive/latest/napi_derive/attr.napi.html)
|
||||||
|
/// automatically derives the [Clone] trait for enums and causes conflicts.
|
||||||
|
///
|
||||||
|
/// This is a wrapper of [`napi_derive::napi`](https://docs.rs/napi-derive/latest/napi_derive/attr.napi.html)
|
||||||
|
/// that expands to
|
||||||
|
/// ```no_run
|
||||||
|
/// #[cfg_attr(not(feature = "napi"), derive(Clone))]
|
||||||
|
/// #[cfg_attr(feature = "napi", napi_derive::napi(attr))]
|
||||||
|
/// # enum E {} // to work around doc test compilation error
|
||||||
|
/// ```
|
||||||
|
/// where `attr` is given attribute(s).
|
||||||
|
derive_clone_and_export(attr, item) {
|
||||||
|
#[cfg_attr(not(feature = "napi"), derive(Clone))]
|
||||||
|
#[cfg_attr(feature = "napi", napi_derive::napi(#attr))]
|
||||||
|
#item
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exports a function, struct, enum, const, etc. to TypeScript.
|
||||||
|
///
|
||||||
|
/// This is a wrapper of [macro@napi] that expands to
|
||||||
|
/// ```no_run
|
||||||
|
/// #[cfg_attr(feature = "napi", macros::napi(attr))]
|
||||||
|
/// # fn f() {} // to work around doc test compilation error
|
||||||
|
/// ```
|
||||||
|
/// where `attr` is given attribute(s). See [macro@napi] for more details.
|
||||||
|
export(attr, item) {
|
||||||
|
#[cfg_attr(feature = "napi", macros::napi(#attr))]
|
||||||
|
#item
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exports a function, struct, enum, const, etc. to TypeScript
|
||||||
|
/// and make it unable to use in Rust.
|
||||||
|
///
|
||||||
|
/// This is a wrapper of [macro@napi] that expands to
|
||||||
|
/// ```no_run
|
||||||
|
/// #[cfg(feature = "napi")]
|
||||||
|
/// #[macros::napi(attr)]
|
||||||
|
/// # fn f() {} // to work around doc test compilation error
|
||||||
|
/// ```
|
||||||
|
/// where `attr` is given attribute(s). See [macro@napi] for more details.
|
||||||
|
ts_export(attr, item) {
|
||||||
|
#[cfg(feature = "napi")]
|
||||||
|
#[macros::napi(#attr)]
|
||||||
|
#item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reexport_proc_macro_attributes! {
|
||||||
|
/// Creates an extra wrapper function for [napi_derive](https://docs.rs/napi-derive/latest/napi_derive/).
|
||||||
|
/// See [macros_impl::napi::napi] for details.
|
||||||
|
macros_impl::napi::napi as napi
|
||||||
|
}
|
|
@ -1,680 +0,0 @@
|
||||||
use convert_case::{Case, Casing};
|
|
||||||
use proc_macro2::{TokenStream, TokenTree};
|
|
||||||
use quote::{quote, ToTokens};
|
|
||||||
|
|
||||||
/// Read the version field in the project root package.json at compile time
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// You can get a compile-time constant version number using this macro:
|
|
||||||
/// ```
|
|
||||||
/// # use macro_rs::read_version_from_package_json;
|
|
||||||
/// // VERSION == "YYYYMMDD" (or "YYYYMMDD-X")
|
|
||||||
/// const VERSION: &str = read_version_from_package_json!();
|
|
||||||
/// ```
|
|
||||||
#[proc_macro]
|
|
||||||
pub fn read_version_from_package_json(_item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|
||||||
#[derive(serde::Deserialize)]
|
|
||||||
struct PackageJson {
|
|
||||||
version: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
let file = std::fs::File::open("package.json").expect("Failed to open package.json");
|
|
||||||
let json: PackageJson = serde_json::from_reader(file).unwrap();
|
|
||||||
let version = &json.version;
|
|
||||||
|
|
||||||
quote! { #version }.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Export an enum to TypeScript, and derive [Clone].
|
|
||||||
///
|
|
||||||
/// You need this macro because [`napi_derive::napi`](https://docs.rs/napi-derive/latest/napi_derive/attr.napi.html)
|
|
||||||
/// automatically derives the [Clone] trait for enums and causes conflicts.
|
|
||||||
///
|
|
||||||
/// This is a wrapper of [`napi_derive::napi`](https://docs.rs/napi-derive/latest/napi_derive/attr.napi.html)
|
|
||||||
/// that expands to
|
|
||||||
/// ```no_run
|
|
||||||
/// #[cfg_attr(not(feature = "napi"), derive(Clone))]
|
|
||||||
/// #[cfg_attr(feature = "napi", napi_derive::napi(attr))]
|
|
||||||
/// # enum E {} // to work around doc test compilation error
|
|
||||||
/// ```
|
|
||||||
/// where `attr` is given attribute(s).
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn derive_clone_and_export(
|
|
||||||
attr: proc_macro::TokenStream,
|
|
||||||
item: proc_macro::TokenStream,
|
|
||||||
) -> proc_macro::TokenStream {
|
|
||||||
let attr: TokenStream = attr.into();
|
|
||||||
let item: TokenStream = item.into();
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#[cfg_attr(not(feature = "napi"), derive(Clone))]
|
|
||||||
#[cfg_attr(feature = "napi", napi_derive::napi(#attr))]
|
|
||||||
#item
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Export a function, struct, enum, const, etc. to TypeScript.
|
|
||||||
///
|
|
||||||
/// This is a wrapper of [macro@napi] that expands to
|
|
||||||
/// ```no_run
|
|
||||||
/// #[cfg_attr(feature = "napi", macro_rs::napi(attr))]
|
|
||||||
/// # fn f() {} // to work around doc test compilation error
|
|
||||||
/// ```
|
|
||||||
/// where `attr` is given attribute(s). See [macro@napi] for more details.
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn export(
|
|
||||||
attr: proc_macro::TokenStream,
|
|
||||||
item: proc_macro::TokenStream,
|
|
||||||
) -> proc_macro::TokenStream {
|
|
||||||
let attr: TokenStream = attr.into();
|
|
||||||
let item: TokenStream = item.into();
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#[cfg_attr(feature = "napi", macro_rs::napi(#attr))]
|
|
||||||
#item
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Export a function, struct, enum, const, etc. to TypeScript
|
|
||||||
/// and make it unable to use in Rust.
|
|
||||||
///
|
|
||||||
/// This is a wrapper of [macro@napi] that expands to
|
|
||||||
/// ```no_run
|
|
||||||
/// #[cfg(feature = "napi")]
|
|
||||||
/// #[macro_rs::napi(attr)]
|
|
||||||
/// # fn f() {} // to work around doc test compilation error
|
|
||||||
/// ```
|
|
||||||
/// where `attr` is given attribute(s). See [macro@napi] for more details.
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn ts_export(
|
|
||||||
attr: proc_macro::TokenStream,
|
|
||||||
item: proc_macro::TokenStream,
|
|
||||||
) -> proc_macro::TokenStream {
|
|
||||||
let attr: TokenStream = attr.into();
|
|
||||||
let item: TokenStream = item.into();
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#[cfg(feature = "napi")]
|
|
||||||
#[macro_rs::napi(#attr)]
|
|
||||||
#item
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an extra wrapper function for [napi_derive](https://docs.rs/napi-derive/latest/napi_derive/).
|
|
||||||
///
|
|
||||||
/// The macro is simply converted into `napi_derive::napi(...)`
|
|
||||||
/// if it is not applied to a function.
|
|
||||||
///
|
|
||||||
/// The macro sets the following attributes by default if not specified:
|
|
||||||
/// - `use_nullable = true` (if `object` or `constructor` attribute is specified)
|
|
||||||
/// - `js_name` to the camelCase version of the original function name (for functions)
|
|
||||||
///
|
|
||||||
/// 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<T>`]
|
|
||||||
/// - `&T` and `&mut T` are converted to `T`
|
|
||||||
/// - Other `T` remains `T`
|
|
||||||
///
|
|
||||||
/// In addition, return type [`Result<T>`] and [`Result<T, E>`] are converted to [`napi::Result<T>`](https://docs.rs/napi/latest/napi/type.Result.html).
|
|
||||||
/// Note that `E` must implement [`std::string::ToString`] trait.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ## Applying the macro to a struct
|
|
||||||
/// ```
|
|
||||||
/// #[macro_rs::napi(object)]
|
|
||||||
/// struct Person {
|
|
||||||
/// id: i32,
|
|
||||||
/// name: String,
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// simply becomes
|
|
||||||
/// ```
|
|
||||||
/// #[napi_derive::napi(use_nullable = true, object)]
|
|
||||||
/// struct Person {
|
|
||||||
/// id: i32,
|
|
||||||
/// name: String,
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Function with explicitly specified `js_name`
|
|
||||||
/// ```
|
|
||||||
/// #[macro_rs::napi(js_name = "add1")]
|
|
||||||
/// pub fn add_one(x: i32) -> i32 {
|
|
||||||
/// x + 1
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// generates
|
|
||||||
/// ```
|
|
||||||
/// # 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
|
|
||||||
/// ```
|
|
||||||
/// #[macro_rs::napi]
|
|
||||||
/// pub fn add_one(x: i32) -> i32 {
|
|
||||||
/// x + 1
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// generates
|
|
||||||
/// ```
|
|
||||||
/// # pub fn add_one(x: i32) -> i32 {
|
|
||||||
/// # x + 1
|
|
||||||
/// # }
|
|
||||||
/// #[napi_derive::napi(js_name = "addOne",)]
|
|
||||||
/// pub fn add_one_napi(x: i32) -> i32 {
|
|
||||||
/// add_one(x)
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Function with `&str` argument
|
|
||||||
/// ```
|
|
||||||
/// #[macro_rs::napi]
|
|
||||||
/// pub fn concatenate_string(str1: &str, str2: &str) -> String {
|
|
||||||
/// str1.to_owned() + str2
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// generates
|
|
||||||
/// ```
|
|
||||||
/// # pub fn concatenate_string(str1: &str, str2: &str) -> String {
|
|
||||||
/// # str1.to_owned() + str2
|
|
||||||
/// # }
|
|
||||||
/// #[napi_derive::napi(js_name = "concatenateString",)]
|
|
||||||
/// pub fn concatenate_string_napi(str1: String, str2: String) -> String {
|
|
||||||
/// concatenate_string(&str1, &str2)
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Function with `&[String]` argument
|
|
||||||
/// ```
|
|
||||||
/// #[macro_rs::napi]
|
|
||||||
/// pub fn string_array_length(array: &[String]) -> u32 {
|
|
||||||
/// array.len() as u32
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// generates
|
|
||||||
/// ```
|
|
||||||
/// # pub fn string_array_length(array: &[String]) -> u32 {
|
|
||||||
/// # array.len() as u32
|
|
||||||
/// # }
|
|
||||||
/// #[napi_derive::napi(js_name = "stringArrayLength",)]
|
|
||||||
/// pub fn string_array_length_napi(array: Vec<String>) -> u32 {
|
|
||||||
/// string_array_length(&array)
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Function with `Result<T, E>` return type
|
|
||||||
/// ```ignore
|
|
||||||
/// #[derive(thiserror::Error, Debug)]
|
|
||||||
/// pub enum IntegerDivisionError {
|
|
||||||
/// #[error("Divided by zero")]
|
|
||||||
/// DividedByZero,
|
|
||||||
/// #[error("Not divisible with remainder = {0}")]
|
|
||||||
/// NotDivisible(i64),
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// #[macro_rs::napi]
|
|
||||||
/// pub fn integer_divide(dividend: i64, divisor: i64) -> Result<i64, IntegerDivisionError> {
|
|
||||||
/// match divisor {
|
|
||||||
/// 0 => Err(IntegerDivisionError::DividedByZero),
|
|
||||||
/// _ => match dividend % divisor {
|
|
||||||
/// 0 => Ok(dividend / divisor),
|
|
||||||
/// remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
|
|
||||||
/// },
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// generates
|
|
||||||
/// ```ignore
|
|
||||||
/// # #[derive(thiserror::Error, Debug)]
|
|
||||||
/// # pub enum IntegerDivisionError {
|
|
||||||
/// # #[error("Divided by zero")]
|
|
||||||
/// # DividedByZero,
|
|
||||||
/// # #[error("Not divisible with remainder = {0}")]
|
|
||||||
/// # NotDivisible(i64),
|
|
||||||
/// # }
|
|
||||||
/// # pub fn integer_divide(dividend: i64, divisor: i64) -> Result<i64, IntegerDivisionError> {
|
|
||||||
/// # match divisor {
|
|
||||||
/// # 0 => Err(IntegerDivisionError::DividedByZero),
|
|
||||||
/// # _ => match dividend % divisor {
|
|
||||||
/// # 0 => Ok(dividend / divisor),
|
|
||||||
/// # remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
|
|
||||||
/// # },
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
/// #[napi_derive::napi(js_name = "integerDivide",)]
|
|
||||||
/// pub fn integer_divide_napi(dividend: i64, divisor: i64) -> napi::Result<i64> {
|
|
||||||
/// integer_divide(dividend, divisor).map_err(|err| napi::Error::from_reason(crate::util::error_chain::format_error(&err)))
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn napi(
|
|
||||||
attr: proc_macro::TokenStream,
|
|
||||||
item: proc_macro::TokenStream,
|
|
||||||
) -> proc_macro::TokenStream {
|
|
||||||
napi_impl(attr.into(), item.into()).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn napi_impl(macro_attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
||||||
let macro_attr_tokens: Vec<TokenTree> = macro_attr.clone().into_iter().collect();
|
|
||||||
// generated extra macro attr TokenStream (prepended before original input `macro_attr`)
|
|
||||||
let mut extra_macro_attr = TokenStream::new();
|
|
||||||
|
|
||||||
let item: syn::Item =
|
|
||||||
syn::parse2(item).expect("Failed to parse input TokenStream to syn::Item");
|
|
||||||
|
|
||||||
// handle non-functions
|
|
||||||
let syn::Item::Fn(item_fn) = item else {
|
|
||||||
// append `use_nullable = true` if `object` or `constructor` present but not `use_nullable`
|
|
||||||
if macro_attr_tokens.iter().any(|token| {
|
|
||||||
matches!(token, TokenTree::Ident(ident) if ident == "object" || ident == "constructor")
|
|
||||||
}) && !macro_attr_tokens.iter().any(|token| {
|
|
||||||
matches!(token, TokenTree::Ident(ident) if ident == "use_nullable")
|
|
||||||
}) {
|
|
||||||
quote! { use_nullable = true, }.to_tokens(&mut extra_macro_attr);
|
|
||||||
}
|
|
||||||
return quote! {
|
|
||||||
#[napi_derive::napi(#extra_macro_attr #macro_attr)]
|
|
||||||
#item
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// handle functions
|
|
||||||
let ident = &item_fn.sig.ident;
|
|
||||||
let item_fn_attrs = &item_fn.attrs;
|
|
||||||
let item_fn_vis = &item_fn.vis;
|
|
||||||
let mut item_fn_sig = item_fn.sig.clone();
|
|
||||||
let mut function_call_modifiers = Vec::<TokenStream>::new();
|
|
||||||
|
|
||||||
// append "_napi" to function name
|
|
||||||
item_fn_sig.ident = syn::parse_str(&format!("{}_napi", &ident)).unwrap();
|
|
||||||
|
|
||||||
// append `.await` to function call in async function
|
|
||||||
if item_fn_sig.asyncness.is_some() {
|
|
||||||
function_call_modifiers.push(quote! {
|
|
||||||
.await
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert return type `...::Result<T, ...>` to `napi::Result<T>`
|
|
||||||
if let syn::ReturnType::Type(_, ref mut return_type) = item_fn_sig.output {
|
|
||||||
if let Some(result_generic_type) = (|| {
|
|
||||||
let syn::Type::Path(return_type_path) = &**return_type else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
// match a::b::c::Result
|
|
||||||
let last_segment = return_type_path.path.segments.last()?;
|
|
||||||
if last_segment.ident != "Result" {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
// extract <T, ...> from Result<T, ...>
|
|
||||||
let syn::PathArguments::AngleBracketed(generic_arguments) = &last_segment.arguments
|
|
||||||
else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
// return T only
|
|
||||||
generic_arguments.args.first()
|
|
||||||
})() {
|
|
||||||
// modify return type
|
|
||||||
*return_type = syn::parse_quote! {
|
|
||||||
napi::Result<#result_generic_type>
|
|
||||||
};
|
|
||||||
// add modifier to function call result
|
|
||||||
function_call_modifiers.push(quote! {
|
|
||||||
.map_err(|err| napi::Error::from_reason(crate::util::error_chain::format_error(&err)))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// arguments in function call
|
|
||||||
let called_args: Vec<TokenStream> = item_fn_sig
|
|
||||||
.inputs
|
|
||||||
.iter_mut()
|
|
||||||
.map(|input| match input {
|
|
||||||
// self
|
|
||||||
syn::FnArg::Receiver(arg) => {
|
|
||||||
let mut tokens = TokenStream::new();
|
|
||||||
if let Some((ampersand, lifetime)) = &arg.reference {
|
|
||||||
ampersand.to_tokens(&mut tokens);
|
|
||||||
lifetime.to_tokens(&mut tokens);
|
|
||||||
}
|
|
||||||
arg.mutability.to_tokens(&mut tokens);
|
|
||||||
arg.self_token.to_tokens(&mut tokens);
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
// typed argument
|
|
||||||
syn::FnArg::Typed(arg) => {
|
|
||||||
match &mut *arg.pat {
|
|
||||||
syn::Pat::Ident(ident) => {
|
|
||||||
let name = &ident.ident;
|
|
||||||
match &*arg.ty {
|
|
||||||
// reference type argument => move ref from sigature to function call
|
|
||||||
syn::Type::Reference(r) => {
|
|
||||||
// add reference anotations to arguments in function call
|
|
||||||
let mut tokens = TokenStream::new();
|
|
||||||
r.and_token.to_tokens(&mut tokens);
|
|
||||||
if let Some(lifetime) = &r.lifetime {
|
|
||||||
lifetime.to_tokens(&mut tokens);
|
|
||||||
}
|
|
||||||
r.mutability.to_tokens(&mut tokens);
|
|
||||||
name.to_tokens(&mut tokens);
|
|
||||||
|
|
||||||
// modify napi argument types in function sigature
|
|
||||||
// (1) add `mut` token to `&mut` type
|
|
||||||
ident.mutability = r.mutability;
|
|
||||||
// (2) remove reference
|
|
||||||
*arg.ty = syn::Type::Verbatim(match &*r.elem {
|
|
||||||
syn::Type::Slice(slice) => {
|
|
||||||
let ty = &*slice.elem;
|
|
||||||
quote! { Vec<#ty> }
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let elem_tokens = r.elem.to_token_stream();
|
|
||||||
match elem_tokens.to_string().as_str() {
|
|
||||||
// &str => String
|
|
||||||
"str" => quote! { String },
|
|
||||||
// &T => T
|
|
||||||
_ => elem_tokens,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// return arguments in function call
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
// o.w., return it as is
|
|
||||||
_ => quote! { #name },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pat => panic!("Unexpected FnArg: {pat:#?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// handle macro attr
|
|
||||||
// append js_name if not specified
|
|
||||||
if !macro_attr_tokens
|
|
||||||
.iter()
|
|
||||||
.any(|token| matches!(token, TokenTree::Ident(ident) if ident == "js_name"))
|
|
||||||
{
|
|
||||||
let js_name = ident.to_string().to_case(Case::Camel);
|
|
||||||
quote! { js_name = #js_name, }.to_tokens(&mut extra_macro_attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#item_fn
|
|
||||||
|
|
||||||
#[napi_derive::napi(#extra_macro_attr #macro_attr)]
|
|
||||||
#(#item_fn_attrs)*
|
|
||||||
#item_fn_vis #item_fn_sig {
|
|
||||||
#ident(#(#called_args),*)
|
|
||||||
#(#function_call_modifiers)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use proc_macro2::TokenStream;
|
|
||||||
use quote::quote;
|
|
||||||
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn primitive_argument() {
|
|
||||||
test_macro_generates!(
|
|
||||||
quote! {
|
|
||||||
pub fn add_one(x: i32) -> i32 {
|
|
||||||
x + 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quote! {
|
|
||||||
#[napi_derive::napi(js_name = "addOne", )]
|
|
||||||
pub fn add_one_napi(x: i32) -> i32 {
|
|
||||||
add_one(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn str_ref_argument() {
|
|
||||||
test_macro_generates!(
|
|
||||||
quote! {
|
|
||||||
pub fn concatenate_string(str1: &str, str2: &str) -> String {
|
|
||||||
str1.to_owned() + str2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quote! {
|
|
||||||
#[napi_derive::napi(js_name = "concatenateString", )]
|
|
||||||
pub fn concatenate_string_napi(str1: String, str2: String) -> String {
|
|
||||||
concatenate_string(&str1, &str2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mut_ref_argument() {
|
|
||||||
test_macro_generates!(
|
|
||||||
quote! {
|
|
||||||
pub fn append_string_and_clone(
|
|
||||||
base_str: &mut String,
|
|
||||||
appended_str: &str,
|
|
||||||
) -> String {
|
|
||||||
base_str.push_str(appended_str);
|
|
||||||
base_str.to_owned()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quote! {
|
|
||||||
#[napi_derive::napi(js_name = "appendStringAndClone", )]
|
|
||||||
pub fn append_string_and_clone_napi(
|
|
||||||
mut base_str: String,
|
|
||||||
appended_str: String,
|
|
||||||
) -> String {
|
|
||||||
append_string_and_clone(&mut base_str, &appended_str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn result_return_type() {
|
|
||||||
test_macro_generates!(
|
|
||||||
quote! {
|
|
||||||
pub fn integer_divide(
|
|
||||||
dividend: i64,
|
|
||||||
divisor: i64,
|
|
||||||
) -> Result<i64, IntegerDivisionError> {
|
|
||||||
match divisor {
|
|
||||||
0 => Err(IntegerDivisionError::DividedByZero),
|
|
||||||
_ => match dividend % divisor {
|
|
||||||
0 => Ok(dividend / divisor),
|
|
||||||
remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quote! {
|
|
||||||
#[napi_derive::napi(js_name = "integerDivide", )]
|
|
||||||
pub fn integer_divide_napi(
|
|
||||||
dividend: i64,
|
|
||||||
divisor: i64,
|
|
||||||
) -> napi::Result<i64> {
|
|
||||||
integer_divide(dividend, divisor)
|
|
||||||
.map_err(|err| napi::Error::from_reason(crate::util::error_chain::format_error(&err)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn async_function() {
|
|
||||||
test_macro_generates!(
|
|
||||||
quote! {
|
|
||||||
pub async fn async_add_one(x: i32) -> i32 {
|
|
||||||
x + 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quote! {
|
|
||||||
#[napi_derive::napi(js_name = "asyncAddOne", )]
|
|
||||||
pub async fn async_add_one_napi(x: i32) -> i32 {
|
|
||||||
async_add_one(x)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn slice_type() {
|
|
||||||
test_macro_generates!(
|
|
||||||
quote! {
|
|
||||||
pub fn string_array_length(array: &[String]) -> u32 {
|
|
||||||
array.len() as u32
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quote! {
|
|
||||||
#[napi_derive::napi(js_name = "stringArrayLength", )]
|
|
||||||
pub fn string_array_length_napi(array: Vec<String>) -> u32 {
|
|
||||||
string_array_length(&array)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn object() {
|
|
||||||
test_macro_becomes!(
|
|
||||||
quote! { object },
|
|
||||||
quote! {
|
|
||||||
struct Person {
|
|
||||||
id: i32,
|
|
||||||
name: Option<String>,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quote! {
|
|
||||||
#[napi_derive::napi(use_nullable = true, object)]
|
|
||||||
struct Person {
|
|
||||||
id: i32,
|
|
||||||
name: Option<String>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn object_with_explicitly_set_use_nullable() {
|
|
||||||
test_macro_becomes!(
|
|
||||||
quote! { object, use_nullable = false },
|
|
||||||
quote! {
|
|
||||||
struct Person {
|
|
||||||
id: i32,
|
|
||||||
name: Option<String>,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quote! {
|
|
||||||
#[napi_derive::napi(object, use_nullable = false)]
|
|
||||||
struct Person {
|
|
||||||
id: i32,
|
|
||||||
name: Option<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(js_name = "addOne", ts_return_type = "number")]
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue