feat(napi-derive): support set property attribute in napi macro (#1257)
This commit is contained in:
parent
b54e698237
commit
0f14799776
14 changed files with 139 additions and 18 deletions
|
@ -23,6 +23,9 @@ pub struct NapiFn {
|
||||||
pub skip_typescript: bool,
|
pub skip_typescript: bool,
|
||||||
pub comments: Vec<String>,
|
pub comments: Vec<String>,
|
||||||
pub parent_is_generator: bool,
|
pub parent_is_generator: bool,
|
||||||
|
pub writable: bool,
|
||||||
|
pub enumerable: bool,
|
||||||
|
pub configurable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -94,6 +97,9 @@ pub struct NapiStructField {
|
||||||
pub ty: syn::Type,
|
pub ty: syn::Type,
|
||||||
pub getter: bool,
|
pub getter: bool,
|
||||||
pub setter: bool,
|
pub setter: bool,
|
||||||
|
pub writable: bool,
|
||||||
|
pub enumerable: bool,
|
||||||
|
pub configurable: bool,
|
||||||
pub comments: Vec<String>,
|
pub comments: Vec<String>,
|
||||||
pub skip_typescript: bool,
|
pub skip_typescript: bool,
|
||||||
pub ts_type: Option<String>,
|
pub ts_type: Option<String>,
|
||||||
|
|
|
@ -7,6 +7,11 @@ mod r#enum;
|
||||||
mod r#fn;
|
mod r#fn;
|
||||||
mod r#struct;
|
mod r#struct;
|
||||||
|
|
||||||
|
pub const PROPERTY_ATTRIBUTE_DEFAULT: i32 = 0;
|
||||||
|
pub const PROPERTY_ATTRIBUTE_WRITABLE: i32 = 1 << 0;
|
||||||
|
pub const PROPERTY_ATTRIBUTE_ENUMERABLE: i32 = 1 << 1;
|
||||||
|
pub const PROPERTY_ATTRIBUTE_CONFIGURABLE: i32 = 1 << 2;
|
||||||
|
|
||||||
pub trait TryToTokens {
|
pub trait TryToTokens {
|
||||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()>;
|
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()>;
|
||||||
|
|
||||||
|
|
|
@ -695,9 +695,21 @@ impl NapiStruct {
|
||||||
}
|
}
|
||||||
|
|
||||||
let js_name = &field.js_name;
|
let js_name = &field.js_name;
|
||||||
|
let mut attribute = super::PROPERTY_ATTRIBUTE_DEFAULT;
|
||||||
|
if field.writable {
|
||||||
|
attribute |= super::PROPERTY_ATTRIBUTE_WRITABLE;
|
||||||
|
}
|
||||||
|
if field.enumerable {
|
||||||
|
attribute |= super::PROPERTY_ATTRIBUTE_ENUMERABLE;
|
||||||
|
}
|
||||||
|
if field.configurable {
|
||||||
|
attribute |= super::PROPERTY_ATTRIBUTE_CONFIGURABLE;
|
||||||
|
}
|
||||||
|
|
||||||
let mut prop = quote! {
|
let mut prop = quote! {
|
||||||
napi::bindgen_prelude::Property::new(#js_name)
|
napi::bindgen_prelude::Property::new(#js_name)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.with_property_attributes(napi::bindgen_prelude::PropertyAttributes::from_bits(#attribute).unwrap())
|
||||||
};
|
};
|
||||||
|
|
||||||
if field.getter {
|
if field.getter {
|
||||||
|
@ -705,7 +717,7 @@ impl NapiStruct {
|
||||||
(quote! { .with_getter(#getter_name) }).to_tokens(&mut prop);
|
(quote! { .with_getter(#getter_name) }).to_tokens(&mut prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
if field.setter {
|
if field.writable && field.setter {
|
||||||
let setter_name = Ident::new(&format!("set_{}", field_name), Span::call_site());
|
let setter_name = Ident::new(&format!("set_{}", field_name), Span::call_site());
|
||||||
(quote! { .with_setter(#setter_name) }).to_tokens(&mut prop);
|
(quote! { .with_setter(#setter_name) }).to_tokens(&mut prop);
|
||||||
}
|
}
|
||||||
|
@ -757,9 +769,20 @@ impl NapiImpl {
|
||||||
let intermediate_name = get_intermediate_ident(&item_str);
|
let intermediate_name = get_intermediate_ident(&item_str);
|
||||||
methods.push(item.try_to_token_stream()?);
|
methods.push(item.try_to_token_stream()?);
|
||||||
|
|
||||||
|
let mut attribute = super::PROPERTY_ATTRIBUTE_DEFAULT;
|
||||||
|
if item.writable {
|
||||||
|
attribute |= super::PROPERTY_ATTRIBUTE_WRITABLE;
|
||||||
|
}
|
||||||
|
if item.enumerable {
|
||||||
|
attribute |= super::PROPERTY_ATTRIBUTE_ENUMERABLE;
|
||||||
|
}
|
||||||
|
if item.configurable {
|
||||||
|
attribute |= super::PROPERTY_ATTRIBUTE_CONFIGURABLE;
|
||||||
|
}
|
||||||
|
|
||||||
let prop = props.entry(&item.js_name).or_insert_with(|| {
|
let prop = props.entry(&item.js_name).or_insert_with(|| {
|
||||||
quote! {
|
quote! {
|
||||||
napi::bindgen_prelude::Property::new(#js_name).unwrap()
|
napi::bindgen_prelude::Property::new(#js_name).unwrap().with_property_attributes(napi::bindgen_prelude::PropertyAttributes::from_bits(#attribute).unwrap())
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,9 @@ macro_rules! attrgen {
|
||||||
(getter, Getter(Span, Option<Ident>)),
|
(getter, Getter(Span, Option<Ident>)),
|
||||||
(setter, Setter(Span, Option<Ident>)),
|
(setter, Setter(Span, Option<Ident>)),
|
||||||
(readonly, Readonly(Span)),
|
(readonly, Readonly(Span)),
|
||||||
|
(enumerable, Enumerable(Span, Option<bool>)),
|
||||||
|
(writable, Writable(Span, Option<bool>)),
|
||||||
|
(configurable, Configurable(Span, Option<bool>)),
|
||||||
(skip, Skip(Span)),
|
(skip, Skip(Span)),
|
||||||
(strict, Strict(Span)),
|
(strict, Strict(Span)),
|
||||||
(return_if_invalid, ReturnIfInvalid(Span)),
|
(return_if_invalid, ReturnIfInvalid(Span)),
|
||||||
|
@ -116,6 +119,22 @@ macro_rules! methods {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(@method $name:ident, $variant:ident(Span, Option<bool>)) => {
|
||||||
|
pub fn $name(&self) -> bool {
|
||||||
|
self.attrs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|a| match &a.1 {
|
||||||
|
BindgenAttr::$variant(_, s) => {
|
||||||
|
a.0.set(true);
|
||||||
|
*s
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
(@method $name:ident, $variant:ident(Span, Vec<String>, Vec<Span>)) => {
|
(@method $name:ident, $variant:ident(Span, Vec<String>, Vec<Span>)) => {
|
||||||
pub fn $name(&self) -> Option<(&[String], &[Span])> {
|
pub fn $name(&self) -> Option<(&[String], &[Span])> {
|
||||||
self.attrs
|
self.attrs
|
||||||
|
|
|
@ -111,6 +111,21 @@ impl Parse for BindgenAttr {
|
||||||
return Ok(BindgenAttr::$variant(attr_span, val, span))
|
return Ok(BindgenAttr::$variant(attr_span, val, span))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
(@parser $variant:ident(Span, Option<bool>)) => ({
|
||||||
|
if let Ok(_) = input.parse::<Token![=]>() {
|
||||||
|
let (val, _) = match input.parse::<syn::LitBool>() {
|
||||||
|
Ok(str) => (str.value(), str.span()),
|
||||||
|
Err(_) => {
|
||||||
|
let ident = input.parse::<AnyIdent>()?.0;
|
||||||
|
(true, ident.span())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Ok::<BindgenAttr, syn::Error>(BindgenAttr::$variant(attr_span, Some(val)))
|
||||||
|
} else {
|
||||||
|
return Ok(BindgenAttr::$variant(attr_span, Some(true)))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
(@parser $variant:ident(Span, Vec<String>, Vec<Span>)) => ({
|
(@parser $variant:ident(Span, Vec<String>, Vec<Span>)) => ({
|
||||||
input.parse::<Token![=]>()?;
|
input.parse::<Token![=]>()?;
|
||||||
let (vals, spans) = match input.parse::<syn::ExprArray>() {
|
let (vals, spans) = match input.parse::<syn::ExprArray>() {
|
||||||
|
@ -677,6 +692,9 @@ fn napi_fn_from_decl(
|
||||||
ts_return_type: opts.ts_return_type().map(|(m, _)| m.to_owned()),
|
ts_return_type: opts.ts_return_type().map(|(m, _)| m.to_owned()),
|
||||||
skip_typescript: opts.skip_typescript().is_some(),
|
skip_typescript: opts.skip_typescript().is_some(),
|
||||||
parent_is_generator,
|
parent_is_generator,
|
||||||
|
writable: opts.writable(),
|
||||||
|
enumerable: opts.enumerable(),
|
||||||
|
configurable: opts.configurable(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -868,6 +886,9 @@ impl ConvertToAST for syn::ItemStruct {
|
||||||
|
|
||||||
let ignored = field_opts.skip().is_some();
|
let ignored = field_opts.skip().is_some();
|
||||||
let readonly = field_opts.readonly().is_some();
|
let readonly = field_opts.readonly().is_some();
|
||||||
|
let writable = field_opts.writable();
|
||||||
|
let enumerable = field_opts.enumerable();
|
||||||
|
let configurable = field_opts.configurable();
|
||||||
let skip_typescript = field_opts.skip_typescript().is_some();
|
let skip_typescript = field_opts.skip_typescript().is_some();
|
||||||
let ts_type = field_opts.ts_type().map(|e| e.0.to_string());
|
let ts_type = field_opts.ts_type().map(|e| e.0.to_string());
|
||||||
|
|
||||||
|
@ -877,6 +898,9 @@ impl ConvertToAST for syn::ItemStruct {
|
||||||
ty: field.ty.clone(),
|
ty: field.ty.clone(),
|
||||||
getter: !ignored,
|
getter: !ignored,
|
||||||
setter: !(ignored || readonly),
|
setter: !(ignored || readonly),
|
||||||
|
writable,
|
||||||
|
enumerable,
|
||||||
|
configurable,
|
||||||
comments: extract_doc_comments(&field.attrs),
|
comments: extract_doc_comments(&field.attrs),
|
||||||
skip_typescript,
|
skip_typescript,
|
||||||
ts_type,
|
ts_type,
|
||||||
|
|
|
@ -51,6 +51,7 @@ tokio_time = ["tokio/time"]
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
thread_local = "1"
|
thread_local = "1"
|
||||||
|
bitflags = "1"
|
||||||
|
|
||||||
[dependencies.napi-sys]
|
[dependencies.napi-sys]
|
||||||
version = "2.2.2"
|
version = "2.2.2"
|
||||||
|
|
|
@ -2,6 +2,8 @@ use std::convert::From;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
|
use bitflags::bitflags;
|
||||||
|
|
||||||
use crate::{sys, Callback, NapiRaw, Result};
|
use crate::{sys, Callback, NapiRaw, Result};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -29,31 +31,25 @@ impl Default for Property {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(i32)]
|
bitflags! {
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
pub struct PropertyAttributes: i32 {
|
||||||
pub enum PropertyAttributes {
|
const Default = sys::PropertyAttributes::default;
|
||||||
Default = sys::PropertyAttributes::default,
|
const Writable = sys::PropertyAttributes::writable;
|
||||||
Writable = sys::PropertyAttributes::writable,
|
const Enumerable = sys::PropertyAttributes::enumerable;
|
||||||
Enumerable = sys::PropertyAttributes::enumerable,
|
const Configurable = sys::PropertyAttributes::configurable;
|
||||||
Configurable = sys::PropertyAttributes::configurable,
|
const Static = sys::PropertyAttributes::static_;
|
||||||
Static = sys::PropertyAttributes::static_,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PropertyAttributes {
|
impl Default for PropertyAttributes {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PropertyAttributes::Default
|
PropertyAttributes::Configurable | PropertyAttributes::Enumerable | PropertyAttributes::Writable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PropertyAttributes> for sys::napi_property_attributes {
|
impl From<PropertyAttributes> for sys::napi_property_attributes {
|
||||||
fn from(value: PropertyAttributes) -> Self {
|
fn from(value: PropertyAttributes) -> Self {
|
||||||
match value {
|
value.bits()
|
||||||
PropertyAttributes::Default => sys::PropertyAttributes::default,
|
|
||||||
PropertyAttributes::Writable => sys::PropertyAttributes::writable,
|
|
||||||
PropertyAttributes::Enumerable => sys::PropertyAttributes::enumerable,
|
|
||||||
PropertyAttributes::Configurable => sys::PropertyAttributes::configurable,
|
|
||||||
PropertyAttributes::Static => sys::PropertyAttributes::static_,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
#![forbid(unsafe_op_in_unsafe_fn)]
|
#![forbid(unsafe_op_in_unsafe_fn)]
|
||||||
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
//! High level Node.js [N-API](https://nodejs.org/api/n-api.html) binding
|
//! High level Node.js [N-API](https://nodejs.org/api/n-api.html) binding
|
||||||
//!
|
//!
|
||||||
|
|
15
examples/napi/__test__/object-attr.spec.ts
Normal file
15
examples/napi/__test__/object-attr.spec.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import test from 'ava'
|
||||||
|
|
||||||
|
import { NotWritableClass } from '../index'
|
||||||
|
|
||||||
|
test('Not Writable Class', (t) => {
|
||||||
|
const obj = new NotWritableClass('1')
|
||||||
|
t.throws(() => {
|
||||||
|
obj.name = '2'
|
||||||
|
})
|
||||||
|
obj.setName('2')
|
||||||
|
t.is(obj.name, '2')
|
||||||
|
t.throws(() => {
|
||||||
|
obj.setName = () => {}
|
||||||
|
})
|
||||||
|
})
|
|
@ -277,6 +277,11 @@ Generated by [AVA](https://avajs.dev).
|
||||||
static optionStartEnd(optional1: string | undefined | null, required: string, optional2?: string | undefined | null): string␊
|
static optionStartEnd(optional1: string | undefined | null, required: string, optional2?: string | undefined | null): string␊
|
||||||
static optionOnly(optional?: string | undefined | null): string␊
|
static optionOnly(optional?: string | undefined | null): string␊
|
||||||
}␊
|
}␊
|
||||||
|
export class NotWritableClass {␊
|
||||||
|
name: string␊
|
||||||
|
constructor(name: string)␊
|
||||||
|
setName(name: string): void␊
|
||||||
|
}␊
|
||||||
export class ClassWithFactory {␊
|
export class ClassWithFactory {␊
|
||||||
name: string␊
|
name: string␊
|
||||||
static withName(name: string): ClassWithFactory␊
|
static withName(name: string): ClassWithFactory␊
|
||||||
|
|
Binary file not shown.
|
@ -162,6 +162,13 @@ test('class', (t) => {
|
||||||
t.is(dog.kind, Kind.Dog)
|
t.is(dog.kind, Kind.Dog)
|
||||||
t.is(dog.whoami(), 'Dog: 旺财')
|
t.is(dog.whoami(), 'Dog: 旺财')
|
||||||
|
|
||||||
|
t.notThrows(() => {
|
||||||
|
const rawMethod = dog.whoami
|
||||||
|
dog.whoami = function (...args) {
|
||||||
|
return rawMethod.apply(this, args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
dog.name = '可乐'
|
dog.name = '可乐'
|
||||||
t.is(dog.name, '可乐')
|
t.is(dog.name, '可乐')
|
||||||
t.deepEqual(dog.returnOtherClass(), new Dog('Doge'))
|
t.deepEqual(dog.returnOtherClass(), new Dog('Doge'))
|
||||||
|
|
5
examples/napi/index.d.ts
vendored
5
examples/napi/index.d.ts
vendored
|
@ -267,6 +267,11 @@ export class Optional {
|
||||||
static optionStartEnd(optional1: string | undefined | null, required: string, optional2?: string | undefined | null): string
|
static optionStartEnd(optional1: string | undefined | null, required: string, optional2?: string | undefined | null): string
|
||||||
static optionOnly(optional?: string | undefined | null): string
|
static optionOnly(optional?: string | undefined | null): string
|
||||||
}
|
}
|
||||||
|
export class NotWritableClass {
|
||||||
|
name: string
|
||||||
|
constructor(name: string)
|
||||||
|
setName(name: string): void
|
||||||
|
}
|
||||||
export class ClassWithFactory {
|
export class ClassWithFactory {
|
||||||
name: string
|
name: string
|
||||||
static withName(name: string): ClassWithFactory
|
static withName(name: string): ClassWithFactory
|
||||||
|
|
|
@ -347,3 +347,17 @@ pub fn receive_object_with_class_field(
|
||||||
) -> Result<ClassInstance<Bird>> {
|
) -> Result<ClassInstance<Bird>> {
|
||||||
Ok(object.bird)
|
Ok(object.bird)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi(constructor)]
|
||||||
|
pub struct NotWritableClass {
|
||||||
|
#[napi(writable = false)]
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
impl NotWritableClass {
|
||||||
|
#[napi(writable = false)]
|
||||||
|
pub fn set_name(&mut self, name: String) {
|
||||||
|
self.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue