diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 75efceb7..916fbd37 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -213,9 +213,9 @@ fn fill_ty(template: &str, args: Vec) -> String { ret } -pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> (String, bool) { +pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool, is_struct_field: bool) -> (String, bool) { match ty { - Type::Reference(r) => ty_to_ts_type(&r.elem, is_return_ty), + Type::Reference(r) => ty_to_ts_type(&r.elem, is_return_ty, is_struct_field), Type::Tuple(tuple) => { if tuple.elems.is_empty() { ("undefined".to_owned(), false) @@ -226,7 +226,7 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> (String, bool) { tuple .elems .iter() - .map(|elem| ty_to_ts_type(elem, false).0) + .map(|elem| ty_to_ts_type(elem, false, false).0) .collect::>() .join(", ") ), @@ -244,7 +244,9 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> (String, bool) { .args .iter() .filter_map(|arg| match arg { - syn::GenericArgument::Type(generic_ty) => Some(ty_to_ts_type(generic_ty, false)), + syn::GenericArgument::Type(generic_ty) => { + Some(ty_to_ts_type(generic_ty, false, false)) + } _ => None, }) .collect::>() @@ -255,9 +257,18 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> (String, bool) { if rust_ty == "Result" && is_return_ty { ts_ty = Some(args.first().unwrap().to_owned()); } else if rust_ty == "Option" { - ts_ty = args - .first() - .map(|(arg, _)| (format!("{} | undefined | null", arg), true)); + ts_ty = args.first().map(|(arg, _)| { + ( + if is_struct_field { + arg.to_string() + } else if is_return_ty { + format!("{}?", arg) + } else { + format!("{} | undefined | null", arg) + }, + true, + ) + }); } else if rust_ty == "AsyncTask" { ts_ty = r#struct::TASK_STRUCTS.with(|t| { let (output_type, _) = args.first().unwrap().to_owned(); @@ -299,7 +310,7 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> (String, bool) { ts_ty.unwrap_or_else(|| ("any".to_owned(), false)) } - Type::Group(g) => ty_to_ts_type(&g.elem, is_return_ty), + Type::Group(g) => ty_to_ts_type(&g.elem, is_return_ty, is_struct_field), _ => ("any".to_owned(), false), } } diff --git a/crates/backend/src/typegen/const.rs b/crates/backend/src/typegen/const.rs index 7def2c14..e926608d 100644 --- a/crates/backend/src/typegen/const.rs +++ b/crates/backend/src/typegen/const.rs @@ -17,7 +17,7 @@ impl ToTypeDef for NapiConst { def: format!( "export const {}: {}", &self.js_name, - ty_to_ts_type(&self.type_name, false).0 + ty_to_ts_type(&self.type_name, false, false).0 ), js_mod: self.js_mod.to_owned(), js_doc: js_doc_from_comments(&self.comments), diff --git a/crates/backend/src/typegen/fn.rs b/crates/backend/src/typegen/fn.rs index b1b2133d..91f65407 100644 --- a/crates/backend/src/typegen/fn.rs +++ b/crates/backend/src/typegen/fn.rs @@ -97,7 +97,7 @@ fn gen_callback_type(callback: &CallbackArg) -> String { .iter() .enumerate() .map(|(i, arg)| { - let (ts_type, is_optional) = ty_to_ts_type(arg, false); + let (ts_type, is_optional) = ty_to_ts_type(arg, false, false); FnArg { arg: format!("arg{}", i), ts_type, @@ -106,7 +106,7 @@ fn gen_callback_type(callback: &CallbackArg) -> String { }) .collect::(), ret = match &callback.ret { - Some(ty) => ty_to_ts_type(ty, true).0, + Some(ty) => ty_to_ts_type(ty, true, false).0, None => "void".to_owned(), } ) @@ -131,7 +131,7 @@ impl NapiFn { i.mutability = None; } let arg = path.pat.to_token_stream().to_string().to_case(Case::Camel); - let (ts_type, is_optional) = ty_to_ts_type(&path.ty, false); + let (ts_type, is_optional) = ty_to_ts_type(&path.ty, false, false); Some(FnArg { arg, @@ -181,7 +181,7 @@ impl NapiFn { .unwrap_or_else(|| "".to_owned()), _ => { let ret = if let Some(ret) = &self.ret { - let (ts_type, _) = ty_to_ts_type(ret, true); + let (ts_type, _) = ty_to_ts_type(ret, true, false); if ts_type == "undefined" { "void".to_owned() } else if ts_type == "Self" { diff --git a/crates/backend/src/typegen/struct.rs b/crates/backend/src/typegen/struct.rs index d09047bc..36968760 100644 --- a/crates/backend/src/typegen/struct.rs +++ b/crates/backend/src/typegen/struct.rs @@ -36,8 +36,10 @@ impl ToTypeDef for NapiImpl { fn to_type_def(&self) -> Option { if let Some(output_type) = &self.task_output_type { TASK_STRUCTS.with(|t| { - t.borrow_mut() - .insert(self.js_name.clone(), ty_to_ts_type(output_type, false).0); + t.borrow_mut().insert( + self.js_name.clone(), + ty_to_ts_type(output_type, false, true).0, + ); }); } @@ -90,7 +92,7 @@ impl NapiStruct { field_str.push_str("readonly ") } - let (arg, is_optional) = ty_to_ts_type(&f.ty, false); + let (arg, is_optional) = ty_to_ts_type(&f.ty, false, true); let arg = f.ts_type.as_ref().map(|ty| ty.to_string()).unwrap_or(arg); let sep = if is_optional { "?" } else { "" }; diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 3a8c38e5..beea5282 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -46,7 +46,7 @@ Generated by [AVA](https://avajs.dev). export function chronoDateAdd1Minute(input: Date): Date␊ export interface Dates {␊ start: Date␊ - end?: Date | undefined | null␊ + end?: Date␊ }␊ export function eitherStringOrNumber(input: string | number): number␊ export function returnEither(input: number): string | number␊ @@ -106,7 +106,7 @@ Generated by [AVA](https://avajs.dev). export function xxh64Alias(input: Buffer): bigint␊ export function getMapping(): Record␊ export function sumMapping(nums: Record): number␊ - export function mapOption(val?: number | undefined | null): number | undefined | null␊ + export function mapOption(val?: number | undefined | null): number?␊ export function returnNull(): null␊ export function returnUndefined(): void␊ export function add(a: number, b: number): number␊ @@ -117,8 +117,8 @@ Generated by [AVA](https://avajs.dev). export function getUndefined(): void␊ export function getNull(): JsNull␊ export interface AllOptionalObject {␊ - name?: string | undefined | null␊ - age?: number | undefined | null␊ + name?: string␊ + age?: number␊ }␊ export function receiveAllOptionalObject(obj?: AllOptionalObject | undefined | null): void␊ export const enum ALIAS {␊ @@ -147,8 +147,8 @@ Generated by [AVA](https://avajs.dev). name: string␊ /** The version of the package */␊ version: string␊ - dependencies?: Record | undefined | null␊ - devDependencies?: Record | undefined | null␊ + dependencies?: Record␊ + devDependencies?: Record␊ }␊ export function readPackageJson(): PackageJson␊ export function getPackageJsonName(packageJson: PackageJson): string␊ @@ -221,7 +221,7 @@ Generated by [AVA](https://avajs.dev). export type Blake2bKey = Blake2BKey␊ export class Blake2BKey { }␊ export class Context {␊ - maybeNeed?: boolean | undefined | null␊ + maybeNeed?: boolean␊ constructor()␊ static withData(data: string): Context␊ method(): string␊ @@ -241,7 +241,7 @@ Generated by [AVA](https://avajs.dev). export type JsAssets = Assets␊ export class Assets {␊ constructor()␊ - get(id: number): JsAsset | undefined | null␊ + get(id: number): JsAsset?␊ }␊ export type JsAsset = Asset␊ export class Asset {␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 68e1450c..0274c678 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/index.d.ts b/examples/napi/index.d.ts index c03e1ff1..ceab0ab7 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -36,7 +36,7 @@ export function chronoDateToMillis(input: Date): number export function chronoDateAdd1Minute(input: Date): Date export interface Dates { start: Date - end?: Date | undefined | null + end?: Date } export function eitherStringOrNumber(input: string | number): number export function returnEither(input: number): string | number @@ -96,7 +96,7 @@ export function tsRename(a: { foo: number }): string[] export function xxh64Alias(input: Buffer): bigint export function getMapping(): Record export function sumMapping(nums: Record): number -export function mapOption(val?: number | undefined | null): number | undefined | null +export function mapOption(val?: number | undefined | null): number? export function returnNull(): null export function returnUndefined(): void export function add(a: number, b: number): number @@ -107,8 +107,8 @@ export function getGlobal(): typeof global export function getUndefined(): void export function getNull(): JsNull export interface AllOptionalObject { - name?: string | undefined | null - age?: number | undefined | null + name?: string + age?: number } export function receiveAllOptionalObject(obj?: AllOptionalObject | undefined | null): void export const enum ALIAS { @@ -137,8 +137,8 @@ export interface PackageJson { name: string /** The version of the package */ version: string - dependencies?: Record | undefined | null - devDependencies?: Record | undefined | null + dependencies?: Record + devDependencies?: Record } export function readPackageJson(): PackageJson export function getPackageJsonName(packageJson: PackageJson): string @@ -211,7 +211,7 @@ export class Blake2BHasher { export type Blake2bKey = Blake2BKey export class Blake2BKey { } export class Context { - maybeNeed?: boolean | undefined | null + maybeNeed?: boolean constructor() static withData(data: string): Context method(): string @@ -231,7 +231,7 @@ export class NinjaTurtle { export type JsAssets = Assets export class Assets { constructor() - get(id: number): JsAsset | undefined | null + get(id: number): JsAsset? } export type JsAsset = Asset export class Asset {