feat(napi-derive): support set property attribute in napi macro (#1257)

This commit is contained in:
LongYinan 2022-08-06 21:54:58 +08:00 committed by GitHub
parent b54e698237
commit 0f14799776
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 139 additions and 18 deletions

View file

@ -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>,

View file

@ -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<()>;

View file

@ -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())
}
});

View file

@ -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

View file

@ -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,

View file

@ -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"

View file

@ -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()
}
}

View file

@ -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
//!

View 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 = () => {}
})
})

View file

@ -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␊

View file

@ -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'))

View file

@ -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

View file

@ -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;
}
}