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