diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 7d5c1e0c..cbacc189 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -16,6 +16,8 @@ pub struct NapiFn { pub parent: Option, pub strict: bool, pub js_mod: Option, + pub ts_args_type: Option, + pub ts_return_type: Option, } #[derive(Debug, Clone)] diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 1d7ca257..60ef40fc 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -67,6 +67,7 @@ static KNOWN_TYPES: Lazy> = Lazy::new(|| { ("char", "string"), ("JsObject", "object"), ("Object", "object"), + ("Array", "unknown[]"), ("Value", "any"), ("Map", "Record"), ("HashMap", "Record<{}, {}>"), diff --git a/crates/backend/src/typegen/fn.rs b/crates/backend/src/typegen/fn.rs index 6f2ed754..24e0cc82 100644 --- a/crates/backend/src/typegen/fn.rs +++ b/crates/backend/src/typegen/fn.rs @@ -11,8 +11,15 @@ impl ToTypeDef for NapiFn { r#"{prefix} {name}({args}){ret}"#, prefix = self.gen_ts_func_prefix(), name = &self.js_name, - args = self.gen_ts_func_args(), - ret = self.gen_ts_func_ret(), + args = self + .ts_args_type + .clone() + .unwrap_or_else(|| self.gen_ts_func_args()), + ret = self + .ts_return_type + .clone() + .map(|t| format!(": {}", t)) + .unwrap_or_else(|| self.gen_ts_func_ret()), ); TypeDef { diff --git a/crates/macro/src/parser/attrs.rs b/crates/macro/src/parser/attrs.rs index a53e8272..a04da0ef 100644 --- a/crates/macro/src/parser/attrs.rs +++ b/crates/macro/src/parser/attrs.rs @@ -53,12 +53,13 @@ macro_rules! attrgen { (strict, Strict(Span)), (object, Object(Span)), (namespace, Namespace(Span, String, Span)), + (ts_args_type, TsArgsType(Span, String, Span)), + (ts_return_type, TsReturnType(Span, String, Span)), // impl later // (inspectable, Inspectable(Span)), // (typescript_custom_section, TypescriptCustomSection(Span)), // (skip_typescript, SkipTypescript(Span)), - // (typescript_type, TypeScriptType(Span, String, Span)), // (getter_with_clone, GetterWithClone(Span)), // For testing purposes only. diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index 9d6a2b65..fa0b109b 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -575,6 +575,8 @@ fn napi_fn_from_decl( attrs, strict: opts.strict().is_some(), js_mod: opts.namespace().map(|(m, _)| m.to_owned()), + ts_args_type: opts.ts_args_type().map(|(m, _)| m.to_owned()), + ts_return_type: opts.ts_return_type().map(|(m, _)| m.to_owned()), } }) } @@ -603,6 +605,12 @@ impl ParseNapi for syn::ItemFn { } impl ParseNapi for syn::ItemStruct { fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult { + if opts.ts_args_type().is_some() || opts.ts_return_type().is_some() { + bail_span!( + self, + "#[napi] can't be applied to a struct with #[napi(ts_args_type)] or #[napi(ts_return_type)]" + ); + } let napi = self.convert_to_ast(opts); self.to_tokens(tokens); @@ -611,6 +619,12 @@ impl ParseNapi for syn::ItemStruct { } impl ParseNapi for syn::ItemImpl { fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult { + if opts.ts_args_type().is_some() || opts.ts_return_type().is_some() { + bail_span!( + self, + "#[napi] can't be applied to impl with #[napi(ts_args_type)] or #[napi(ts_return_type)]" + ); + } // #[napi] macro will be remove from impl items after converted to ast let napi = self.convert_to_ast(opts); self.to_tokens(tokens); @@ -618,18 +632,28 @@ impl ParseNapi for syn::ItemImpl { napi } } - 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() { + bail_span!( + self, + "#[napi] can't be applied to a enum with #[napi(ts_args_type)] or #[napi(ts_return_type)]" + ); + } let napi = self.convert_to_ast(opts); self.to_tokens(tokens); napi } } - 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() { + bail_span!( + self, + "#[napi] can't be applied to a const with #[napi(ts_args_type)] or #[napi(ts_return_type)]" + ); + } let napi = self.convert_to_ast(opts); self.to_tokens(tokens); napi diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 31303f56..d2d2f385 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -42,6 +42,7 @@ Generated by [AVA](https://avajs.dev). export function createExternalString(content: string): ExternalObject␊ export function getExternal(external: ExternalObject): number␊ export function mutateExternal(external: ExternalObject, newVal: number): void␊ + export function tsRename(a: { foo: number }): string[]␊ export function xxh64Alias(input: Buffer): BigInt␊ export function mapOption(val?: number | undefined | null): number | undefined | null␊ export function add(a: number, b: number): number␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 1357fa79..ac8bd40a 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index ecd626a0..01c83b59 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -55,6 +55,7 @@ import { xxh2, xxh3, xxh64Alias, + tsRename, } from '../' test('export const', (t) => { @@ -194,6 +195,10 @@ test('Result', (t) => { t.throws(() => throwError(), null, 'Manual Error') }) +test('function ts type override', (t) => { + t.deepEqual(tsRename({ foo: 1, bar: 2, baz: 2 }), ['foo', 'bar', 'baz']) +}) + test('serde-json', (t) => { const packageJson = readPackageJson() t.is(packageJson.name, 'napi-rs') diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index d9afc5fd..40eadb14 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -32,6 +32,7 @@ export function createExternal(size: number): ExternalObject export function createExternalString(content: string): ExternalObject export function getExternal(external: ExternalObject): number export function mutateExternal(external: ExternalObject, newVal: number): void +export function tsRename(a: { foo: number }): string[] export function xxh64Alias(input: Buffer): BigInt export function mapOption(val?: number | undefined | null): number | undefined | null export function add(a: number, b: number): number diff --git a/examples/napi/src/fn_ts_override.rs b/examples/napi/src/fn_ts_override.rs new file mode 100644 index 00000000..7793325f --- /dev/null +++ b/examples/napi/src/fn_ts_override.rs @@ -0,0 +1,6 @@ +use napi::bindgen_prelude::{Object, Result}; + +#[napi(ts_args_type = "a: { foo: number }", ts_return_type = "string[]")] +fn ts_rename(a: Object) -> Result { + a.get_property_names() +} diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index 751d16c9..9cf239a1 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -16,6 +16,7 @@ mod either; mod r#enum; mod error; mod external; +mod fn_ts_override; mod js_mod; mod nullable; mod number;