feat(napi): support export rust mod as ts namespace
This commit is contained in:
parent
e4ca46f32b
commit
1fe39ff66d
29 changed files with 639 additions and 336 deletions
|
@ -15,6 +15,7 @@ pub struct NapiFn {
|
|||
pub vis: syn::Visibility,
|
||||
pub parent: Option<Ident>,
|
||||
pub strict: bool,
|
||||
pub js_mod: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -54,6 +55,7 @@ pub struct NapiStruct {
|
|||
pub fields: Vec<NapiStructField>,
|
||||
pub is_tuple: bool,
|
||||
pub kind: NapiStructKind,
|
||||
pub js_mod: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -78,6 +80,7 @@ pub struct NapiImpl {
|
|||
pub js_name: String,
|
||||
pub items: Vec<NapiFn>,
|
||||
pub task_output_type: Option<Type>,
|
||||
pub js_mod: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -85,6 +88,7 @@ pub struct NapiEnum {
|
|||
pub name: Ident,
|
||||
pub js_name: String,
|
||||
pub variants: Vec<NapiEnumVariant>,
|
||||
pub js_mod: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -100,4 +104,11 @@ pub struct NapiConst {
|
|||
pub js_name: String,
|
||||
pub type_name: Type,
|
||||
pub value: Expr,
|
||||
pub js_mod: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NapiMod {
|
||||
pub name: Ident,
|
||||
pub js_name: String,
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::BindgenResult;
|
|||
mod r#const;
|
||||
mod r#enum;
|
||||
mod r#fn;
|
||||
mod js_mod;
|
||||
mod r#struct;
|
||||
|
||||
pub trait TryToTokens {
|
||||
|
@ -27,3 +28,12 @@ fn get_register_ident(name: &str) -> Ident {
|
|||
let new_name = format!("__napi_register__{}", name);
|
||||
Ident::new(&new_name, Span::call_site())
|
||||
}
|
||||
|
||||
fn js_mod_to_token_stream(js_mod: Option<&String>) -> TokenStream {
|
||||
js_mod
|
||||
.map(|i| {
|
||||
let i = format!("{}\0", i);
|
||||
quote! { Some(#i) }
|
||||
})
|
||||
.unwrap_or_else(|| quote! { None })
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use proc_macro2::{Ident, Literal, TokenStream};
|
||||
use quote::ToTokens;
|
||||
|
||||
use crate::{codegen::get_register_ident, BindgenResult, NapiConst, TryToTokens};
|
||||
use crate::{
|
||||
codegen::{get_register_ident, js_mod_to_token_stream},
|
||||
BindgenResult, NapiConst, TryToTokens,
|
||||
};
|
||||
|
||||
impl TryToTokens for NapiConst {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
|
||||
|
@ -26,6 +29,7 @@ impl NapiConst {
|
|||
&format!("__register__const__{}_callback__", register_name),
|
||||
self.name.span(),
|
||||
);
|
||||
let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
|
||||
quote! {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::all)]
|
||||
|
@ -36,7 +40,7 @@ impl NapiConst {
|
|||
#[allow(clippy::all)]
|
||||
#[napi::bindgen_prelude::ctor]
|
||||
fn #register_name() {
|
||||
napi::bindgen_prelude::register_module_export(#js_name_lit, #cb_name);
|
||||
napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #cb_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use proc_macro2::{Ident, Literal, Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
|
||||
use crate::{codegen::get_register_ident, BindgenResult, NapiEnum, TryToTokens};
|
||||
use crate::{
|
||||
codegen::{get_register_ident, js_mod_to_token_stream},
|
||||
BindgenResult, NapiEnum, TryToTokens,
|
||||
};
|
||||
|
||||
impl TryToTokens for NapiEnum {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
|
||||
|
@ -124,6 +127,8 @@ impl NapiEnum {
|
|||
Span::call_site(),
|
||||
);
|
||||
|
||||
let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
|
||||
|
||||
quote! {
|
||||
unsafe fn #callback_name(env: napi::bindgen_prelude::sys::napi_env) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
|
||||
use std::ffi::CString;
|
||||
|
@ -144,7 +149,7 @@ impl NapiEnum {
|
|||
#[allow(clippy::all)]
|
||||
#[napi::bindgen_prelude::ctor]
|
||||
fn #register_name() {
|
||||
napi::bindgen_prelude::register_module_export(#js_name_lit, #callback_name);
|
||||
napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #callback_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use proc_macro2::{Ident, Span, TokenStream};
|
|||
use quote::ToTokens;
|
||||
|
||||
use crate::{
|
||||
codegen::{get_intermediate_ident, get_register_ident},
|
||||
codegen::{get_intermediate_ident, get_register_ident, js_mod_to_token_stream},
|
||||
BindgenResult, CallbackArg, FnKind, FnSelf, NapiFn, NapiFnArgKind, TryToTokens,
|
||||
};
|
||||
|
||||
|
@ -294,12 +294,11 @@ impl NapiFn {
|
|||
let name_len = js_name.len();
|
||||
let module_register_name = get_register_ident(&name_str);
|
||||
let intermediate_ident = get_intermediate_ident(&name_str);
|
||||
|
||||
let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
|
||||
let cb_name = Ident::new(
|
||||
&format!("__register__fn__{}_callback__", name_str),
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
quote! {
|
||||
unsafe fn #cb_name(env: napi::bindgen_prelude::sys::napi_env) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
|
||||
let mut fn_ptr = std::ptr::null_mut();
|
||||
|
@ -324,7 +323,7 @@ impl NapiFn {
|
|||
#[allow(non_snake_case)]
|
||||
#[napi::bindgen_prelude::ctor]
|
||||
fn #module_register_name() {
|
||||
napi::bindgen_prelude::register_module_export(#js_name, #cb_name);
|
||||
napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
9
crates/backend/src/codegen/js_mod.rs
Normal file
9
crates/backend/src/codegen/js_mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use proc_macro2::TokenStream;
|
||||
|
||||
use crate::{NapiMod, TryToTokens};
|
||||
|
||||
impl TryToTokens for NapiMod {
|
||||
fn try_to_tokens(&self, _tokens: &mut TokenStream) -> crate::BindgenResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ use proc_macro2::{Ident, Literal, Span, TokenStream};
|
|||
use quote::ToTokens;
|
||||
|
||||
use crate::{
|
||||
codegen::{get_intermediate_ident, get_register_ident},
|
||||
codegen::{get_intermediate_ident, get_register_ident, js_mod_to_token_stream},
|
||||
BindgenResult, FnKind, NapiImpl, NapiStruct, NapiStructKind, TryToTokens,
|
||||
};
|
||||
|
||||
|
@ -413,13 +413,13 @@ impl NapiStruct {
|
|||
|
||||
props.push(prop);
|
||||
}
|
||||
|
||||
let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
|
||||
quote! {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::all)]
|
||||
#[napi::bindgen_prelude::ctor]
|
||||
fn #struct_register_name() {
|
||||
napi::bindgen_prelude::register_class(#name_str, #js_name, vec![#(#props),*]);
|
||||
napi::bindgen_prelude::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -478,7 +478,7 @@ impl NapiImpl {
|
|||
let mut props: Vec<_> = props.into_iter().collect();
|
||||
props.sort_by_key(|(_, prop)| prop.to_string());
|
||||
let props = props.into_iter().map(|(_, prop)| prop);
|
||||
|
||||
let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
|
||||
Ok(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::all)]
|
||||
|
@ -488,7 +488,7 @@ impl NapiImpl {
|
|||
|
||||
#[napi::bindgen_prelude::ctor]
|
||||
fn #register_name() {
|
||||
napi::bindgen_prelude::register_class(#name_str, #js_name, vec![#(#props),*]);
|
||||
napi::bindgen_prelude::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -54,4 +54,5 @@ napi_ast_impl! {
|
|||
(Impl, NapiImpl),
|
||||
(Enum, NapiEnum),
|
||||
(Const, NapiConst),
|
||||
(Mod, NapiMod),
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mod r#const;
|
||||
mod r#enum;
|
||||
mod r#fn;
|
||||
mod js_mod;
|
||||
pub(crate) mod r#struct;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
@ -13,13 +14,19 @@ pub struct TypeDef {
|
|||
pub kind: String,
|
||||
pub name: String,
|
||||
pub def: String,
|
||||
pub js_mod: Option<String>,
|
||||
}
|
||||
|
||||
impl ToString for TypeDef {
|
||||
fn to_string(&self) -> String {
|
||||
let js_mod = if let Some(js_mod) = &self.js_mod {
|
||||
format!(", \"js_mod\": \"{}\"", js_mod)
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
format!(
|
||||
r#"{{"kind": "{}", "name": "{}", "def": "{}"}}"#,
|
||||
self.kind, self.name, self.def,
|
||||
r#"{{"kind": "{}", "name": "{}", "def": "{}"{}}}"#,
|
||||
self.kind, self.name, self.def, js_mod,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ impl ToTypeDef for NapiConst {
|
|||
&self.js_name,
|
||||
ty_to_ts_type(&self.type_name, false).0
|
||||
),
|
||||
js_mod: self.js_mod.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ impl ToTypeDef for NapiEnum {
|
|||
js_name = &self.js_name,
|
||||
variants = self.gen_ts_variants()
|
||||
),
|
||||
js_mod: self.js_mod.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ impl ToTypeDef for NapiFn {
|
|||
kind: "fn".to_owned(),
|
||||
name: self.js_name.clone(),
|
||||
def,
|
||||
js_mod: self.js_mod.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
crates/backend/src/typegen/js_mod.rs
Normal file
12
crates/backend/src/typegen/js_mod.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use crate::{NapiMod, ToTypeDef, TypeDef};
|
||||
|
||||
impl ToTypeDef for NapiMod {
|
||||
fn to_type_def(&self) -> TypeDef {
|
||||
TypeDef {
|
||||
kind: "mod".to_owned(),
|
||||
name: self.js_name.clone(),
|
||||
def: "".to_owned(),
|
||||
js_mod: None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ impl ToTypeDef for NapiStruct {
|
|||
}),
|
||||
name: self.js_name.to_owned(),
|
||||
def: self.gen_ts_class(),
|
||||
js_mod: self.js_mod.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +45,7 @@ impl ToTypeDef for NapiImpl {
|
|||
.map(|f| f.to_type_def().def)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\\n"),
|
||||
js_mod: self.js_mod.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,10 @@ use napi_derive_backend::{BindgenResult, TryToTokens};
|
|||
|
||||
#[cfg(feature = "type-def")]
|
||||
use napi_derive_backend::{ToTypeDef, TypeDef};
|
||||
use parser::ParseNapi;
|
||||
use parser::{attrs::BindgenAttrs, ParseNapi};
|
||||
use proc_macro::TokenStream as RawStream;
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro2::{TokenStream, TokenTree};
|
||||
use quote::ToTokens;
|
||||
use std::env;
|
||||
#[cfg(feature = "type-def")]
|
||||
use std::{
|
||||
|
@ -23,6 +24,7 @@ use std::{
|
|||
};
|
||||
#[cfg(feature = "compat-mode")]
|
||||
use syn::{fold::Fold, parse_macro_input, ItemFn};
|
||||
use syn::{Attribute, Item};
|
||||
|
||||
/// ```ignore
|
||||
/// #[napi]
|
||||
|
@ -49,21 +51,78 @@ pub fn napi(attr: RawStream, input: RawStream) -> RawStream {
|
|||
|
||||
fn expand(attr: TokenStream, input: TokenStream) -> BindgenResult<TokenStream> {
|
||||
let mut item = syn::parse2::<syn::Item>(input)?;
|
||||
let opts = syn::parse2(attr)?;
|
||||
|
||||
let opts: BindgenAttrs = syn::parse2(attr)?;
|
||||
let mut tokens = proc_macro2::TokenStream::new();
|
||||
|
||||
let napi = item.parse_napi(&mut tokens, opts)?;
|
||||
napi.try_to_tokens(&mut tokens)?;
|
||||
|
||||
#[cfg(feature = "type-def")]
|
||||
if let Ok(type_def_file) = env::var("TYPE_DEF_TMP_PATH") {
|
||||
if let Err(e) = output_type_def(type_def_file, napi.to_type_def()) {
|
||||
println!("Failed to write type def file: {:?}", e);
|
||||
if let Item::Mod(mut js_mod) = item {
|
||||
let js_name = opts.js_name().map_or_else(
|
||||
|| js_mod.ident.to_string(),
|
||||
|(js_name, _)| js_name.to_owned(),
|
||||
);
|
||||
if let Some((_, mut items)) = js_mod.content.clone() {
|
||||
for item in items.iter_mut() {
|
||||
let mut empty_attrs = vec![];
|
||||
if let Some(item_opts) = replace_napi_attr_in_mod(
|
||||
js_name.clone(),
|
||||
match item {
|
||||
syn::Item::Fn(ref mut function) => &mut function.attrs,
|
||||
syn::Item::Struct(ref mut struct_) => &mut struct_.attrs,
|
||||
syn::Item::Enum(ref mut enum_) => &mut enum_.attrs,
|
||||
syn::Item::Const(ref mut const_) => &mut const_.attrs,
|
||||
syn::Item::Impl(ref mut impl_) => &mut impl_.attrs,
|
||||
syn::Item::Mod(mod_) => {
|
||||
let mod_in_mod = mod_
|
||||
.attrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, m)| m.path.segments[0].ident == "napi");
|
||||
if mod_in_mod.is_some() {
|
||||
bail_span!(
|
||||
mod_,
|
||||
"napi module cannot be nested under another napi module"
|
||||
);
|
||||
} else {
|
||||
&mut empty_attrs
|
||||
}
|
||||
}
|
||||
_ => &mut empty_attrs,
|
||||
},
|
||||
) {
|
||||
let napi = item.parse_napi(&mut tokens, item_opts)?;
|
||||
napi.try_to_tokens(&mut tokens)?;
|
||||
#[cfg(feature = "type-def")]
|
||||
if let Ok(type_def_file) = env::var("TYPE_DEF_TMP_PATH") {
|
||||
if let Err(e) = output_type_def(type_def_file, napi.to_type_def()) {
|
||||
println!("Failed to write type def file: {:?}", e);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
item.to_tokens(&mut tokens);
|
||||
};
|
||||
}
|
||||
js_mod.content = None;
|
||||
};
|
||||
}
|
||||
let js_mod_attrs: Vec<Attribute> = js_mod
|
||||
.attrs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|attr| attr.path.segments[0].ident != "napi")
|
||||
.collect();
|
||||
let mod_name = js_mod.ident;
|
||||
let visible = js_mod.vis;
|
||||
let mod_tokens = quote! { #(#js_mod_attrs)* #visible mod #mod_name { #tokens } };
|
||||
Ok(mod_tokens)
|
||||
} else {
|
||||
let napi = item.parse_napi(&mut tokens, opts)?;
|
||||
napi.try_to_tokens(&mut tokens)?;
|
||||
|
||||
Ok(tokens)
|
||||
#[cfg(feature = "type-def")]
|
||||
if let Ok(type_def_file) = env::var("TYPE_DEF_TMP_PATH") {
|
||||
if let Err(e) = output_type_def(type_def_file, napi.to_type_def()) {
|
||||
println!("Failed to write type def file: {:?}", e);
|
||||
};
|
||||
}
|
||||
Ok(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "type-def")]
|
||||
|
@ -213,3 +272,49 @@ pub fn module_exports(_attr: RawStream, input: RawStream) -> RawStream {
|
|||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn replace_napi_attr_in_mod(
|
||||
js_namespace: String,
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
) -> Option<BindgenAttrs> {
|
||||
let napi_attr = attrs.clone();
|
||||
let napi_attr = napi_attr
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, m)| m.path.segments[0].ident == "napi");
|
||||
if let Some((index, napi_attr)) = napi_attr {
|
||||
let attr_token_stream = napi_attr.tokens.clone();
|
||||
let raw_attr_stream = attr_token_stream.to_string();
|
||||
let raw_attr_stream = if !raw_attr_stream.is_empty() {
|
||||
raw_attr_stream
|
||||
.strip_prefix('(')
|
||||
.unwrap()
|
||||
.strip_suffix(')')
|
||||
.unwrap()
|
||||
.to_string()
|
||||
} else {
|
||||
raw_attr_stream
|
||||
};
|
||||
let raw_attr_token_stream = syn::parse_str::<TokenStream>(raw_attr_stream.as_str()).unwrap();
|
||||
|
||||
let new_attr: syn::Attribute = if !raw_attr_stream.is_empty() {
|
||||
syn::parse_quote!(
|
||||
#[napi(#raw_attr_token_stream, namespace = #js_namespace)]
|
||||
)
|
||||
} else {
|
||||
syn::parse_quote!(
|
||||
#[napi(namespace = #js_namespace)]
|
||||
)
|
||||
};
|
||||
let struct_opts: BindgenAttrs;
|
||||
if let Some(TokenTree::Group(g)) = new_attr.tokens.into_iter().next() {
|
||||
struct_opts = syn::parse2(g.stream()).ok()?;
|
||||
} else {
|
||||
struct_opts = syn::parse2(quote! {}).ok()?;
|
||||
}
|
||||
attrs.remove(index);
|
||||
Some(struct_opts)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ struct AttributeParseState {
|
|||
checks: Cell<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Parsed attributes from a `#[napi(..)]`.
|
||||
pub struct BindgenAttrs {
|
||||
/// Whether `#[napi]` attribute exists
|
||||
|
@ -51,7 +52,7 @@ macro_rules! attrgen {
|
|||
(skip, Skip(Span)),
|
||||
(strict, Strict(Span)),
|
||||
(object, Object(Span)),
|
||||
(task, Task(Span)),
|
||||
(namespace, Namespace(Span, String, Span)),
|
||||
|
||||
// impl later
|
||||
// (inspectable, Inspectable(Span)),
|
||||
|
@ -169,11 +170,11 @@ impl BindgenAttrs {
|
|||
.enumerate()
|
||||
.find(|&(_, m)| m.path.segments[0].ident == "napi");
|
||||
|
||||
let pos = match &napi_attr {
|
||||
let pos = match napi_attr {
|
||||
Some((pos, raw_attr)) => {
|
||||
ret.exists = true;
|
||||
ret.span = raw_attr.tokens.span();
|
||||
*pos
|
||||
pos
|
||||
}
|
||||
None => return Ok(ret),
|
||||
};
|
||||
|
@ -216,6 +217,7 @@ impl Default for BindgenAttrs {
|
|||
macro_rules! gen_bindgen_attr {
|
||||
($( ($method:ident, $($variants:tt)*) ,)*) => {
|
||||
/// The possible attributes in the `#[napi]`.
|
||||
#[derive(Debug)]
|
||||
pub enum BindgenAttr {
|
||||
$($($variants)*,)*
|
||||
}
|
||||
|
@ -243,7 +245,6 @@ pub fn record_struct(ident: &Ident, js_name: String, opts: &BindgenAttrs) {
|
|||
pub fn check_recorded_struct_for_impl(ident: &Ident, opts: &BindgenAttrs) -> BindgenResult<String> {
|
||||
STRUCTS.with(|state| {
|
||||
let struct_name = ident.to_string();
|
||||
|
||||
let mut map = state.parsed.borrow_mut();
|
||||
if let Some(parsed) = map.get_mut(&struct_name) {
|
||||
if opts.constructor().is_some() && !cfg!(debug_assertions) {
|
||||
|
|
|
@ -574,6 +574,7 @@ fn napi_fn_from_decl(
|
|||
parent: parent.cloned(),
|
||||
attrs,
|
||||
strict: opts.strict().is_some(),
|
||||
js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -588,7 +589,7 @@ impl ParseNapi for syn::Item {
|
|||
syn::Item::Const(c) => c.parse_napi(tokens, opts),
|
||||
_ => bail_span!(
|
||||
self,
|
||||
"#[napi] can only be applied to a function, struct, enum or impl."
|
||||
"#[napi] can only be applied to a function, struct, enum, const, mod or impl."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -735,7 +736,6 @@ impl ConvertToAST for syn::ItemStruct {
|
|||
setter: !(ignored || readonly),
|
||||
})
|
||||
}
|
||||
|
||||
record_struct(&struct_name, js_name.clone(), &opts);
|
||||
|
||||
Diagnostic::from_vec(errors).map(|()| Napi {
|
||||
|
@ -747,13 +747,14 @@ impl ConvertToAST for syn::ItemStruct {
|
|||
fields,
|
||||
is_tuple,
|
||||
kind: struct_kind,
|
||||
js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ConvertToAST for syn::ItemImpl {
|
||||
fn convert_to_ast(&mut self, _opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
fn convert_to_ast(&mut self, impl_opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
let struct_name = match get_ty(&self.self_ty) {
|
||||
syn::Type::Path(syn::TypePath {
|
||||
ref path,
|
||||
|
@ -823,6 +824,7 @@ impl ConvertToAST for syn::ItemImpl {
|
|||
js_name: struct_js_name,
|
||||
items,
|
||||
task_output_type,
|
||||
js_mod: impl_opts.namespace().map(|(m, _)| m.to_owned()),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
@ -916,6 +918,7 @@ impl ConvertToAST for syn::ItemEnum {
|
|||
name: self.ident.clone(),
|
||||
js_name,
|
||||
variants,
|
||||
js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
@ -933,6 +936,7 @@ impl ConvertToAST for syn::ItemConst {
|
|||
.map_or_else(|| self.ident.to_string(), |(s, _)| s.to_string()),
|
||||
type_name: *self.ty.clone(),
|
||||
value: *self.expr.clone(),
|
||||
js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
|
||||
}),
|
||||
}),
|
||||
_ => bail_span!(self, "only public const allowed"),
|
||||
|
|
|
@ -21,6 +21,7 @@ pub struct i64n(pub i64);
|
|||
|
||||
/// <https://nodejs.org/api/n-api.html#napi_create_bigint_words>
|
||||
/// The resulting BigInt is calculated as: (–1)^sign_bit (words\[0\] × (2^64)^0 + words\[1\] × (2^64)^1 + …)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BigInt {
|
||||
/// true for negative numbers
|
||||
pub sign_bit: bool,
|
||||
|
|
|
@ -9,9 +9,15 @@ pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result<sys::napi_v
|
|||
pub type ModuleExportsCallback =
|
||||
unsafe fn(env: sys::napi_env, exports: sys::napi_value) -> Result<()>;
|
||||
|
||||
type ModuleRegisterCallback =
|
||||
RefCell<Vec<(Option<&'static str>, (&'static str, ExportRegisterCallback))>>;
|
||||
|
||||
type ModuleClassProperty =
|
||||
RefCell<HashMap<&'static str, HashMap<Option<&'static str>, (&'static str, Vec<Property>)>>>;
|
||||
|
||||
thread_local! {
|
||||
static MODULE_REGISTER_CALLBACK: RefCell<Vec<(&'static str, ExportRegisterCallback)>> = Default::default();
|
||||
static MODULE_CLASS_PROPERTIES: RefCell<HashMap<&'static str, (&'static str, Vec<Property>)>> = Default::default();
|
||||
static MODULE_REGISTER_CALLBACK: ModuleRegisterCallback = Default::default();
|
||||
static MODULE_CLASS_PROPERTIES: ModuleClassProperty = Default::default();
|
||||
static REGISTERED_CLASSES: RefCell<HashMap<
|
||||
/* export name */ &'static str,
|
||||
/* constructor */ sys::napi_ref,
|
||||
|
@ -34,17 +40,27 @@ pub fn register_module_exports(callback: ModuleExportsCallback) {
|
|||
MODULE_EXPORTS.with(|cell| cell.set(vec![callback]));
|
||||
}
|
||||
|
||||
pub fn register_module_export(name: &'static str, cb: ExportRegisterCallback) {
|
||||
pub fn register_module_export(
|
||||
js_mod: Option<&'static str>,
|
||||
name: &'static str,
|
||||
cb: ExportRegisterCallback,
|
||||
) {
|
||||
MODULE_REGISTER_CALLBACK.with(|exports| {
|
||||
let mut list = exports.borrow_mut();
|
||||
list.push((name, cb));
|
||||
list.push((js_mod, (name, cb)));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn register_class(rust_name: &'static str, js_name: &'static str, props: Vec<Property>) {
|
||||
pub fn register_class(
|
||||
rust_name: &'static str,
|
||||
js_mod: Option<&'static str>,
|
||||
js_name: &'static str,
|
||||
props: Vec<Property>,
|
||||
) {
|
||||
MODULE_CLASS_PROPERTIES.with(|map| {
|
||||
let mut map = map.borrow_mut();
|
||||
let val = map.entry(rust_name).or_default();
|
||||
let val = val.entry(js_mod).or_default();
|
||||
|
||||
val.0 = js_name;
|
||||
val.1.extend(props.into_iter());
|
||||
|
@ -56,73 +72,158 @@ unsafe extern "C" fn napi_register_module_v1(
|
|||
env: sys::napi_env,
|
||||
exports: sys::napi_value,
|
||||
) -> sys::napi_value {
|
||||
let mut exports_objects: HashMap<Option<&'static str>, sys::napi_value> = HashMap::default();
|
||||
MODULE_REGISTER_CALLBACK.with(|to_register_exports| {
|
||||
to_register_exports
|
||||
.take()
|
||||
.iter_mut()
|
||||
.fold(
|
||||
HashMap::<Option<&'static str>, Vec<(&'static str, ExportRegisterCallback)>>::new(),
|
||||
|mut acc, (js_mod, item)| {
|
||||
if let Some(k) = acc.get_mut(js_mod) {
|
||||
k.push(*item);
|
||||
} else {
|
||||
acc.insert(*js_mod, vec![*item]);
|
||||
}
|
||||
acc
|
||||
},
|
||||
)
|
||||
.iter()
|
||||
.for_each(|(name, callback)| {
|
||||
let js_name = CStr::from_bytes_with_nul_unchecked(name.as_bytes());
|
||||
unsafe {
|
||||
if let Err(e) = callback(env).and_then(|v| {
|
||||
check_status!(
|
||||
sys::napi_set_named_property(env, exports, js_name.as_ptr(), v),
|
||||
"Failed to register export `{}`",
|
||||
name,
|
||||
)
|
||||
}) {
|
||||
JsError::from(e).throw_into(env)
|
||||
.for_each(|(js_mod, items)| {
|
||||
let mut exports_js_mod = ptr::null_mut();
|
||||
if let Some(js_mod_str) = js_mod {
|
||||
if let Some(exports_object) = exports_objects.get(js_mod) {
|
||||
exports_js_mod = *exports_object;
|
||||
} else {
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
sys::napi_create_object(env, &mut exports_js_mod),
|
||||
"Create export JavaScript Object [{}] failed",
|
||||
js_mod_str
|
||||
);
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
sys::napi_set_named_property(
|
||||
env,
|
||||
exports,
|
||||
js_mod_str.as_ptr() as *const _,
|
||||
exports_js_mod
|
||||
),
|
||||
"Set exports Object [{}] into exports object failed",
|
||||
js_mod_str
|
||||
);
|
||||
exports_objects.insert(*js_mod, exports_js_mod);
|
||||
}
|
||||
}
|
||||
for (name, callback) in items {
|
||||
let js_name = CStr::from_bytes_with_nul_unchecked(name.as_bytes());
|
||||
unsafe {
|
||||
if let Err(e) = callback(env).and_then(|v| {
|
||||
check_status!(
|
||||
sys::napi_set_named_property(
|
||||
env,
|
||||
if exports_js_mod.is_null() {
|
||||
exports
|
||||
} else {
|
||||
exports_js_mod
|
||||
},
|
||||
js_name.as_ptr(),
|
||||
v
|
||||
),
|
||||
"Failed to register export `{}`",
|
||||
name,
|
||||
)
|
||||
}) {
|
||||
JsError::from(e).throw_into(env)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
MODULE_CLASS_PROPERTIES.with(|to_register_classes| {
|
||||
for (rust_name, (js_name, props)) in to_register_classes.take().into_iter() {
|
||||
unsafe {
|
||||
let (ctor, props): (Vec<_>, Vec<_>) = props.into_iter().partition(|prop| prop.is_ctor);
|
||||
// one or more or zero?
|
||||
// zero is for `#[napi(task)]`
|
||||
if ctor.is_empty() && props.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let ctor = ctor.get(0).map(|c| c.raw().method.unwrap()).unwrap_or(noop);
|
||||
let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect();
|
||||
for (rust_name, js_mods) in to_register_classes.take().iter() {
|
||||
for (js_mod, (js_name, props)) in js_mods {
|
||||
let mut exports_js_mod = ptr::null_mut();
|
||||
unsafe {
|
||||
if let Some(js_mod_str) = js_mod {
|
||||
if let Some(exports_object) = exports_objects.get(js_mod) {
|
||||
exports_js_mod = *exports_object;
|
||||
} else {
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
sys::napi_create_object(env, &mut exports_js_mod),
|
||||
"Create export JavaScript Object [{}] failed",
|
||||
js_mod_str
|
||||
);
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
sys::napi_set_named_property(
|
||||
env,
|
||||
exports,
|
||||
js_mod_str.as_ptr() as *const _,
|
||||
exports_js_mod
|
||||
),
|
||||
"Set exports Object [{}] into exports object failed",
|
||||
js_mod_str
|
||||
);
|
||||
exports_objects.insert(*js_mod, exports_js_mod);
|
||||
}
|
||||
}
|
||||
let (ctor, props): (Vec<_>, Vec<_>) = props.iter().partition(|prop| prop.is_ctor);
|
||||
// one or more or zero?
|
||||
// zero is for `#[napi(task)]`
|
||||
if ctor.is_empty() && props.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let ctor = ctor.get(0).map(|c| c.raw().method.unwrap()).unwrap_or(noop);
|
||||
let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect();
|
||||
|
||||
let js_class_name = CStr::from_bytes_with_nul_unchecked(js_name.as_bytes());
|
||||
let mut class_ptr = ptr::null_mut();
|
||||
let js_class_name = CStr::from_bytes_with_nul_unchecked(js_name.as_bytes());
|
||||
let mut class_ptr = ptr::null_mut();
|
||||
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
sys::napi_define_class(
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
js_class_name.as_ptr(),
|
||||
js_name.len(),
|
||||
Some(ctor),
|
||||
ptr::null_mut(),
|
||||
raw_props.len(),
|
||||
raw_props.as_ptr(),
|
||||
&mut class_ptr,
|
||||
),
|
||||
"Failed to register class `{}` generate by struct `{}`",
|
||||
&js_name,
|
||||
&rust_name
|
||||
);
|
||||
sys::napi_define_class(
|
||||
env,
|
||||
js_class_name.as_ptr(),
|
||||
js_name.len(),
|
||||
Some(ctor),
|
||||
ptr::null_mut(),
|
||||
raw_props.len(),
|
||||
raw_props.as_ptr(),
|
||||
&mut class_ptr,
|
||||
),
|
||||
"Failed to register class `{}` generate by struct `{}`",
|
||||
&js_name,
|
||||
&rust_name
|
||||
);
|
||||
|
||||
let mut ctor_ref = ptr::null_mut();
|
||||
sys::napi_create_reference(env, class_ptr, 1, &mut ctor_ref);
|
||||
let mut ctor_ref = ptr::null_mut();
|
||||
sys::napi_create_reference(env, class_ptr, 1, &mut ctor_ref);
|
||||
|
||||
REGISTERED_CLASSES.with(|registered_classes| {
|
||||
let mut registered_class = registered_classes.borrow_mut();
|
||||
registered_class.insert(js_name, ctor_ref);
|
||||
});
|
||||
REGISTERED_CLASSES.with(|registered_classes| {
|
||||
let mut registered_class = registered_classes.borrow_mut();
|
||||
registered_class.insert(js_name, ctor_ref);
|
||||
});
|
||||
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
sys::napi_set_named_property(env, exports, js_class_name.as_ptr(), class_ptr),
|
||||
"Failed to register class `{}` generate by struct `{}`",
|
||||
&js_name,
|
||||
&rust_name
|
||||
);
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
sys::napi_set_named_property(
|
||||
env,
|
||||
if exports_js_mod.is_null() {
|
||||
exports
|
||||
} else {
|
||||
exports_js_mod
|
||||
},
|
||||
js_class_name.as_ptr(),
|
||||
class_ptr
|
||||
),
|
||||
"Failed to register class `{}` generate by struct `{}`",
|
||||
&js_name,
|
||||
&rust_name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue