1mod r#const;
2mod r#enum;
3mod r#fn;
4pub(crate) mod r#struct;
5
6use std::{
7 cell::RefCell,
8 collections::HashMap,
9 env,
10 fmt::{self, Display, Formatter},
11};
12
13use once_cell::sync::Lazy;
14use syn::{PathSegment, Type, TypePath, TypeSlice};
15
16pub static NAPI_RS_CLI_VERSION: Lazy<semver::Version> = Lazy::new(|| {
17 let version: String = env::var("CARGO_CFG_NAPI_RS_CLI_VERSION").unwrap_or_else(|_| "0.0.0".to_string());
18 semver::Version::parse(&version).unwrap_or_else(|_| semver::Version::new(major:0, minor:0, patch:0))
19});
20
21pub static NAPI_RS_CLI_VERSION_WITH_SHARED_CRATES_FIX: Lazy<semver::Version> =
22 Lazy::new(|| semver::Version::new(major:2, minor:15, patch:1));
23
24#[derive(Default, Debug)]
25pub struct TypeDef {
26 pub kind: String,
27 pub name: String,
28 pub original_name: Option<String>,
29 pub def: String,
30 pub js_mod: Option<String>,
31 pub js_doc: String,
32}
33
34thread_local! {
35 static ALIAS: RefCell<HashMap<String, String>> = Default::default();
36}
37
38fn add_alias(name: String, alias: String) {
39 ALIAS.with(|aliases: &RefCell>| {
40 aliases.borrow_mut().insert(k:name, v:alias);
41 });
42}
43
44pub fn js_doc_from_comments(comments: &[String]) -> String {
45 if comments.is_empty() {
46 return "".to_owned();
47 }
48
49 if comments.len() == 1 {
50 return format!("/**{} */\n", comments[0]);
51 }
52
53 format!(
54 "/**\n{} */\n",
55 comments
56 .iter()
57 .map(|c| format!(" *{}\n", c))
58 .collect::<Vec<String>>()
59 .join("")
60 )
61}
62
63fn escape_json(src: &str) -> String {
64 use std::fmt::Write;
65 let mut escaped = String::with_capacity(src.len());
66 let mut utf16_buf = [0u16; 2];
67 let mut pending_backslash = false;
68 for c in src.chars() {
69 if pending_backslash {
70 match c {
71 'b' | 'f' | 'n' | 'r' | 't' | 'u' | '"' => escaped += "\\",
72 _ => escaped += "\\\\",
73 }
74 pending_backslash = false;
75 }
76
77 match c {
78 '\x08' => escaped += "\\b",
79 '\x0c' => escaped += "\\f",
80 '\n' => escaped += "\\n",
81 '\r' => escaped += "\\r",
82 '\t' => escaped += "\\t",
83 '"' => escaped += "\\\"",
84 '\\' => {
85 pending_backslash = true;
86 }
87 ' ' => escaped += " ",
88 c if c.is_ascii_graphic() => escaped.push(c),
89 c => {
90 let encoded = c.encode_utf16(&mut utf16_buf);
91 for utf16 in encoded {
92 write!(escaped, "\\u{:04X}", utf16).unwrap();
93 }
94 }
95 }
96 }
97
98 // cater for trailing backslash
99 if pending_backslash {
100 escaped += "\\\\"
101 }
102
103 escaped
104}
105
106impl Display for TypeDef {
107 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
108 let pkg_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME is not set");
109 let js_mod = if let Some(js_mod) = &self.js_mod {
110 format!(", \"js_mod\": \"{}\"", js_mod)
111 } else {
112 "".to_string()
113 };
114 let original_name = if let Some(original_name) = &self.original_name {
115 format!(", \"original_name\": \"{}\"", original_name)
116 } else {
117 "".to_string()
118 };
119 // TODO: remove this in v3
120 // This is a workaround for lower version of @napi-rs/cli
121 // See https://github.com/napi-rs/napi-rs/pull/1531
122 let prefix = if *NAPI_RS_CLI_VERSION >= *NAPI_RS_CLI_VERSION_WITH_SHARED_CRATES_FIX {
123 format!("{}:", pkg_name)
124 } else {
125 "".to_string()
126 };
127 write!(
128 f,
129 r#"{}{{"kind": "{}", "name": "{}", "js_doc": "{}", "def": "{}"{}{}}}"#,
130 prefix,
131 self.kind,
132 self.name,
133 escape_json(&self.js_doc),
134 escape_json(&self.def),
135 original_name,
136 js_mod,
137 )
138 }
139}
140
141pub trait ToTypeDef {
142 fn to_type_def(&self) -> Option<TypeDef>;
143}
144
145/// Mapping from `rust_type` to (`ts_type`, `is_ts_function_type_notation`, `is_ts_union_type`)
146static KNOWN_TYPES: Lazy<HashMap<&'static str, (&'static str, bool, bool)>> = Lazy::new(|| {
147 let mut map: HashMap<&str, (&str, bool, …)> = HashMap::default();
148 map.extend(crate::PRIMITIVE_TYPES.iter().cloned());
149 map.extend([
150 ("JsObject", ("object", false, false)),
151 ("Object", ("object", false, false)),
152 ("Array", ("unknown[]", false, false)),
153 ("Value", ("any", false, false)),
154 ("Map", ("Record<string, any>", false, false)),
155 ("HashMap", ("Record<{}, {}>", false, false)),
156 ("BTreeMap", ("Record<{}, {}>", false, false)),
157 ("IndexMap", ("Record<{}, {}>", false, false)),
158 ("ArrayBuffer", ("ArrayBuffer", false, false)),
159 ("JsArrayBuffer", ("ArrayBuffer", false, false)),
160 ("Int8Array", ("Int8Array", false, false)),
161 ("Uint8Array", ("Uint8Array", false, false)),
162 ("Uint8ClampedArray", ("Uint8ClampedArray", false, false)),
163 ("Uint8ClampedSlice", ("Uint8ClampedArray", false, false)),
164 ("Int16Array", ("Int16Array", false, false)),
165 ("Uint16Array", ("Uint16Array", false, false)),
166 ("Int32Array", ("Int32Array", false, false)),
167 ("Uint32Array", ("Uint32Array", false, false)),
168 ("Float32Array", ("Float32Array", false, false)),
169 ("Float64Array", ("Float64Array", false, false)),
170 ("BigInt64Array", ("BigInt64Array", false, false)),
171 ("BigUint64Array", ("BigUint64Array", false, false)),
172 ("DataView", ("DataView", false, false)),
173 ("DateTime", ("Date", false, false)),
174 ("NaiveDateTime", ("Date", false ,false)),
175 ("Date", ("Date", false, false)),
176 ("JsDate", ("Date", false, false)),
177 ("JsBuffer", ("Buffer", false, false)),
178 ("BufferSlice", ("Buffer", false, false)),
179 ("Buffer", ("Buffer", false, false)),
180 ("Vec", ("Array<{}>", false, false)),
181 ("Result", ("Error | {}", false, true)),
182 ("Error", ("Error", false, false)),
183 ("JsError", ("Error", false, false)),
184 ("JsTypeError", ("TypeError", false, false)),
185 ("JsRangeError", ("RangeError", false, false)),
186 ("ClassInstance", ("{}", false, false)),
187 ("Function", ("({}) => {}", true, false)),
188 ("FunctionRef", ("({}) => {}", true, false)),
189 ("Either", ("{} | {}", false, true)),
190 ("Either3", ("{} | {} | {}", false, true)),
191 ("Either4", ("{} | {} | {} | {}", false, true)),
192 ("Either5", ("{} | {} | {} | {} | {}", false, true)),
193 ("Either6", ("{} | {} | {} | {} | {} | {}", false, true)),
194 ("Either7", ("{} | {} | {} | {} | {} | {} | {}", false, true)),
195 ("Either8", ("{} | {} | {} | {} | {} | {} | {} | {}", false, true)),
196 ("Either9", ("{} | {} | {} | {} | {} | {} | {} | {} | {}",false, true)),
197 ("Either10", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
198 ("Either11", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
199 ("Either12", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
200 ("Either13", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
201 ("Either14", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
202 ("Either15", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
203 ("Either16", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
204 ("Either17", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
205 ("Either18", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
206 ("Either19", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
207 ("Either20", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
208 ("Either21", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
209 ("Either22", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
210 ("Either23", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
211 ("Either24", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
212 ("Either25", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
213 ("Either26", ("{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}", false, true)),
214 ("external", ("object", false, false)),
215 ("Promise", ("Promise<{}>", false, false)),
216 ("AbortSignal", ("AbortSignal", false, false)),
217 ("JsGlobal", ("typeof global", false, false)),
218 ("External", ("ExternalObject<{}>", false, false)),
219 ("unknown", ("unknown", false, false)),
220 ("Unknown", ("unknown", false, false)),
221 ("JsUnknown", ("unknown", false, false)),
222 ("This", ("this", false, false)),
223 ("Rc", ("{}", false, false)),
224 ("Arc", ("{}", false, false)),
225 ("Mutex", ("{}", false, false)),
226 ]);
227
228 map
229});
230
231fn fill_ty(template: &str, args: Vec<String>) -> String {
232 let matches: Vec<(usize, &str)> = template.match_indices("{}").collect::<Vec<_>>();
233 if args.len() != matches.len() {
234 return String::from("any");
235 }
236
237 let mut ret: String = String::from("");
238 let mut prev: usize = 0;
239 matches.into_iter().zip(args).for_each(|((index: usize, _), arg: String)| {
240 ret.push_str(&template[prev..index]);
241 ret.push_str(&arg);
242 prev = index + 2;
243 });
244
245 ret.push_str(&template[prev..]);
246 ret
247}
248
249fn is_ts_union_type(rust_ty: &str) -> bool {
250 KNOWN_TYPES
251 .get(rust_ty)
252 .map(|&(_, _, is_union_type)| is_union_type)
253 .unwrap_or(default:false)
254}
255
256const TSFN_RUST_TY: &str = "ThreadsafeFunction";
257const FUNCTION_TY: &str = "Function";
258const FUNCTION_REF_TY: &str = "FunctionRef";
259
260fn is_generic_function_type(rust_ty: &str) -> bool {
261 rust_ty == TSFN_RUST_TY || rust_ty == FUNCTION_TY || rust_ty == FUNCTION_REF_TY
262}
263
264fn is_ts_function_type_notation(ty: &Type) -> bool {
265 match ty {
266 Type::Path(syn::TypePath { qself: None, path: &Path }) => {
267 if let Some(syn::PathSegment { ident: &Ident, .. }) = path.segments.last() {
268 let rust_ty: String = ident.to_string();
269 return KNOWN_TYPES
270 .get(&*rust_ty)
271 .map(|&(_, is_fn, _)| is_fn)
272 .unwrap_or(default:false);
273 }
274
275 false
276 }
277 _ => false,
278 }
279}
280
281// return (type, is_optional)
282pub fn ty_to_ts_type(
283 ty: &Type,
284 is_return_ty: bool,
285 is_struct_field: bool,
286 convert_tuple_to_variadic: bool,
287) -> (String, bool) {
288 match ty {
289 Type::Reference(r) => ty_to_ts_type(&r.elem, is_return_ty, is_struct_field, false),
290 Type::Tuple(tuple) => {
291 if tuple.elems.is_empty() {
292 if convert_tuple_to_variadic {
293 if is_return_ty {
294 ("void".to_owned(), false)
295 } else {
296 ("".to_owned(), false)
297 }
298 } else {
299 ("undefined".to_owned(), false)
300 }
301 } else if convert_tuple_to_variadic {
302 let variadic = &tuple
303 .elems
304 .iter()
305 .enumerate()
306 .map(|(i, arg)| {
307 let (ts_type, is_optional) = ty_to_ts_type(arg, false, false, false);
308 r#fn::FnArg {
309 arg: format!("arg{}", i),
310 ts_type,
311 is_optional,
312 }
313 })
314 .collect::<r#fn::FnArgList>();
315 (format!("{}", variadic), false)
316 } else {
317 (
318 format!(
319 "[{}]",
320 tuple
321 .elems
322 .iter()
323 .map(|elem| ty_to_ts_type(elem, false, false, false).0)
324 .collect::<Vec<_>>()
325 .join(", ")
326 ),
327 false,
328 )
329 }
330 }
331 Type::Path(syn::TypePath { qself: None, path }) => {
332 let mut ts_ty = None;
333
334 if let Some(syn::PathSegment { ident, arguments }) = path.segments.last() {
335 let rust_ty = ident.to_string();
336 let is_ts_union_type = is_ts_union_type(&rust_ty);
337 let args = if let syn::PathArguments::AngleBracketed(arguments) = arguments {
338 arguments
339 .args
340 .iter()
341 .enumerate()
342 .filter_map(|(index, arg)| match arg {
343 syn::GenericArgument::Type(generic_ty) => Some(ty_to_ts_type(
344 generic_ty,
345 index == 1 && is_generic_function_type(&rust_ty),
346 false,
347 is_generic_function_type(&rust_ty),
348 ))
349 .map(|(mut ty, is_optional)| {
350 if is_ts_union_type && is_ts_function_type_notation(generic_ty) {
351 ty = format!("({})", ty);
352 }
353 (ty, is_optional)
354 }),
355 _ => None,
356 })
357 .collect::<Vec<_>>()
358 } else {
359 vec![]
360 };
361
362 if rust_ty == "Result" && is_return_ty {
363 ts_ty = Some(args.first().unwrap().to_owned());
364 } else if rust_ty == "Option" {
365 ts_ty = args.first().map(|(arg, _)| {
366 (
367 if is_struct_field {
368 arg.to_string()
369 } else if is_return_ty {
370 format!("{} | null", arg)
371 } else {
372 format!("{} | undefined | null", arg)
373 },
374 true,
375 )
376 });
377 } else if rust_ty == "AsyncTask" {
378 ts_ty = r#struct::TASK_STRUCTS.with(|t| {
379 let (output_type, _) = args.first().unwrap().to_owned();
380 if let Some(o) = t.borrow().get(&output_type) {
381 Some((format!("Promise<{}>", o), false))
382 } else {
383 Some(("Promise<unknown>".to_owned(), false))
384 }
385 });
386 } else if rust_ty == "Reference" || rust_ty == "WeakReference" {
387 ts_ty = r#struct::TASK_STRUCTS.with(|t| {
388 // Reference<T> => T
389 if let Some(arg) = args.first() {
390 let (output_type, _) = arg.to_owned();
391 if let Some(o) = t.borrow().get(&output_type) {
392 Some((o.to_owned(), false))
393 } else {
394 Some((output_type, false))
395 }
396 } else {
397 // Not NAPI-RS `Reference`
398 Some((rust_ty, false))
399 }
400 });
401 } else if let Some(&(known_ty, _, _)) = KNOWN_TYPES.get(rust_ty.as_str()) {
402 if rust_ty == "()" && is_return_ty {
403 ts_ty = Some(("void".to_owned(), false));
404 } else if known_ty.contains("{}") {
405 ts_ty = Some((
406 fill_ty(known_ty, args.into_iter().map(|(arg, _)| arg).collect()),
407 false,
408 ));
409 } else {
410 ts_ty = Some((known_ty.to_owned(), false));
411 }
412 } else if let Some(t) = crate::typegen::r#struct::CLASS_STRUCTS
413 .with(|c| c.borrow_mut().get(rust_ty.as_str()).cloned())
414 {
415 ts_ty = Some((t, false));
416 } else if rust_ty == TSFN_RUST_TY {
417 let fatal_tsfn = match args.get(1) {
418 Some((arg, _)) => arg == "Fatal",
419 _ => false,
420 };
421 let args = args.first().map(|(arg, _)| arg).unwrap();
422 ts_ty = if fatal_tsfn {
423 Some((format!("({}) => any", args), false))
424 } else {
425 Some((format!("(err: Error | null, {}) => any", args), false))
426 };
427 } else {
428 // there should be runtime registered type in else
429 let type_alias = ALIAS.with(|aliases| {
430 aliases
431 .borrow()
432 .get(rust_ty.as_str())
433 .map(|a| (a.to_owned(), false))
434 });
435 ts_ty = type_alias.or(Some((rust_ty, false)));
436 }
437 }
438
439 let (ty, is_optional) = ts_ty.unwrap_or_else(|| ("any".to_owned(), false));
440 (
441 (convert_tuple_to_variadic && !is_return_ty)
442 .then(|| format!("arg: {ty}"))
443 .unwrap_or(ty),
444 is_optional,
445 )
446 }
447 Type::Group(g) => ty_to_ts_type(&g.elem, is_return_ty, is_struct_field, false),
448 Type::Array(a) => {
449 let (element_type, is_optional) =
450 ty_to_ts_type(&a.elem, is_return_ty, is_struct_field, false);
451 (format!("{}[]", element_type), is_optional)
452 }
453 Type::Paren(p) => {
454 let (element_type, is_optional) =
455 ty_to_ts_type(&p.elem, is_return_ty, is_struct_field, false);
456 (element_type, is_optional)
457 }
458 Type::Slice(TypeSlice { elem, .. }) => {
459 if let Type::Path(TypePath { path, .. }) = &**elem {
460 if let Some(PathSegment { ident, .. }) = path.segments.last() {
461 if let Some(js_type) = crate::TYPEDARRAY_SLICE_TYPES.get(&ident.to_string().as_str()) {
462 return (js_type.to_string(), false);
463 }
464 }
465 }
466 ("any[]".to_owned(), false)
467 }
468 _ => ("any".to_owned(), false),
469 }
470}
471