From 40651714c9aec7b01277c403f2ea2c01f9545ee2 Mon Sep 17 00:00:00 2001 From: Jose L Date: Sun, 23 Jan 2022 02:45:41 -0800 Subject: [PATCH] feat(napi-derive): add `ts_type` attribute to override typtescript type for fields in structs --- crates/backend/src/ast.rs | 1 + crates/backend/src/typegen/struct.rs | 3 +++ crates/macro/src/parser/attrs.rs | 1 + crates/macro/src/parser/mod.rs | 24 +++++++++++++++----- examples/napi/__test__/typegen.spec.ts.md | 4 ++++ examples/napi/__test__/typegen.spec.ts.snap | Bin 2362 -> 2397 bytes examples/napi/index.d.ts | 4 ++++ examples/napi/src/object.rs | 9 ++++++++ 8 files changed, 40 insertions(+), 6 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 687f5748..140c0e26 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -79,6 +79,7 @@ pub struct NapiStructField { pub setter: bool, pub comments: Vec, pub skip_typescript: bool, + pub ts_type: Option, } #[derive(Debug, Clone)] diff --git a/crates/backend/src/typegen/struct.rs b/crates/backend/src/typegen/struct.rs index d1bee1ca..d09047bc 100644 --- a/crates/backend/src/typegen/struct.rs +++ b/crates/backend/src/typegen/struct.rs @@ -89,7 +89,10 @@ impl NapiStruct { if !f.setter { field_str.push_str("readonly ") } + let (arg, is_optional) = ty_to_ts_type(&f.ty, false); + let arg = f.ts_type.as_ref().map(|ty| ty.to_string()).unwrap_or(arg); + let sep = if is_optional { "?" } else { "" }; let arg = format!("{}{}: {}", &f.js_name, sep, arg); if self.kind == NapiStructKind::Constructor { diff --git a/crates/macro/src/parser/attrs.rs b/crates/macro/src/parser/attrs.rs index afd00aa6..e066ed6c 100644 --- a/crates/macro/src/parser/attrs.rs +++ b/crates/macro/src/parser/attrs.rs @@ -54,6 +54,7 @@ macro_rules! attrgen { (namespace, Namespace(Span, String, Span)), (ts_args_type, TsArgsType(Span, String, Span)), (ts_return_type, TsReturnType(Span, String, Span)), + (ts_type, TsType(Span, String, Span)), // impl later // (inspectable, Inspectable(Span)), diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index 23c9c7e1..cae6fd52 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -602,6 +602,12 @@ impl ParseNapi for syn::Item { impl ParseNapi for syn::ItemFn { fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult { + if opts.ts_type().is_some() { + bail_span!( + self, + "#[napi] can't be applied to a function with #[napi(ts_type)]" + ); + } self.to_tokens(tokens); self.convert_to_ast(opts) } @@ -611,10 +617,11 @@ impl ParseNapi for syn::ItemStruct { if opts.ts_args_type().is_some() || opts.ts_return_type().is_some() || opts.skip_typescript().is_some() + || opts.ts_type().is_some() { bail_span!( self, - "#[napi] can't be applied to a struct with #[napi(ts_args_type)] or #[napi(ts_return_type)] or #[napi(skip_typescript)]" + "#[napi] can't be applied to a struct with #[napi(ts_args_type)], #[napi(ts_return_type)], #[napi(skip_typescript)] or #[napi(ts_type)]" ); } let napi = self.convert_to_ast(opts); @@ -628,10 +635,11 @@ impl ParseNapi for syn::ItemImpl { if opts.ts_args_type().is_some() || opts.ts_return_type().is_some() || opts.skip_typescript().is_some() + || opts.ts_type().is_some() { bail_span!( self, - "#[napi] can't be applied to impl with #[napi(ts_args_type)] or #[napi(ts_return_type)] or #[napi(skip_typescript)]" + "#[napi] can't be applied to impl with #[napi(ts_args_type)], #[napi(ts_return_type)], #[napi(skip_typescript)] or #[napi(ts_type)]" ); } // #[napi] macro will be remove from impl items after converted to ast @@ -643,10 +651,11 @@ impl ParseNapi for syn::ItemImpl { } impl ParseNapi for syn::ItemEnum { fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult { - if opts.ts_args_type().is_some() || opts.ts_return_type().is_some() { + if opts.ts_args_type().is_some() || opts.ts_return_type().is_some() || opts.ts_type().is_some() + { bail_span!( self, - "#[napi] can't be applied to a enum with #[napi(ts_args_type)] or #[napi(ts_return_type)]" + "#[napi] can't be applied to a enum with #[napi(ts_args_type)], #[napi(ts_return_type)] or #[napi(ts_type)]" ); } let napi = self.convert_to_ast(opts); @@ -657,10 +666,11 @@ impl ParseNapi for syn::ItemEnum { } impl ParseNapi for syn::ItemConst { fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult { - if opts.ts_args_type().is_some() || opts.ts_return_type().is_some() { + if opts.ts_args_type().is_some() || opts.ts_return_type().is_some() || opts.ts_type().is_some() + { bail_span!( self, - "#[napi] can't be applied to a const with #[napi(ts_args_type)] or #[napi(ts_return_type)]" + "#[napi] can't be applied to a const with #[napi(ts_args_type)], #[napi(ts_return_type)] or #[napi(ts_type)]" ); } let napi = self.convert_to_ast(opts); @@ -760,6 +770,7 @@ impl ConvertToAST for syn::ItemStruct { let ignored = field_opts.skip().is_some(); let readonly = field_opts.readonly().is_some(); let skip_typescript = field_opts.skip_typescript().is_some(); + let ts_type = field_opts.ts_type().map(|e| e.0.to_string()); fields.push(NapiStructField { name, @@ -769,6 +780,7 @@ impl ConvertToAST for syn::ItemStruct { setter: !(ignored || readonly), comments: extract_doc_comments(&field.attrs), skip_typescript, + ts_type, }) } diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 61d797e9..7812868e 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -102,6 +102,10 @@ Generated by [AVA](https://avajs.dev). }␊ export function receiveStrictObject(strictObject: StrictObject): void␊ export function getStrFromObject(): void␊ + export interface TsTypeChanged {␊ + typeOverride: object␊ + typeOverrideOptional?: object␊ + }␊ export function asyncPlus100(p: Promise): Promise␊ /** This is an interface for package.json */␊ export interface PackageJson {␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 6da8ac63cf3d82676b385d6fbf7c040d75150413..90bb2d66e15ff43a2621be1e8c138b031c62f1f6 100644 GIT binary patch literal 2397 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 diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index a747fe77..8c1f4e5c 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -92,6 +92,10 @@ export interface StrictObject { } export function receiveStrictObject(strictObject: StrictObject): void export function getStrFromObject(): void +export interface TsTypeChanged { + typeOverride: object + typeOverrideOptional?: object +} export function asyncPlus100(p: Promise): Promise /** This is an interface for package.json */ export interface PackageJson { diff --git a/examples/napi/src/object.rs b/examples/napi/src/object.rs index bab1e228..9e4720a2 100644 --- a/examples/napi/src/object.rs +++ b/examples/napi/src/object.rs @@ -76,3 +76,12 @@ pub fn get_str_from_object(env: Env) { obj.set("name", "value").unwrap(); assert_eq!(obj.get("name").unwrap(), Some("value")); } + +#[napi(object)] +pub struct TsTypeChanged { + #[napi(ts_type = "object")] + pub type_override: String, + + #[napi(ts_type = "object")] + pub type_override_optional: Option, +}