1use alef_codegen::generators::trait_bridge::{BridgeOutput, TraitBridgeGenerator, TraitBridgeSpec, gen_bridge_all};
7use alef_core::config::TraitBridgeConfig;
8use alef_core::ir::{ApiSurface, MethodDef, TypeDef, TypeRef};
9use std::collections::HashMap;
10use std::fmt::Write;
11
12pub struct NapiBridgeGenerator {
15 pub core_import: String,
17 pub type_paths: HashMap<String, String>,
19 pub error_type: String,
21}
22
23impl TraitBridgeGenerator for NapiBridgeGenerator {
24 fn foreign_object_type(&self) -> &str {
25 "napi::bindgen_prelude::Object<'static>"
26 }
27
28 fn bridge_imports(&self) -> Vec<String> {
29 vec![
30 "napi::bindgen_prelude::{JsObjectValue, ToNapiValue, Unknown, Object}".to_string(),
31 "napi::JsValue".to_string(),
32 "std::sync::Arc".to_string(),
33 ]
34 }
35
36 fn gen_sync_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
37 let name = &method.name;
38 let has_error = method.error_type.is_some();
39 let mut out = String::with_capacity(512);
40
41 let js_args_exprs = build_napi_args(method);
43 let args_tuple_ty = unknown_tuple_type(js_args_exprs.len());
44
45 writeln!(
46 out,
47 "let func: napi::bindgen_prelude::Function<{args_tuple_ty}, napi::bindgen_prelude::Unknown> = match self.inner.get_named_property(\"{name}\") {{"
48 )
49 .ok();
50 writeln!(out, " Ok(f) => f,").ok();
51 if has_error {
52 let err = spec.make_error("format!(\"Method '{}' not found on bridge object: {}\", self.cached_name, e)");
53 writeln!(out, " Err(e) => return Err({err}),").ok();
54 } else {
55 writeln!(out, " Err(_) => return Default::default(),").ok();
56 }
57 writeln!(out, "}};").ok();
58
59 if js_args_exprs.is_empty() {
61 writeln!(out, "let result = func.call(());").ok();
62 } else {
63 let tuple_str = if js_args_exprs.len() == 1 {
65 format!("({},)", js_args_exprs[0])
66 } else {
67 format!("({})", js_args_exprs.join(", "))
68 };
69 writeln!(out, "let result = func.call({tuple_str});").ok();
70 }
71
72 if has_error {
74 writeln!(out, "match result {{").ok();
75 let err = spec.make_error(&format!(
76 "format!(\"Plugin '{{}}' method '{}' failed: {{}}\", self.cached_name, e)",
77 name
78 ));
79 writeln!(out, " Err(e) => Err({err}),").ok();
80 writeln!(out, " Ok(val) => {{").ok();
81 if matches!(method.return_type, TypeRef::Unit) {
82 writeln!(out, " Ok(())").ok();
83 } else {
84 writeln!(out, " // Convert JS value to Rust type via string coercion").ok();
86 writeln!(out, " let s = val.coerce_to_string()").ok();
87 writeln!(out, " .and_then(|s| s.into_utf8())").ok();
88 writeln!(out, " .and_then(|s| s.into_owned())").ok();
89 writeln!(out, " .map_err(|e| {{").ok();
90 let err = spec.make_error(&format!(
91 "format!(\"Failed to extract return value from method '{}': {{}}\", e)",
92 name
93 ));
94 writeln!(out, " {err}").ok();
95 writeln!(out, " }})?;").ok();
96 writeln!(out, " match s.as_str() {{").ok();
97 writeln!(out, " \"\" | \"null\" => Ok(Default::default()),").ok();
98 writeln!(out, " _ => {{").ok();
99 writeln!(out, " serde_json::from_str::<_>(&s).map_err(|_| {{").ok();
100 let err = spec.make_error(&format!(
101 "format!(\"Failed to parse return value for method '{}'\", {})",
102 name, "self.cached_name"
103 ));
104 writeln!(out, " {err}").ok();
105 writeln!(out, " }})").ok();
106 writeln!(out, " }}").ok();
107 writeln!(out, " }}").ok();
108 }
109 writeln!(out, " }}").ok();
110 writeln!(out, "}}").ok();
111 } else {
112 writeln!(out, "match result {{").ok();
114 writeln!(out, " Err(_) => Default::default(),").ok();
115 writeln!(out, " Ok(val) => {{").ok();
116 if matches!(method.return_type, TypeRef::Unit) {
117 writeln!(out, " ()").ok();
118 } else {
119 writeln!(out, " // Convert JS value to Rust type via string coercion").ok();
121 writeln!(out, " let s = val.coerce_to_string()").ok();
122 writeln!(out, " .and_then(|s| s.into_utf8())").ok();
123 writeln!(out, " .and_then(|s| s.into_owned())").ok();
124 writeln!(out, " .unwrap_or_default();").ok();
125 writeln!(out, " match s.as_str() {{").ok();
126 writeln!(out, " \"\" | \"null\" => Default::default(),").ok();
127 writeln!(
128 out,
129 " _ => serde_json::from_str::<_>(&s).unwrap_or_default()"
130 )
131 .ok();
132 writeln!(out, " }}").ok();
133 }
134 writeln!(out, " }}").ok();
135 writeln!(out, "}}").ok();
136 }
137 out
138 }
139
140 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
141 let name = &method.name;
142 let mut out = String::with_capacity(1024);
143
144 writeln!(out, "let cached_name = self.cached_name.clone();").ok();
146
147 let js_args_exprs = build_napi_args(method);
149 let args_tuple_ty = unknown_tuple_type(js_args_exprs.len());
150
151 writeln!(
152 out,
153 "let func: napi::bindgen_prelude::Function<{args_tuple_ty}, napi::bindgen_prelude::Unknown> = match self.inner.get_named_property(\"{name}\") {{"
154 )
155 .ok();
156 writeln!(out, " Ok(f) => f,").ok();
157 writeln!(out, " Err(e) => {{").ok();
158 let err = spec.make_error("format!(\"Method '{}' not found on bridge object: {}\", cached_name, e)");
159 writeln!(out, " return Err({err});").ok();
160 writeln!(out, " }}").ok();
161 writeln!(out, "}};").ok();
162
163 let tuple_str = if js_args_exprs.is_empty() {
165 "()".to_string()
166 } else {
167 if js_args_exprs.len() == 1 {
168 format!("({},)", js_args_exprs[0])
169 } else {
170 format!("({})", js_args_exprs.join(", "))
171 }
172 };
173
174 writeln!(out, "let result = func.call({tuple_str});").ok();
175 writeln!(out, "match result {{").ok();
176 let err = spec.make_error(&format!(
177 "format!(\"Plugin '{{}}' method '{}' failed: {{}}\", cached_name, e)",
178 name
179 ));
180 writeln!(out, " Err(e) => Err({err}),").ok();
181 writeln!(out, " Ok(val) => {{").ok();
182 if matches!(method.return_type, TypeRef::Unit) {
183 writeln!(out, " Ok(())").ok();
184 } else {
185 writeln!(out, " // Convert JS value to Rust type via string coercion").ok();
187 writeln!(out, " let s = val.coerce_to_string()").ok();
188 writeln!(out, " .and_then(|s| s.into_utf8())").ok();
189 writeln!(out, " .and_then(|s| s.into_owned())").ok();
190 writeln!(out, " .map_err(|e| {{").ok();
191 let err = spec.make_error(&format!(
192 "format!(\"Failed to extract return value from method '{}': {{}}\", e)",
193 name
194 ));
195 writeln!(out, " {err}").ok();
196 writeln!(out, " }})?;").ok();
197 writeln!(out, " match s.as_str() {{").ok();
199 writeln!(out, " \"\" | \"null\" => Ok(Default::default()),").ok();
200 writeln!(out, " _ => serde_json::from_str::<_>(&s).map_err(|_| {{").ok();
201 let err = spec.make_error(&format!(
202 "\"Failed to parse return value for method '{}'\".to_string()",
203 name
204 ));
205 writeln!(out, " {err}").ok();
206 writeln!(out, " }})").ok();
207 writeln!(out, " }}").ok();
208 }
209 writeln!(out, " }}").ok();
210 writeln!(out, "}}").ok();
211 out
212 }
213
214 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
215 let wrapper = spec.wrapper_name();
216 let mut out = String::with_capacity(512);
217
218 writeln!(out, "impl {wrapper} {{").ok();
219 writeln!(out, " /// Create a new bridge wrapping a NAPI Object.").ok();
220 writeln!(out, " ///").ok();
221 writeln!(out, " /// Validates that the object provides all required methods.").ok();
222 writeln!(
223 out,
224 " pub fn new(js_obj: napi::bindgen_prelude::Object<'_>) -> napi::Result<Self> {{"
225 )
226 .ok();
227
228 for req_method in spec.required_methods() {
230 writeln!(
231 out,
232 " if !js_obj.has_named_property(\"{}\").unwrap_or(false) {{",
233 req_method.name
234 )
235 .ok();
236 writeln!(out, " return Err(napi::Error::new(").ok();
237 writeln!(out, " napi::Status::GenericFailure,").ok();
238 writeln!(
239 out,
240 " format!(\"Object missing required method: {{}}\", \"{}\")",
241 req_method.name
242 )
243 .ok();
244 writeln!(out, " ));").ok();
245 writeln!(out, " }}").ok();
246 }
247
248 writeln!(
250 out,
251 " // SAFETY: The JS object is owned by the Node.js runtime and lives for"
252 )
253 .ok();
254 writeln!(
255 out,
256 " // the duration of the enclosing #[napi] call. The bridge is only used"
257 )
258 .ok();
259 writeln!(
260 out,
261 " // synchronously during that same call, so 'static is safe here."
262 )
263 .ok();
264 writeln!(
265 out,
266 " let js_obj: napi::bindgen_prelude::Object<'static> = unsafe {{"
267 )
268 .ok();
269 writeln!(out, " std::mem::transmute(js_obj)").ok();
270 writeln!(out, " }};").ok();
271
272 writeln!(
274 out,
275 " let cached_name = match js_obj.get_named_property::<String>(\"name\") {{"
276 )
277 .ok();
278 writeln!(out, " Ok(n) => n,").ok();
279 writeln!(out, " Err(_) => \"unknown\".to_string(),").ok();
280 writeln!(out, " }};").ok();
281
282 writeln!(out, " Ok(Self {{").ok();
283 writeln!(out, " inner: js_obj,").ok();
284 writeln!(out, " cached_name,").ok();
285 writeln!(out, " }})").ok();
286 writeln!(out, " }}").ok();
287 writeln!(out).ok();
288 writeln!(out, " /// Extract napi::Env from the stored Object.").ok();
289 writeln!(out, " fn env(&self) -> napi::Env {{").ok();
290 writeln!(
291 out,
292 " // SAFETY: Object<'static> is 3 pointer-sized words; first word is napi_env."
293 )
294 .ok();
295 writeln!(
296 out,
297 " let raw: [*mut std::ffi::c_void; 3] = unsafe {{ std::mem::transmute_copy(&self.inner) }};"
298 )
299 .ok();
300 writeln!(out, " napi::Env::from_raw(raw[0] as napi::sys::napi_env)").ok();
301 writeln!(out, " }}").ok();
302 writeln!(out, "}}").ok();
303 writeln!(out).ok();
304 writeln!(
305 out,
306 "// SAFETY: The bridge is created from a NAPI Object that is pinned to the"
307 )
308 .ok();
309 writeln!(
310 out,
311 "// Node.js event loop thread. All access occurs on that thread. Send+Sync"
312 )
313 .ok();
314 writeln!(
315 out,
316 "// are required by the Plugin trait but the bridge is never actually moved"
317 )
318 .ok();
319 writeln!(out, "// across threads.").ok();
320 writeln!(out, "unsafe impl Send for {wrapper} {{}}").ok();
321 writeln!(out, "unsafe impl Sync for {wrapper} {{}}").ok();
322 out
323 }
324
325 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
326 let Some(register_fn) = spec.bridge_config.register_fn.as_deref() else {
327 return String::new();
328 };
329 let Some(registry_getter) = spec.bridge_config.registry_getter.as_deref() else {
330 return String::new();
331 };
332 let wrapper = spec.wrapper_name();
333 let trait_path = spec.trait_path();
334
335 let mut out = String::with_capacity(1024);
336
337 writeln!(out, "#[napi]").ok();
338 writeln!(
339 out,
340 "pub fn {register_fn}(obj: napi::bindgen_prelude::Object) -> napi::Result<()> {{"
341 )
342 .ok();
343
344 writeln!(out, " let bridge = {wrapper}::new(obj)?;").ok();
346 writeln!(out, " let arc: Arc<dyn {trait_path}> = Arc::new(bridge);").ok();
347
348 let extra = spec
350 .bridge_config
351 .register_extra_args
352 .as_deref()
353 .map(|a| format!(", {a}"))
354 .unwrap_or_default();
355 writeln!(out, " let registry = {registry_getter}();").ok();
356 writeln!(out, " let mut registry = registry.write();").ok();
357 writeln!(out, " registry.register(arc{extra}).map_err(|e| napi::Error::new(").ok();
358 writeln!(out, " napi::Status::GenericFailure,").ok();
359 writeln!(out, " format!(\"Failed to register backend: {{}}\", e)").ok();
360 writeln!(out, " ))").ok();
361 writeln!(out, "}}").ok();
362 out
363 }
364}
365
366pub fn gen_trait_bridge(
368 trait_type: &TypeDef,
369 bridge_cfg: &TraitBridgeConfig,
370 core_import: &str,
371 error_type: &str,
372 error_constructor: &str,
373 api: &ApiSurface,
374) -> BridgeOutput {
375 let type_paths: HashMap<String, String> = api
377 .types
378 .iter()
379 .map(|t| (t.name.clone(), t.rust_path.replace('-', "_")))
380 .chain(
381 api.enums
382 .iter()
383 .map(|e| (e.name.clone(), e.rust_path.replace('-', "_"))),
384 )
385 .collect();
386
387 let is_visitor_bridge = bridge_cfg.type_alias.is_some()
389 && bridge_cfg.register_fn.is_none()
390 && bridge_cfg.super_trait.is_none()
391 && trait_type.methods.iter().all(|m| m.has_default_impl);
392
393 if is_visitor_bridge {
394 let struct_name = format!("Js{}Bridge", bridge_cfg.trait_name);
395 let trait_path = trait_type.rust_path.replace('-', "_");
396 let code = gen_visitor_bridge(
397 trait_type,
398 bridge_cfg,
399 &struct_name,
400 &trait_path,
401 core_import,
402 &type_paths,
403 );
404 BridgeOutput { imports: vec![], code }
405 } else {
406 let generator = NapiBridgeGenerator {
408 core_import: core_import.to_string(),
409 type_paths: type_paths.clone(),
410 error_type: error_type.to_string(),
411 };
412 let spec = TraitBridgeSpec {
413 trait_def: trait_type,
414 bridge_config: bridge_cfg,
415 core_import,
416 wrapper_prefix: "Js",
417 type_paths,
418 error_type: error_type.to_string(),
419 error_constructor: error_constructor.to_string(),
420 };
421 gen_bridge_all(&spec, &generator)
422 }
423}
424
425fn gen_visitor_bridge(
430 trait_type: &TypeDef,
431 _bridge_cfg: &TraitBridgeConfig,
432 struct_name: &str,
433 trait_path: &str,
434 core_crate: &str,
435 type_paths: &HashMap<String, String>,
436) -> String {
437 let mut out = String::with_capacity(8192);
438 writeln!(out, "#[allow(unused_imports)]").unwrap();
441 writeln!(
442 out,
443 "use napi::bindgen_prelude::{{JsObjectValue, ToNapiValue, Unknown, Object}};"
444 )
445 .unwrap();
446 writeln!(out, "#[allow(unused_imports)]").unwrap();
447 writeln!(out, "use napi::JsValue;").unwrap();
448 writeln!(out).unwrap();
449
450 writeln!(out, "fn nodecontext_to_js_object<'e>(").unwrap();
452 writeln!(out, " env: &'e napi::Env,").unwrap();
453 writeln!(out, " ctx: &{core_crate}::visitor::NodeContext,").unwrap();
454 writeln!(out, ") -> napi::Result<napi::bindgen_prelude::Object<'e>> {{").unwrap();
455 writeln!(out, " let mut obj = napi::bindgen_prelude::Object::new(env)?;").unwrap();
456 writeln!(
457 out,
458 " obj.set_named_property(\"nodeType\", env.create_string(&format!(\"{{:?}}\", ctx.node_type))?)?;"
459 )
460 .unwrap();
461 writeln!(
462 out,
463 " obj.set_named_property(\"tagName\", env.create_string(&ctx.tag_name)?)?;"
464 )
465 .unwrap();
466 writeln!(
467 out,
468 " obj.set_named_property(\"depth\", env.create_uint32(ctx.depth as u32)?)?;"
469 )
470 .unwrap();
471 writeln!(
472 out,
473 " obj.set_named_property(\"indexInParent\", env.create_uint32(ctx.index_in_parent as u32)?)?;"
474 )
475 .unwrap();
476 writeln!(out, " obj.set_named_property(\"isInline\", ctx.is_inline)?;").unwrap();
477 writeln!(out, " let parent_tag = match &ctx.parent_tag {{").unwrap();
478 writeln!(out, " Some(s) => env.create_string(s)?.to_unknown(),").unwrap();
479 writeln!(out, " None => {{").unwrap();
480 writeln!(
481 out,
482 " // SAFETY: napi_get_null returns a valid napi_value for the given env."
483 )
484 .unwrap();
485 writeln!(
486 out,
487 " let raw = unsafe {{ napi::bindgen_prelude::ToNapiValue::to_napi_value(env.raw(), napi::bindgen_prelude::Null)? }};"
488 )
489 .unwrap();
490 writeln!(
491 out,
492 " unsafe {{ napi::bindgen_prelude::Unknown::from_raw_unchecked(env.raw(), raw) }}"
493 )
494 .unwrap();
495 writeln!(out, " }}").unwrap();
496 writeln!(out, " }};").unwrap();
497 writeln!(out, " obj.set_named_property(\"parentTag\", parent_tag)?;").unwrap();
498 writeln!(out, " let mut attrs = napi::bindgen_prelude::Object::new(env)?;").unwrap();
499 writeln!(out, " for (k, v) in &ctx.attributes {{").unwrap();
500 writeln!(out, " attrs.set_named_property(k, env.create_string(v)?)?;").unwrap();
501 writeln!(out, " }}").unwrap();
502 writeln!(out, " obj.set_named_property(\"attributes\", attrs)?;").unwrap();
503 writeln!(out, " Ok(obj)").unwrap();
504 writeln!(out, "}}").unwrap();
505 writeln!(out).unwrap();
506
507 writeln!(out, "pub struct {struct_name} {{").unwrap();
511 writeln!(out, " obj: napi::bindgen_prelude::Object<'static>,").unwrap();
512 writeln!(out, "}}").unwrap();
513 writeln!(out).unwrap();
514
515 writeln!(out, "impl std::fmt::Debug for {struct_name} {{").unwrap();
517 writeln!(
518 out,
519 " fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
520 )
521 .unwrap();
522 writeln!(out, " write!(f, \"{struct_name}\")").unwrap();
523 writeln!(out, " }}").unwrap();
524 writeln!(out, "}}").unwrap();
525 writeln!(out).unwrap();
526
527 writeln!(out, "impl {struct_name} {{").unwrap();
529 writeln!(
530 out,
531 " pub fn new(js_obj: napi::bindgen_prelude::Object<'_>) -> Self {{"
532 )
533 .unwrap();
534 writeln!(
535 out,
536 " // SAFETY: The JS object is owned by the Node.js runtime and lives for"
537 )
538 .unwrap();
539 writeln!(
540 out,
541 " // the duration of the enclosing #[napi] call. The bridge is only used"
542 )
543 .unwrap();
544 writeln!(
545 out,
546 " // synchronously during that same call, so 'static is safe here."
547 )
548 .unwrap();
549 writeln!(
550 out,
551 " let obj: napi::bindgen_prelude::Object<'static> = unsafe {{ std::mem::transmute(js_obj) }};"
552 )
553 .unwrap();
554 writeln!(out, " Self {{ obj }}").unwrap();
555 writeln!(out, " }}").unwrap();
556 writeln!(out).unwrap();
557
558 writeln!(out, " fn env(&self) -> napi::Env {{").unwrap();
561 writeln!(
562 out,
563 " // SAFETY: Object<'static> is 3 pointer-sized words; first word is napi_env."
564 )
565 .unwrap();
566 writeln!(
567 out,
568 " let raw: [*mut std::ffi::c_void; 3] = unsafe {{ std::mem::transmute_copy(&self.obj) }};"
569 )
570 .unwrap();
571 writeln!(out, " napi::Env::from_raw(raw[0] as napi::sys::napi_env)").unwrap();
572 writeln!(out, " }}").unwrap();
573 writeln!(out, "}}").unwrap();
574 writeln!(out).unwrap();
575
576 writeln!(out, "impl {trait_path} for {struct_name} {{").unwrap();
578 for method in &trait_type.methods {
579 if method.trait_source.is_some() {
580 continue;
581 }
582 gen_visitor_method_napi(&mut out, method, trait_path, core_crate, type_paths);
583 }
584 writeln!(out, "}}").unwrap();
585 writeln!(out).unwrap();
586 out
587}
588
589fn visitor_param_type(ty: &TypeRef, is_ref: bool, optional: bool, tp: &HashMap<String, String>) -> String {
591 if optional && matches!(ty, TypeRef::String) && is_ref {
592 return "Option<&str>".to_string();
593 }
594 if is_ref {
595 if let TypeRef::Vec(inner) = ty {
596 let inner_str = param_type(inner, "", false, tp);
597 return format!("&[{inner_str}]");
598 }
599 }
600 param_type(ty, "", is_ref, tp)
601}
602
603fn unknown_tuple_type(count: usize) -> String {
605 if count == 0 {
606 return "()".to_string();
607 }
608 let parts = vec!["napi::bindgen_prelude::Unknown"; count];
609 format!("({}{})", parts.join(", "), if count == 1 { "," } else { "" })
610}
611
612fn gen_visitor_method_napi(
614 out: &mut String,
615 method: &MethodDef,
616 _trait_path: &str,
617 _core_crate: &str,
618 type_paths: &HashMap<String, String>,
619) {
620 let name = &method.name;
621
622 let js_name = to_camel_case(name);
624
625 let mut sig_parts = vec!["&mut self".to_string()];
626 for p in &method.params {
627 let ty_str = visitor_param_type(&p.ty, p.is_ref, p.optional, type_paths);
628 sig_parts.push(format!("{}: {}", p.name, ty_str));
629 }
630 let sig = sig_parts.join(", ");
631
632 let ret_ty = match &method.return_type {
633 TypeRef::Named(n) => type_paths
634 .get(n.as_str())
635 .map(|p| p.replace('-', "_"))
636 .unwrap_or_else(|| n.clone()),
637 other => param_type(other, "", false, type_paths),
638 };
639
640 writeln!(out, " fn {name}({sig}) -> {ret_ty} {{").unwrap();
641
642 writeln!(
644 out,
645 " let has_method = self.obj.has_named_property(\"{js_name}\").unwrap_or(false);"
646 )
647 .unwrap();
648 writeln!(out, " if !has_method {{").unwrap();
649 writeln!(out, " return {ret_ty}::Continue;").unwrap();
650 writeln!(out, " }}").unwrap();
651
652 let arg_count = method.params.len();
654 let args_tuple_ty = unknown_tuple_type(arg_count);
655 writeln!(
656 out,
657 " let func: napi::bindgen_prelude::Function<{args_tuple_ty}, napi::bindgen_prelude::Unknown> = match self.obj.get_named_property(\"{js_name}\") {{"
658 )
659 .unwrap();
660 writeln!(out, " Ok(f) => f,").unwrap();
661 writeln!(out, " Err(_) => return {ret_ty}::Continue,").unwrap();
662 writeln!(out, " }};").unwrap();
663
664 let js_args_exprs = build_napi_args(method);
666 if arg_count == 0 {
667 writeln!(out, " let result = func.call(());").unwrap();
668 } else {
669 writeln!(out, " let __env = self.env();").unwrap();
671 for (i, expr) in js_args_exprs.iter().enumerate() {
673 let expr = expr.replace("self.env()", "__env");
675 writeln!(out, " let arg_{i}: napi::bindgen_prelude::Unknown = {expr};").unwrap();
676 }
677 let tuple_args: Vec<String> = (0..arg_count).map(|i| format!("arg_{i}")).collect();
678 let tuple_str = if arg_count == 1 {
679 format!("({},)", tuple_args[0])
680 } else {
681 format!("({})", tuple_args.join(", "))
682 };
683 writeln!(out, " let result = func.call({tuple_str});").unwrap();
684 }
685
686 writeln!(out, " match result {{").unwrap();
688 writeln!(out, " Err(_) => {ret_ty}::Continue,").unwrap();
689 writeln!(out, " Ok(val) => {{").unwrap();
690 writeln!(
691 out,
692 " if let Ok(s) = val.coerce_to_string().and_then(|s| s.into_utf8()).and_then(|s| s.into_owned()) {{"
693 )
694 .unwrap();
695 writeln!(out, " match s.to_lowercase().as_str() {{").unwrap();
696 writeln!(out, " \"continue\" => {ret_ty}::Continue,").unwrap();
697 writeln!(out, " \"skip\" => {ret_ty}::Skip,").unwrap();
698 writeln!(
699 out,
700 " \"preserve_html\" | \"preservehtml\" => {ret_ty}::PreserveHtml,"
701 )
702 .unwrap();
703 writeln!(
704 out,
705 " other => {ret_ty}::Custom(other.to_string()),"
706 )
707 .unwrap();
708 writeln!(out, " }}").unwrap();
709 writeln!(out, " }} else {{").unwrap();
710 writeln!(out, " {ret_ty}::Continue").unwrap();
711 writeln!(out, " }}").unwrap();
712 writeln!(out, " }}").unwrap();
713 writeln!(out, " }}").unwrap();
714 writeln!(out, " }}").unwrap();
715 writeln!(out).unwrap();
716}
717
718fn build_napi_args(method: &MethodDef) -> Vec<String> {
722 method
723 .params
724 .iter()
725
726 .map(|p| {
727 if let TypeRef::Named(n) = &p.ty {
728 if n == "NodeContext" {
729 return format!(
730 "match nodecontext_to_js_object(&self.env(), {}{}) {{ Ok(o) => o.to_unknown(), Err(_) => unsafe {{ \
731 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(std::ptr::null_mut(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
732 napi::bindgen_prelude::Unknown::from_raw_unchecked(std::ptr::null_mut(), r) }} \
733 }}",
734 if p.is_ref { "" } else { "&" },
735 p.name
736 );
737 }
738 }
739 if p.optional && matches!(&p.ty, TypeRef::String) && p.is_ref {
741 return format!(
742 "match {name} {{ \
743 Some(s) => match self.env().create_string(s) {{ \
744 Ok(v) => v.to_unknown(), \
745 Err(_) => unsafe {{ \
746 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(std::ptr::null_mut(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
747 napi::bindgen_prelude::Unknown::from_raw_unchecked(std::ptr::null_mut(), r) }} \
748 }}, \
749 None => unsafe {{ \
750 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(std::ptr::null_mut(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
751 napi::bindgen_prelude::Unknown::from_raw_unchecked(std::ptr::null_mut(), r) }} \
752 }}",
753 name = p.name
754 );
755 }
756 if matches!(&p.ty, TypeRef::String) && p.is_ref {
758 return format!(
759 "match self.env().create_string({name}) {{ \
760 Ok(s) => s.to_unknown(), \
761 Err(_) => unsafe {{ \
762 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(std::ptr::null_mut(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
763 napi::bindgen_prelude::Unknown::from_raw_unchecked(std::ptr::null_mut(), r) }} \
764 }}",
765 name = p.name
766 );
767 }
768 if matches!(&p.ty, TypeRef::String) {
770 return format!(
771 "match self.env().create_string({name}.as_str()) {{ \
772 Ok(s) => s.to_unknown(), \
773 Err(_) => unsafe {{ \
774 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(std::ptr::null_mut(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
775 napi::bindgen_prelude::Unknown::from_raw_unchecked(std::ptr::null_mut(), r) }} \
776 }}",
777 name = p.name
778 );
779 }
780 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool)) {
782 return format!(
783 "unsafe {{ \
784 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), {name}).unwrap_or(std::ptr::null_mut()); \
785 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }}",
786 name = p.name
787 );
788 }
789 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::U32)) {
791 return format!(
792 "match self.env().create_uint32({name}) {{ Ok(n) => n.to_unknown(), Err(_) => unsafe {{ \
793 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(std::ptr::null_mut(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
794 napi::bindgen_prelude::Unknown::from_raw_unchecked(std::ptr::null_mut(), r) }} \
795 }}",
796 name = p.name
797 );
798 }
799 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Usize)) {
800 return format!(
801 "match self.env().create_uint32({name} as u32) {{ Ok(n) => n.to_unknown(), Err(_) => unsafe {{ \
802 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(std::ptr::null_mut(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
803 napi::bindgen_prelude::Unknown::from_raw_unchecked(std::ptr::null_mut(), r) }} \
804 }}",
805 name = p.name
806 );
807 }
808 format!(
811 "match self.env().create_string(&format!(\"{{:?}}\", {name})) {{ Ok(s) => s.to_unknown(), Err(_) => unsafe {{ \
812 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(std::ptr::null_mut(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
813 napi::bindgen_prelude::Unknown::from_raw_unchecked(std::ptr::null_mut(), r) }} \
814 }}",
815 name = p.name
816 )
817 })
818 .collect()
819}
820
821fn to_camel_case(name: &str) -> String {
823 let mut result = String::with_capacity(name.len());
824 let mut capitalize_next = false;
825 for (i, c) in name.chars().enumerate() {
826 if c == '_' {
827 capitalize_next = true;
828 } else if capitalize_next {
829 result.extend(c.to_uppercase());
830 capitalize_next = false;
831 } else if i == 0 {
832 result.extend(c.to_lowercase());
833 } else {
834 result.push(c);
835 }
836 }
837 result
838}
839
840fn param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &HashMap<String, String>) -> String {
842 match ty {
843 TypeRef::Bytes if is_ref => "&[u8]".into(),
844 TypeRef::Bytes => "Vec<u8>".into(),
845 TypeRef::String if is_ref => "&str".into(),
846 TypeRef::String => "String".into(),
847 TypeRef::Path if is_ref => "&std::path::Path".into(),
848 TypeRef::Path => "std::path::PathBuf".into(),
849 TypeRef::Named(n) => {
850 let qualified = tp.get(n).cloned().unwrap_or_else(|| format!("{ci}::{n}"));
851 if is_ref { format!("&{qualified}") } else { qualified }
852 }
853 TypeRef::Vec(inner) => format!("Vec<{}>", param_type(inner, ci, false, tp)),
854 TypeRef::Optional(inner) => format!("Option<{}>", param_type(inner, ci, false, tp)),
855 TypeRef::Primitive(p) => prim(p).into(),
856 TypeRef::Unit => "()".into(),
857 TypeRef::Char => "char".into(),
858 TypeRef::Map(k, v) => format!(
859 "std::collections::HashMap<{}, {}>",
860 param_type(k, ci, false, tp),
861 param_type(v, ci, false, tp)
862 ),
863 TypeRef::Json => "serde_json::Value".into(),
864 TypeRef::Duration => "std::time::Duration".into(),
865 }
866}
867
868fn prim(p: &alef_core::ir::PrimitiveType) -> &'static str {
869 use alef_core::ir::PrimitiveType::*;
870 match p {
871 Bool => "bool",
872 U8 => "u8",
873 U16 => "u16",
874 U32 => "u32",
875 U64 => "u64",
876 I8 => "i8",
877 I16 => "i16",
878 I32 => "i32",
879 I64 => "i64",
880 F32 => "f32",
881 F64 => "f64",
882 Usize => "usize",
883 Isize => "isize",
884 }
885}
886
887pub fn find_bridge_param<'a>(
892 func: &alef_core::ir::FunctionDef,
893 bridges: &'a [TraitBridgeConfig],
894) -> Option<(usize, &'a TraitBridgeConfig)> {
895 for (idx, param) in func.params.iter().enumerate() {
896 let named = match ¶m.ty {
897 TypeRef::Named(n) => Some(n.as_str()),
898 TypeRef::Optional(inner) => {
899 if let TypeRef::Named(n) = inner.as_ref() {
900 Some(n.as_str())
901 } else {
902 None
903 }
904 }
905 _ => None,
906 };
907 for bridge in bridges {
908 if let Some(type_name) = named {
909 if bridge.type_alias.as_deref() == Some(type_name) {
910 return Some((idx, bridge));
911 }
912 }
913 if bridge.param_name.as_deref() == Some(param.name.as_str()) {
914 return Some((idx, bridge));
915 }
916 }
917 }
918 None
919}
920
921#[allow(clippy::too_many_arguments)]
925pub fn gen_bridge_function(
926 func: &alef_core::ir::FunctionDef,
927 bridge_param_idx: usize,
928 bridge_cfg: &TraitBridgeConfig,
929 mapper: &dyn alef_codegen::type_mapper::TypeMapper,
930 _cfg: &alef_codegen::generators::RustBindingConfig<'_>,
931 _adapter_bodies: &alef_codegen::generators::AdapterBodies,
932 opaque_types: &ahash::AHashSet<String>,
933 core_import: &str,
934) -> String {
935 use alef_core::ir::TypeRef;
936
937 let struct_name = format!("Js{}Bridge", bridge_cfg.trait_name);
938 let handle_path = format!("{core_import}::visitor::VisitorHandle");
939 let param_name = &func.params[bridge_param_idx].name;
940 let bridge_param = &func.params[bridge_param_idx];
941 let is_optional = bridge_param.optional || matches!(&bridge_param.ty, TypeRef::Optional(_));
942
943 let mut sig_parts = vec![];
946 for (idx, p) in func.params.iter().enumerate() {
947 if idx == bridge_param_idx {
948 if is_optional {
949 sig_parts.push(format!("{}: Option<napi::bindgen_prelude::Object>", p.name));
950 } else {
951 sig_parts.push(format!("{}: napi::bindgen_prelude::Object", p.name));
952 }
953 } else {
954 let promoted = idx > bridge_param_idx || func.params[..idx].iter().any(|pp| pp.optional);
955 let ty = if p.optional || promoted {
956 format!("Option<{}>", mapper.map_type(&p.ty))
957 } else {
958 mapper.map_type(&p.ty)
959 };
960 sig_parts.push(format!("{}: {}", p.name, ty));
961 }
962 }
963
964 let params_str = sig_parts.join(", ");
965 let return_type = mapper.map_type(&func.return_type);
966 let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
967
968 let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
969
970 let bridge_wrap = if is_optional {
972 format!(
973 "let {param_name} = {param_name}.map(|v| {{\n \
974 let bridge = {struct_name}::new(v);\n \
975 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
976 }});"
977 )
978 } else {
979 format!(
980 "let {param_name} = {{\n \
981 let bridge = {struct_name}::new({param_name});\n \
982 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
983 }};"
984 )
985 };
986
987 let serde_bindings: String = func
989 .params
990 .iter()
991 .enumerate()
992 .filter(|(idx, p)| {
993 if *idx == bridge_param_idx {
994 return false;
995 }
996 let named = match &p.ty {
997 TypeRef::Named(n) => Some(n.as_str()),
998 TypeRef::Optional(inner) => {
999 if let TypeRef::Named(n) = inner.as_ref() {
1000 Some(n.as_str())
1001 } else {
1002 None
1003 }
1004 }
1005 _ => None,
1006 };
1007 named.is_some_and(|n| !opaque_types.contains(n))
1008 })
1009 .map(|(_, p)| {
1010 let name = &p.name;
1011 let core_path = format!(
1012 "{core_import}::{}",
1013 match &p.ty {
1014 TypeRef::Named(n) => n.clone(),
1015 TypeRef::Optional(inner) =>
1016 if let TypeRef::Named(n) = inner.as_ref() {
1017 n.clone()
1018 } else {
1019 String::new()
1020 },
1021 _ => String::new(),
1022 }
1023 );
1024 if p.optional || matches!(&p.ty, TypeRef::Optional(_)) {
1025 format!("let {name}_core: Option<{core_path}> = {name}.map(|v| v.into());\n ")
1026 } else {
1027 format!("let {name}_core: {core_path} = {name}.into();\n ")
1028 }
1029 })
1030 .collect();
1031
1032 let call_args: Vec<String> = func
1034 .params
1035 .iter()
1036 .enumerate()
1037 .map(|(idx, p)| {
1038 if idx == bridge_param_idx {
1039 return p.name.clone();
1040 }
1041 match &p.ty {
1042 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
1043 if p.optional {
1044 format!("{}.as_ref().map(|v| &v.inner)", p.name)
1045 } else {
1046 format!("&{}.inner", p.name)
1047 }
1048 }
1049 TypeRef::Named(_) => format!("{}_core", p.name),
1050 TypeRef::Optional(inner) => {
1051 if let TypeRef::Named(n) = inner.as_ref() {
1052 if opaque_types.contains(n.as_str()) {
1053 format!("{}.as_ref().map(|v| &v.inner)", p.name)
1054 } else {
1055 format!("{}_core", p.name)
1056 }
1057 } else {
1058 p.name.clone()
1059 }
1060 }
1061 TypeRef::String | TypeRef::Char => {
1062 if p.is_ref {
1063 format!("&{}", p.name)
1064 } else {
1065 p.name.clone()
1066 }
1067 }
1068 _ => p.name.clone(),
1069 }
1070 })
1071 .collect();
1072 let call_args_str = call_args.join(", ");
1073
1074 let core_fn_path = {
1075 let path = func.rust_path.replace('-', "_");
1076 if path.starts_with(core_import) {
1077 path
1078 } else {
1079 format!("{core_import}::{}", func.name)
1080 }
1081 };
1082 let core_call = format!("{core_fn_path}({call_args_str})");
1083
1084 let return_wrap = match &func.return_type {
1085 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
1086 format!("{name} {{ inner: std::sync::Arc::new(val) }}")
1087 }
1088 TypeRef::Named(_) => "val.into()".to_string(),
1089 TypeRef::String | TypeRef::Bytes => "val.into()".to_string(),
1090 _ => "val".to_string(),
1091 };
1092
1093 let body = if func.error_type.is_some() {
1094 if return_wrap == "val" {
1095 format!("{bridge_wrap}\n {serde_bindings}{core_call}{err_conv}")
1096 } else {
1097 format!("{bridge_wrap}\n {serde_bindings}{core_call}.map(|val| {return_wrap}){err_conv}")
1098 }
1099 } else {
1100 format!("{bridge_wrap}\n {serde_bindings}{core_call}")
1101 };
1102
1103 let js_name = {
1104 let mut result = String::with_capacity(func.name.len());
1105 let mut capitalize_next = false;
1106 for (i, c) in func.name.chars().enumerate() {
1107 if c == '_' {
1108 capitalize_next = true;
1109 } else if capitalize_next {
1110 result.extend(c.to_uppercase());
1111 capitalize_next = false;
1112 } else if i == 0 {
1113 result.extend(c.to_lowercase());
1114 } else {
1115 result.push(c);
1116 }
1117 }
1118 result
1119 };
1120 let js_name_attr = if js_name != func.name {
1121 format!("(js_name = \"{}\")", js_name)
1122 } else {
1123 String::new()
1124 };
1125
1126 let mut out = String::with_capacity(1024);
1127 if func.error_type.is_some() {
1128 writeln!(out, "#[allow(clippy::missing_errors_doc)]").ok();
1129 }
1130 writeln!(out, "#[napi{js_name_attr}]").ok();
1131 let func_name = &func.name;
1132 writeln!(out, "pub fn {func_name}({params_str}) -> {ret} {{").ok();
1133 writeln!(out, " {body}").ok();
1134 writeln!(out, "}}").ok();
1135
1136 out
1137}