mod bridge;
mod bridge_functions;
mod bridge_generator;
mod options_field_bridge;
mod typescript_bridge;
mod visitor_bridge;
use crate::core::config::TraitBridgeConfig;
pub use bridge::gen_trait_bridge;
pub use bridge_functions::gen_bridge_function;
pub use bridge_generator::NapiBridgeGenerator;
pub use options_field_bridge::gen_options_field_bridge_function;
pub use typescript_bridge::gen_typescript_trait_bridge_files;
pub use crate::codegen::generators::trait_bridge::find_bridge_param;
pub fn find_options_field_binding<'a>(
func: &crate::core::ir::FunctionDef,
bridges: &'a [TraitBridgeConfig],
) -> Option<(usize, &'a TraitBridgeConfig)> {
for bridge in bridges {
if bridge.bind_via != crate::core::config::BridgeBinding::OptionsField {
continue;
}
if let Some(options_type) = &bridge.options_type {
for (idx, param) in func.params.iter().enumerate() {
let matches = match ¶m.ty {
crate::core::ir::TypeRef::Named(n) => n == options_type,
crate::core::ir::TypeRef::Optional(inner) => {
if let crate::core::ir::TypeRef::Named(n) = inner.as_ref() {
n == options_type
} else {
false
}
}
_ => false,
};
if matches {
return Some((idx, bridge));
}
}
}
}
None
}
#[cfg(test)]
mod tests {
#[test]
fn visitor_bridge_uses_configured_context_and_result_metadata() {
let (api, trait_type, bridge) = crate::codegen::visitor_context::test_support::neutral_visitor_fixture();
let output = super::gen_trait_bridge(
&trait_type,
&bridge,
"sample_core",
"SampleError",
"SampleError::Message { message: {msg} }",
&api,
)
.expect("visitor bridge should generate");
crate::codegen::visitor_context::test_support::assert_neutral_visitor_output(&output.code);
assert!(output.code.contains("displayName"));
}
#[test]
fn plugin_trait_bridge_emits_dispose_method_on_rust_struct() {
use crate::core::config::{BridgeBinding, TraitBridgeConfig};
use crate::core::ir::ApiSurface;
let trait_def = crate::core::ir::TypeDef {
name: "TextProcessor".to_string(),
rust_path: "sample_core::TextProcessor".to_string(),
original_rust_path: String::new(),
fields: vec![],
methods: vec![],
is_opaque: false,
is_clone: false,
is_copy: false,
is_trait: true,
has_default: false,
has_stripped_cfg_fields: false,
is_return_type: false,
serde_rename_all: None,
has_serde: false,
super_traits: vec![],
doc: String::new(),
cfg: None,
binding_excluded: false,
binding_exclusion_reason: None,
is_variant_wrapper: false,
has_lifetime_params: false,
version: Default::default(),
};
let bridge_cfg = TraitBridgeConfig {
trait_name: "TextProcessor".to_string(),
super_trait: Some("Plugin".to_string()),
registry_getter: Some("sample_core::get_text_processor_registry".to_string()),
register_fn: Some("register_text_processor".to_string()),
unregister_fn: None,
clear_fn: None,
type_alias: None,
param_name: None,
register_extra_args: None,
exclude_languages: vec![],
ffi_skip_methods: vec![],
bind_via: BridgeBinding::FunctionParam,
options_type: None,
options_field: None,
context_type: None,
result_type: None,
};
let api = ApiSurface {
crate_name: "sample-core".to_string(),
version: "0.1.0".to_string(),
types: vec![trait_def.clone()],
functions: vec![],
enums: vec![],
errors: vec![],
excluded_type_paths: Default::default(),
excluded_trait_names: Default::default(),
services: vec![],
handler_contracts: vec![],
unsupported_public_items: vec![],
};
let output = super::gen_trait_bridge(
&trait_def,
&bridge_cfg,
"sample_core",
"SampleCoreError",
"SampleCoreError::from({msg})",
&api,
)
.expect("gen_trait_bridge must succeed for TextProcessor");
assert!(
output.code.contains("pub async fn dispose"),
"bridge struct must expose `dispose()` to release TSFN and allow vitest workers to exit;\nactual code:\n{}",
output.code
);
}
}