fix(napi-derive-backend): Option value should produce optional types
This commit is contained in:
parent
0e0bfb1c0a
commit
b4a8cadb21
5 changed files with 61 additions and 34 deletions
|
@ -69,7 +69,6 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||||
("Buffer", "Buffer"),
|
("Buffer", "Buffer"),
|
||||||
// TODO: Vec<u8> should be Buffer, now is Array<number>
|
// TODO: Vec<u8> should be Buffer, now is Array<number>
|
||||||
("Vec", "Array<{}>"),
|
("Vec", "Array<{}>"),
|
||||||
("Option", "{} | null"),
|
|
||||||
("Result", "Error | {}"),
|
("Result", "Error | {}"),
|
||||||
("Either", "{} | {}"),
|
("Either", "{} | {}"),
|
||||||
("Either3", "{} | {} | {}"),
|
("Either3", "{} | {} | {}"),
|
||||||
|
@ -106,21 +105,24 @@ fn fill_ty(template: &str, args: Vec<String>) -> String {
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> String {
|
pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> (String, bool) {
|
||||||
match ty {
|
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),
|
||||||
Type::Tuple(tuple) => {
|
Type::Tuple(tuple) => {
|
||||||
if tuple.elems.is_empty() {
|
if tuple.elems.is_empty() {
|
||||||
"undefined".to_owned()
|
("undefined".to_owned(), false)
|
||||||
} else {
|
} else {
|
||||||
|
(
|
||||||
format!(
|
format!(
|
||||||
"[{}]",
|
"[{}]",
|
||||||
tuple
|
tuple
|
||||||
.elems
|
.elems
|
||||||
.iter()
|
.iter()
|
||||||
.map(|elem| ty_to_ts_type(elem, false))
|
.map(|elem| ty_to_ts_type(elem, false).0)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
|
),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,36 +146,46 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> String {
|
||||||
|
|
||||||
if rust_ty == "Result" && is_return_ty {
|
if rust_ty == "Result" && is_return_ty {
|
||||||
ts_ty = Some(args.first().unwrap().to_owned());
|
ts_ty = Some(args.first().unwrap().to_owned());
|
||||||
|
} else if rust_ty == "Option" {
|
||||||
|
ts_ty = args
|
||||||
|
.first()
|
||||||
|
.map(|(arg, _)| (format!("{} | undefined | null", arg), true));
|
||||||
} else if rust_ty == "AsyncTask" {
|
} else if rust_ty == "AsyncTask" {
|
||||||
ts_ty = r#struct::TASK_STRUCTS.with(|t| {
|
ts_ty = r#struct::TASK_STRUCTS.with(|t| {
|
||||||
let output_type = args.first().unwrap().to_owned();
|
let (output_type, _) = args.first().unwrap().to_owned();
|
||||||
if let Some(o) = t.borrow().get(&output_type) {
|
if let Some(o) = t.borrow().get(&output_type) {
|
||||||
Some(format!("Promise<{}>", o))
|
Some((format!("Promise<{}>", o), false))
|
||||||
} else {
|
} else {
|
||||||
Some("Promise<unknown>".to_owned())
|
Some(("Promise<unknown>".to_owned(), false))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if let Some(&known_ty) = KNOWN_TYPES.get(rust_ty.as_str()) {
|
} else if let Some(&known_ty) = KNOWN_TYPES.get(rust_ty.as_str()) {
|
||||||
if known_ty.contains("{}") {
|
if known_ty.contains("{}") {
|
||||||
ts_ty = Some(fill_ty(known_ty, args));
|
ts_ty = Some((
|
||||||
|
fill_ty(known_ty, args.into_iter().map(|(arg, _)| arg).collect()),
|
||||||
|
false,
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
ts_ty = Some(known_ty.to_owned());
|
ts_ty = Some((known_ty.to_owned(), false));
|
||||||
}
|
}
|
||||||
} else if let Some(t) = crate::typegen::r#struct::CLASS_STRUCTS
|
} else if let Some(t) = crate::typegen::r#struct::CLASS_STRUCTS
|
||||||
.with(|c| c.borrow_mut().get(rust_ty.as_str()).cloned())
|
.with(|c| c.borrow_mut().get(rust_ty.as_str()).cloned())
|
||||||
{
|
{
|
||||||
ts_ty = Some(t);
|
ts_ty = Some((t, false));
|
||||||
} else if rust_ty == "Promise" {
|
} else if rust_ty == "Promise" {
|
||||||
ts_ty = Some(format!("Promise<{}>", args.first().unwrap()));
|
ts_ty = Some((
|
||||||
|
format!("Promise<{}>", args.first().map(|(arg, _)| arg).unwrap()),
|
||||||
|
false,
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
// there should be runtime registered type in else
|
// there should be runtime registered type in else
|
||||||
ts_ty = Some(rust_ty);
|
ts_ty = Some((rust_ty, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ts_ty.unwrap_or_else(|| "any".to_owned())
|
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),
|
||||||
_ => "any".to_owned(),
|
_ => ("any".to_owned(), false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,18 @@ fn gen_callback_type(callback: &CallbackArg) -> String {
|
||||||
.args
|
.args
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, arg)| { format!("arg{}: {}", i, ty_to_ts_type(arg, false)) })
|
.map(|(i, arg)| {
|
||||||
|
let (arg, is_optional) = ty_to_ts_type(arg, false);
|
||||||
|
if is_optional {
|
||||||
|
format!("arg{}?: {}", i, arg)
|
||||||
|
} else {
|
||||||
|
format!("arg{}: {}", i, arg)
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", "),
|
.join(", "),
|
||||||
ret = match &callback.ret {
|
ret = match &callback.ret {
|
||||||
Some(ty) => ty_to_ts_type(ty, true),
|
Some(ty) => ty_to_ts_type(ty, true).0,
|
||||||
None => "void".to_owned(),
|
None => "void".to_owned(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -56,8 +63,9 @@ impl NapiFn {
|
||||||
i.mutability = None;
|
i.mutability = None;
|
||||||
}
|
}
|
||||||
let mut arg = path.pat.to_token_stream().to_string().to_case(Case::Camel);
|
let mut arg = path.pat.to_token_stream().to_string().to_case(Case::Camel);
|
||||||
arg.push_str(": ");
|
let (ts_arg, is_optional) = ty_to_ts_type(&path.ty, false);
|
||||||
arg.push_str(&ty_to_ts_type(&path.ty, false));
|
arg.push_str(if is_optional { "?: " } else { ": " });
|
||||||
|
arg.push_str(&ts_arg);
|
||||||
|
|
||||||
Some(arg)
|
Some(arg)
|
||||||
}
|
}
|
||||||
|
@ -100,7 +108,7 @@ impl NapiFn {
|
||||||
.unwrap_or_else(|| "".to_owned()),
|
.unwrap_or_else(|| "".to_owned()),
|
||||||
_ => {
|
_ => {
|
||||||
let ret = if let Some(ret) = &self.ret {
|
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);
|
||||||
if ts_type == "undefined" {
|
if ts_type == "undefined" {
|
||||||
"void".to_owned()
|
"void".to_owned()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -32,7 +32,7 @@ impl ToTypeDef for NapiImpl {
|
||||||
if let Some(output_type) = &self.task_output_type {
|
if let Some(output_type) = &self.task_output_type {
|
||||||
TASK_STRUCTS.with(|t| {
|
TASK_STRUCTS.with(|t| {
|
||||||
t.borrow_mut()
|
t.borrow_mut()
|
||||||
.insert(self.js_name.clone(), ty_to_ts_type(output_type, false));
|
.insert(self.js_name.clone(), ty_to_ts_type(output_type, false).0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
TypeDef {
|
TypeDef {
|
||||||
|
@ -61,7 +61,9 @@ impl NapiStruct {
|
||||||
if !f.setter {
|
if !f.setter {
|
||||||
field_str.push_str("readonly ")
|
field_str.push_str("readonly ")
|
||||||
}
|
}
|
||||||
let arg = format!("{}: {}", &f.js_name, ty_to_ts_type(&f.ty, false));
|
let (arg, is_optional) = ty_to_ts_type(&f.ty, false);
|
||||||
|
let sep = if is_optional { "?" } else { "" };
|
||||||
|
let arg = format!("{}{}: {}", &f.js_name, sep, arg);
|
||||||
if self.kind == NapiStructKind::Constructor {
|
if self.kind == NapiStructKind::Constructor {
|
||||||
ctor_args.push(arg.clone());
|
ctor_args.push(arg.clone());
|
||||||
}
|
}
|
||||||
|
|
10
examples/napi/index.d.ts
vendored
10
examples/napi/index.d.ts
vendored
|
@ -7,7 +7,7 @@ export function bigintAdd(a: BigInt, b: BigInt): BigInt
|
||||||
export function createBigInt(): BigInt
|
export function createBigInt(): BigInt
|
||||||
export function createBigIntI64(): BigInt
|
export function createBigIntI64(): BigInt
|
||||||
export function getCwd(callback: (arg0: string) => void): void
|
export function getCwd(callback: (arg0: string) => void): void
|
||||||
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void
|
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void
|
||||||
export function eitherStringOrNumber(input: string | number): number
|
export function eitherStringOrNumber(input: string | number): number
|
||||||
export function returnEither(input: number): string | number
|
export function returnEither(input: number): string | number
|
||||||
export function either3(input: string | number | boolean): number
|
export function either3(input: string | number | boolean): number
|
||||||
|
@ -19,7 +19,7 @@ export enum Kind { Dog = 0, Cat = 1, Duck = 2 }
|
||||||
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }
|
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }
|
||||||
export function enumToI32(e: CustomNumEnum): number
|
export function enumToI32(e: CustomNumEnum): number
|
||||||
export function throwError(): void
|
export function throwError(): void
|
||||||
export function mapOption(val: number | null): number | null
|
export function mapOption(val?: number | undefined | null): number | undefined | null
|
||||||
export function add(a: number, b: number): number
|
export function add(a: number, b: number): number
|
||||||
export function fibonacci(n: number): number
|
export function fibonacci(n: number): number
|
||||||
export function listObjKeys(obj: object): Array<string>
|
export function listObjKeys(obj: object): Array<string>
|
||||||
|
@ -31,8 +31,8 @@ export function asyncPlus100(p: Promise<number>): Promise<number>
|
||||||
interface PackageJson {
|
interface PackageJson {
|
||||||
name: string
|
name: string
|
||||||
version: string
|
version: string
|
||||||
dependencies: Record<string, any> | null
|
dependencies?: Record<string, any> | undefined | null
|
||||||
devDependencies: Record<string, any> | null
|
devDependencies?: Record<string, any> | undefined | null
|
||||||
}
|
}
|
||||||
export function readPackageJson(): PackageJson
|
export function readPackageJson(): PackageJson
|
||||||
export function getPackageJsonName(packageJson: PackageJson): string
|
export function getPackageJsonName(packageJson: PackageJson): string
|
||||||
|
@ -64,7 +64,7 @@ export class Blake2BKey {
|
||||||
|
|
||||||
}
|
}
|
||||||
export class Context {
|
export class Context {
|
||||||
|
maybeNeed?: boolean | undefined | null
|
||||||
constructor()
|
constructor()
|
||||||
static withData(data: string): Context
|
static withData(data: string): Context
|
||||||
method(): string
|
method(): string
|
||||||
|
|
|
@ -79,6 +79,7 @@ impl Blake2bKey {
|
||||||
#[napi]
|
#[napi]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
data: String,
|
data: String,
|
||||||
|
pub maybe_need: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test for return `napi::Result` and `Result`
|
// Test for return `napi::Result` and `Result`
|
||||||
|
@ -88,12 +89,16 @@ impl Context {
|
||||||
pub fn new() -> napi::Result<Self> {
|
pub fn new() -> napi::Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
data: "not empty".into(),
|
data: "not empty".into(),
|
||||||
|
maybe_need: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(factory)]
|
#[napi(factory)]
|
||||||
pub fn with_data(data: String) -> Result<Self> {
|
pub fn with_data(data: String) -> Result<Self> {
|
||||||
Ok(Self { data })
|
Ok(Self {
|
||||||
|
data,
|
||||||
|
maybe_need: Some(true),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
|
|
Loading…
Reference in a new issue