1 | mod r#const; |
2 | mod r#enum; |
3 | mod r#fn; |
4 | pub(crate) mod r#struct; |
5 | |
6 | use std::{cell::RefCell, collections::HashMap, env}; |
7 | |
8 | use once_cell::sync::Lazy; |
9 | use syn::{PathSegment, Type, TypePath, TypeSlice}; |
10 | |
11 | pub 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 | |
16 | pub 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)] |
20 | pub 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 | |
29 | thread_local! { |
30 | static ALIAS: RefCell<HashMap<String, String>> = Default::default(); |
31 | } |
32 | |
33 | fn add_alias(name: String, alias: String) { |
34 | ALIAS.with(|aliases: &RefCell>| { |
35 | aliases.borrow_mut().insert(k:name, v:alias); |
36 | }); |
37 | } |
38 | |
39 | pub 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 | |
58 | fn 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 | |
101 | impl 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 | |
135 | pub 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`) |
140 | static 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 | |
225 | fn 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 | |
243 | fn 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 | |
250 | const TSFN_RUST_TY: &str = "ThreadsafeFunction" ; |
251 | const FUNCTION_TY: &str = "Function" ; |
252 | const FUNCTION_REF_TY: &str = "FunctionRef" ; |
253 | |
254 | fn 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 | |
258 | fn 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) |
276 | pub 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 | |