use super::cfg_gates::free_pub_fn_name;
use super::text_transformations::snake_to_camel;
pub fn free_function_names(lib_rs_source: &str) -> Vec<String> {
lib_rs_source.lines().filter_map(free_pub_fn_name).collect()
}
pub fn missing_bridge_functions(
lib_rs_source: &str,
bridge_dart_source: &str,
exclude_functions: &[String],
) -> Vec<String> {
let excluded: std::collections::HashSet<&str> = exclude_functions.iter().map(String::as_str).collect();
free_function_names(lib_rs_source)
.into_iter()
.filter(|name| !excluded.contains(name.as_str()))
.filter(|name| {
let camel = snake_to_camel(name);
!bridge_dart_source.contains(&format!(" {camel}("))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn free_function_names_lists_every_top_level_pub_fn() {
let lib_rs = "\
use std::sync::Arc;
pub fn count_widgets(collection: String) -> Result<i64, String> {
Ok(0)
}
pub async fn record_price(id: String, price_cents: i64) -> Result<(), String> {
Ok(())
}
fn private_helper() {}
";
assert_eq!(
free_function_names(lib_rs),
vec!["count_widgets".to_string(), "record_price".to_string()],
);
}
#[test]
fn missing_bridge_functions_reports_a_facade_function_absent_from_the_bridge() {
let lib_rs = "\
pub fn count_widgets(collection: String) -> Result<i64, String> {
Ok(0)
}
pub fn record_price(id: String, price_cents: i64) -> Result<(), String> {
Ok(())
}
";
let bridge_dart = "Future<int> countWidgets({required String collection}) => RustLib.instance.api.crateCountWidgets(collection: collection);\n";
let missing = missing_bridge_functions(lib_rs, bridge_dart, &[]);
assert_eq!(
missing,
vec!["record_price".to_string()],
"record_price is declared in the facade but has no matching function in the bridge"
);
}
#[test]
fn missing_bridge_functions_is_empty_when_every_facade_function_is_bridged() {
let lib_rs = "pub fn count_widgets(collection: String) -> Result<i64, String> {\n Ok(0)\n}\n";
let bridge_dart = "Future<int> countWidgets({required String collection}) => RustLib.instance.api.crateCountWidgets(collection: collection);\n";
assert!(missing_bridge_functions(lib_rs, bridge_dart, &[]).is_empty());
}
#[test]
fn missing_bridge_functions_ignores_configured_exclusions() {
let lib_rs = "\
pub fn count_widgets(collection: String) -> Result<i64, String> {
Ok(0)
}
pub fn internal_only(id: String) -> Result<(), String> {
Ok(())
}
";
let bridge_dart = "Future<int> countWidgets({required String collection}) => RustLib.instance.api.crateCountWidgets(collection: collection);\n";
let missing = missing_bridge_functions(lib_rs, bridge_dart, &["internal_only".to_string()]);
assert!(
missing.is_empty(),
"excluded function must not be reported missing: {missing:?}"
);
}
}