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 comments: Vec<String>,
|
||||
pub parent_is_generator: bool,
|
||||
pub writable: bool,
|
||||
pub enumerable: bool,
|
||||
pub configurable: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -94,6 +97,9 @@ pub struct NapiStructField {
|
|||
pub ty: syn::Type,
|
||||
pub getter: bool,
|
||||
pub setter: bool,
|
||||
pub writable: bool,
|
||||
pub enumerable: bool,
|
||||
pub configurable: bool,
|
||||
pub comments: Vec<String>,
|
||||
pub skip_typescript: bool,
|
||||
pub ts_type: Option<String>,
|
||||
|
|
|
@ -7,6 +7,11 @@ mod r#enum;
|
|||
mod r#fn;
|
||||
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 {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()>;
|
||||
|
||||
|
|
|
@ -695,9 +695,21 @@ impl NapiStruct {
|
|||
}
|
||||
|
||||
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! {
|
||||
napi::bindgen_prelude::Property::new(#js_name)
|
||||
.unwrap()
|
||||
.with_property_attributes(napi::bindgen_prelude::PropertyAttributes::from_bits(#attribute).unwrap())
|
||||
};
|
||||
|
||||
if field.getter {
|
||||
|
@ -705,7 +717,7 @@ impl NapiStruct {
|
|||
(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());
|
||||
(quote! { .with_setter(#setter_name) }).to_tokens(&mut prop);
|
||||
}
|
||||
|
@ -757,9 +769,20 @@ impl NapiImpl {
|
|||
let intermediate_name = get_intermediate_ident(&item_str);
|
||||
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(|| {
|
||||
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>)),
|
||||
(setter, Setter(Span, Option<Ident>)),
|
||||
(readonly, Readonly(Span)),
|
||||
(enumerable, Enumerable(Span, Option<bool>)),
|
||||
(writable, Writable(Span, Option<bool>)),
|
||||
(configurable, Configurable(Span, Option<bool>)),
|
||||
(skip, Skip(Span)),
|
||||
(strict, Strict(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>)) => {
|
||||
pub fn $name(&self) -> Option<(&[String], &[Span])> {
|
||||
self.attrs
|
||||
|
|
|
@ -111,6 +111,21 @@ impl Parse for BindgenAttr {
|
|||
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>)) => ({
|
||||
input.parse::<Token![=]>()?;
|
||||
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()),
|
||||
skip_typescript: opts.skip_typescript().is_some(),
|
||||
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 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 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(),
|
||||
getter: !ignored,
|
||||
setter: !(ignored || readonly),
|
||||
writable,
|
||||
enumerable,
|
||||
configurable,
|
||||
comments: extract_doc_comments(&field.attrs),
|
||||
skip_typescript,
|
||||
ts_type,
|
||||
|
|
|
@ -51,6 +51,7 @@ tokio_time = ["tokio/time"]
|
|||
ctor = "0.1"
|
||||
once_cell = "1"
|
||||
thread_local = "1"
|
||||
bitflags = "1"
|
||||
|
||||
[dependencies.napi-sys]
|
||||
version = "2.2.2"
|
||||
|
|
|
@ -2,6 +2,8 @@ use std::convert::From;
|
|||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
use crate::{sys, Callback, NapiRaw, Result};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -29,31 +31,25 @@ impl Default for Property {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum PropertyAttributes {
|
||||
Default = sys::PropertyAttributes::default,
|
||||
Writable = sys::PropertyAttributes::writable,
|
||||
Enumerable = sys::PropertyAttributes::enumerable,
|
||||
Configurable = sys::PropertyAttributes::configurable,
|
||||
Static = sys::PropertyAttributes::static_,
|
||||
bitflags! {
|
||||
pub struct PropertyAttributes: i32 {
|
||||
const Default = sys::PropertyAttributes::default;
|
||||
const Writable = sys::PropertyAttributes::writable;
|
||||
const Enumerable = sys::PropertyAttributes::enumerable;
|
||||
const Configurable = sys::PropertyAttributes::configurable;
|
||||
const Static = sys::PropertyAttributes::static_;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PropertyAttributes {
|
||||
fn default() -> Self {
|
||||
PropertyAttributes::Default
|
||||
PropertyAttributes::Configurable | PropertyAttributes::Enumerable | PropertyAttributes::Writable
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PropertyAttributes> for sys::napi_property_attributes {
|
||||
fn from(value: PropertyAttributes) -> Self {
|
||||
match value {
|
||||
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_,
|
||||
}
|
||||
value.bits()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#![deny(clippy::all)]
|
||||
#![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
|
||||
//!
|
||||
|
|
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 optionOnly(optional?: string | undefined | null): string␊
|
||||
}␊
|
||||
export class NotWritableClass {␊
|
||||
name: string␊
|
||||
constructor(name: string)␊
|
||||
setName(name: string): void␊
|
||||
}␊
|
||||
export class ClassWithFactory {␊
|
||||
name: string␊
|
||||
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.whoami(), 'Dog: 旺财')
|
||||
|
||||
t.notThrows(() => {
|
||||
const rawMethod = dog.whoami
|
||||
dog.whoami = function (...args) {
|
||||
return rawMethod.apply(this, args)
|
||||
}
|
||||
})
|
||||
|
||||
dog.name = '可乐'
|
||||
t.is(dog.name, '可乐')
|
||||
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 optionOnly(optional?: string | undefined | null): string
|
||||
}
|
||||
export class NotWritableClass {
|
||||
name: string
|
||||
constructor(name: string)
|
||||
setName(name: string): void
|
||||
}
|
||||
export class ClassWithFactory {
|
||||
name: string
|
||||
static withName(name: string): ClassWithFactory
|
||||
|
|
|
@ -347,3 +347,17 @@ pub fn receive_object_with_class_field(
|
|||
) -> Result<ClassInstance<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