use regex::Regex;
use std::sync::OnceLock;
const ALEF_LOADER_MARKER: &str = "_alefResolveExternalLibrary";
pub fn rewrite_frb_external_library_loader(source: &str, package_name: &str, module_name: &str, stem: &str) -> String {
let with_loader = if source.contains(ALEF_LOADER_MARKER) {
source.to_string()
} else {
let Some(prologue) = frb_init_prologue(source) else {
return source.to_string();
};
let replacement = frb_init_prologue_replacement(package_name, module_name, stem);
source.replacen(&prologue, &replacement, 1)
};
ensure_loader_imports(&with_loader)
}
fn frb_init_prologue(source: &str) -> Option<String> {
let re = init_prologue_regex();
re.find(source).map(|m| m.as_str().to_string())
}
fn init_prologue_regex() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
Regex::new(r"(?m)^\s*/// Initialize flutter_rust_bridge\n\s*static Future<void> init\((?s:.)*?\}\) async \{\n")
.expect("init prologue regex must compile")
})
}
fn frb_init_prologue_replacement(package_name: &str, module_name: &str, stem: &str) -> String {
format!(
r#" /// Resolve the prebuilt native library from environment variable,
/// package-relative location, or defer to flutter_rust_bridge's default loader.
/// Returns `null` to defer to flutter_rust_bridge's default loader.
///
/// Checks in order:
/// 1. FRB_DART_LOAD_EXTERNAL_LIBRARY_NATIVE_LIB_DIR environment variable
/// (allows test harnesses to point to development build paths)
/// 2. Package-installed location with RID subdirectory (lib/src/native/<rid>/)
/// (for published pub.dev packages with platform-specific bundled native libraries)
/// 3. Package-installed location (lib/src/{module}_bridge_generated/)
/// (legacy fallback for development or packages without per-platform binaries)
/// 4. Returns null (flutter_rust_bridge falls back to its default loader)
static Future<ExternalLibrary?> {marker}() async {{
try {{
const candidates = <String>[
'lib{stem}.dylib',
'lib{stem}.so',
'{stem}.dll',
];
// Check FRB_DART_LOAD_EXTERNAL_LIBRARY_NATIVE_LIB_DIR env var first.
// This allows test harnesses to override library location for development.
final envDir = Platform.environment['FRB_DART_LOAD_EXTERNAL_LIBRARY_NATIVE_LIB_DIR'];
if (envDir != null && envDir.isNotEmpty) {{
final libDir = Directory(envDir);
if (libDir.existsSync()) {{
for (final candidate in candidates) {{
final libPath = '$envDir/$candidate';
if (File(libPath).existsSync()) {{
return ExternalLibrary.open(libPath);
}}
}}
}}
}}
// Compute RID (runtime identifier) from platform and architecture.
String? computeRid() {{
final os = Platform.operatingSystem;
// Use Dart's Platform.version to detect architecture.
// Format: "Dart <version> (stable) ... on \"<os> <arch>\""
final version = Platform.version;
final archMatch = version.contains('x86_64') ? 'x64'
: version.contains('aarch64') || version.contains('arm64') ? 'arm64'
: version.contains('armv7') ? 'arm'
: null;
if (archMatch == null) return null;
switch (os) {{
case 'linux':
return 'linux-$archMatch';
case 'macos':
return 'macos-$archMatch';
case 'windows':
return 'windows-$archMatch';
default:
return null;
}}
}}
final rid = computeRid();
if (rid != null) {{
final packageRoot =
await Isolate.resolvePackageUri(Uri.parse('package:{package}/{package}.dart'));
if (packageRoot != null) {{
final ridDir = packageRoot.resolve('src/native/$rid/');
for (final candidate in candidates) {{
final libPath = ridDir.resolve(candidate).toFilePath();
if (File(libPath).existsSync()) {{
return ExternalLibrary.open(libPath);
}}
}}
}}
}}
// Check legacy package-installed location as fallback.
final packageRoot =
await Isolate.resolvePackageUri(Uri.parse('package:{package}/{package}.dart'));
if (packageRoot != null) {{
final libDir = packageRoot.resolve('src/{module}_bridge_generated/');
for (final candidate in candidates) {{
final libPath = libDir.resolve(candidate).toFilePath();
if (File(libPath).existsSync()) {{
return ExternalLibrary.open(libPath);
}}
}}
}}
}} catch (_) {{
// Fall through to the default loader on any resolution failure.
}}
return null;
}}
/// Initialize flutter_rust_bridge
static Future<void> init({{
RustLibApi? api,
BaseHandler? handler,
ExternalLibrary? externalLibrary,
bool forceSameCodegenVersion = true,
}}) async {{
externalLibrary ??= await {marker}();
"#,
marker = ALEF_LOADER_MARKER,
package = package_name,
module = module_name,
stem = stem,
)
}
fn ensure_loader_imports(source: &str) -> String {
let mut result = source.to_string();
let needed = [
("import 'dart:core';", "import 'dart:core';\n"),
("import 'dart:core' as _DartCore;", "import 'dart:core' as _DartCore;\n"),
("import 'dart:io';", "import 'dart:io';\n"),
("import 'dart:isolate';", "import 'dart:isolate';\n"),
];
let anchor = result.find("\nimport ").map(|i| i + 1);
for (probe, line) in needed {
if result.contains(probe) {
continue;
}
match anchor {
Some(pos) => result.insert_str(pos, line),
None => result.insert_str(0, line),
}
}
const SENTINEL: &str = "\u{FEFF}__ALEF_URI_PARSE__\u{FEFF}";
result = result.replace("_DartCore.Uri.parse(", SENTINEL);
result = result.replace("Uri.parse(", "_DartCore.Uri.parse(");
result = result.replace(SENTINEL, "_DartCore.Uri.parse(");
result
}
fn extract_loader_stem(source: &str) -> Option<String> {
let re = stem_regex();
re.captures(source).map(|c| c["stem"].to_string())
}
fn stem_regex() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r"stem:\s*'(?P<stem>[A-Za-z0-9_]+)'").expect("stem regex must compile"))
}
fn apply_loader_fix_from_stem(source: &str) -> String {
let Some(stem) = extract_loader_stem(source) else {
return source.to_string();
};
let crate_base = stem.strip_suffix("_dart").unwrap_or(&stem);
let package_name = crate_base;
let module_name = crate_base;
rewrite_frb_external_library_loader(source, package_name, module_name, &stem)
}
pub fn rewrite_frb_sealed_variants(source: &str) -> String {
let source = apply_loader_fix_from_stem(source);
let source = source.as_str();
let variant_re = variant_regex();
variant_re
.replace_all(source, |caps: ®ex::Captures<'_>| {
let prefix = &caps["prefix"];
let params = &caps["params"];
let suffix = &caps["suffix"];
let variant_pascal = &caps["variant"];
let rewritten_params = rewrite_param_list(params, variant_pascal);
format!("{prefix}{rewritten_params}{suffix}")
})
.into_owned()
}
fn variant_regex() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
Regex::new(
r"(?s)(?P<prefix>const\s+factory\s+[A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*\s*\(\s*\{)(?P<params>[^{}]*)(?P<suffix>\}\s*\)\s*=\s*[A-Za-z_][A-Za-z0-9_]*_(?P<variant>[A-Za-z][A-Za-z0-9]*)\s*;)",
)
.expect("variant regex must compile")
})
}
pub fn fix_handler_executor_calls(source: &str) -> String {
let mut result = source.replace(" implements RustLibApi async {", " implements RustLibApi {");
result = rewrite_handler_calls_in_parameterized_functions(&result);
result = ensure_handler_closures_are_async(&result);
result
}
fn rewrite_handler_calls_in_parameterized_functions(source: &str) -> String {
let lines: Vec<&str> = source.lines().collect();
let mut result = String::new();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
let is_function_start = is_likely_function_start(line);
if is_function_start {
let is_handler_parameterized = detect_handler_parameter(&lines, i);
let mut func_lines = vec![line];
i += 1;
let mut depth = count_brace_depth(line);
while i < lines.len() && depth > 0 {
let curr_line = lines[i];
func_lines.push(curr_line);
depth += count_brace_depth(curr_line);
i += 1;
}
let func_text = func_lines.join("\n");
let rewritten = if is_handler_parameterized {
rewrite_handler_to_task_executor(&func_text)
} else {
func_text
};
result.push_str(&rewritten);
result.push('\n');
} else {
result.push_str(line);
result.push('\n');
i += 1;
}
}
if !source.ends_with('\n') && result.ends_with('\n') {
result.pop();
}
result
}
fn is_likely_function_start(line: &str) -> bool {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("//") {
return false;
}
if trimmed.starts_with("@") {
return false; }
if !line.contains('(') {
return false;
}
if trimmed.starts_with("}") || trimmed.starts_with("]") || trimmed.starts_with(")") {
return false;
}
true
}
fn count_brace_depth(line: &str) -> i32 {
let opens = line.chars().filter(|c| *c == '{').count() as i32;
let closes = line.chars().filter(|c| *c == '}').count() as i32;
opens - closes
}
fn detect_handler_parameter(lines: &[&str], idx: usize) -> bool {
if idx >= lines.len() {
return false;
}
let line = lines[idx];
if !line.contains('(') {
for l in lines.iter().take(std::cmp::min(idx + 20, lines.len())).skip(idx) {
if l.contains("handler") && l.contains("Function") {
return true;
}
if l.contains(')') && l.contains('{') {
break;
}
}
} else {
let mut sig = line.to_string();
let mut paren_depth = line.chars().filter(|c| *c == '(').count() - line.chars().filter(|c| *c == ')').count();
let mut j = idx + 1;
while j < lines.len() && paren_depth > 0 {
let l = lines[j];
sig.push(' ');
sig.push_str(l);
paren_depth += l.chars().filter(|c| *c == '(').count();
paren_depth -= l.chars().filter(|c| *c == ')').count();
j += 1;
}
if sig.contains("handler") && sig.contains("Function") {
return true;
}
}
false
}
fn rewrite_handler_to_task_executor(source: &str) -> String {
let mut result = source.to_string();
let orphaned_paren_sync =
Regex::new(r"(?s)\),\s*\)\.executeSync\(\)").expect("orphaned paren sync pattern must compile");
result = orphaned_paren_sync.replace_all(&result, ").executeSync()").into_owned();
let orphaned_paren_async =
Regex::new(r"(?s)\),\s*\)\.executeNormal\(\)").expect("orphaned paren async pattern must compile");
result = orphaned_paren_async
.replace_all(&result, ").executeNormal()")
.into_owned();
result
}
fn ensure_handler_closures_are_async(source: &str) -> String {
let lines: Vec<&str> = source.lines().collect();
let mut lines_to_fix: std::collections::HashSet<usize> = std::collections::HashSet::new();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
let trimmed_line = line.trim();
if trimmed_line.starts_with("//")
|| line.contains("async")
|| trimmed_line.starts_with("class ")
|| trimmed_line.starts_with("abstract class ")
|| trimmed_line.starts_with("mixin ")
{
i += 1;
continue;
}
let contains_await_handler =
(i..std::cmp::min(i + 30, lines.len())).any(|j| lines[j].contains("await handler("));
if contains_await_handler {
let parens_balanced =
line.chars().filter(|c| *c == '(').count() == line.chars().filter(|c| *c == ')').count();
if parens_balanced && line.contains('{') {
lines_to_fix.insert(i);
}
else if !parens_balanced {
for (j, check_line) in lines
.iter()
.enumerate()
.take(std::cmp::min(i + 30, lines.len()))
.skip(i + 1)
{
if check_line.contains(')') && check_line.contains('{') && !check_line.trim().starts_with("//") {
if !check_line.contains("async") {
lines_to_fix.insert(j);
}
break;
}
}
}
}
i += 1;
}
let mut result = String::new();
for (i, line) in lines.iter().enumerate() {
if lines_to_fix.contains(&i) {
let fixed = if line.contains(") {") {
line.replace(") {", ") async {")
} else {
let trimmed = line.trim_end();
if trimmed.ends_with("{") {
format!("{} async {{", trimmed.trim_end_matches('{').trim_end())
} else {
line.to_string()
}
};
result.push_str(&fixed);
} else {
result.push_str(line);
}
result.push('\n');
}
if !source.ends_with('\n') && result.ends_with('\n') {
result.pop();
}
result
}
fn rewrite_param_list(params: &str, variant_pascal: &str) -> String {
let param_re = param_regex();
let matches: Vec<regex::Captures<'_>> = param_re.captures_iter(params).collect();
let total_fields = matches
.iter()
.filter(|m| {
let name = m.name("name").map(|m| m.as_str()).unwrap_or("");
is_positional_field(name)
})
.count();
if total_fields == 0 {
return params.to_string();
}
let mut out = String::with_capacity(params.len());
let mut cursor = 0usize;
for caps in &matches {
let whole = caps.get(0).expect("regex match must have group 0");
let name_match = caps.name("name").expect("name capture is required");
let raw_name = name_match.as_str();
out.push_str(¶ms[cursor..name_match.start()]);
if let Some(field_idx) = field_index(raw_name) {
let type_name = caps.name("type").map(|m| m.as_str()).unwrap_or("").trim();
let new_name = payload_param_name(type_name, variant_pascal, field_idx, total_fields);
out.push_str(&new_name);
} else {
out.push_str(raw_name);
}
cursor = name_match.end();
let _ = whole; }
out.push_str(¶ms[cursor..]);
out
}
fn param_regex() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
Regex::new(r"required\s+(?P<type>[^,{}]+?)\s+(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s*(?:,|$)")
.expect("param regex must compile")
})
}
fn field_index(name: &str) -> Option<usize> {
let rest = name.strip_prefix("field")?;
rest.parse::<usize>().ok()
}
fn is_positional_field(name: &str) -> bool {
field_index(name).is_some()
}
fn payload_param_name(type_name: &str, variant_pascal: &str, field_idx: usize, total_fields: usize) -> String {
let _ = (type_name, variant_pascal, total_fields);
format!("field{field_idx}")
}
#[allow(dead_code)]
fn to_lower_camel(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
Some(first) => first.to_lowercase().collect::<String>() + chars.as_str(),
None => String::new(),
}
}
#[allow(dead_code)]
fn is_dart_primitive(type_name: &str) -> bool {
matches!(
type_name,
"String"
| "int"
| "double"
| "bool"
| "num"
| "void"
| "dynamic"
| "Object"
| "Uint8List"
| "List"
| "Map"
| "Set"
| "BigInt"
| "DateTime"
| "Duration"
)
}
pub fn filter_excluded_functions(source: &str, exclude_functions: &std::collections::HashSet<&str>) -> String {
if exclude_functions.is_empty() {
return source.to_string();
}
let lines: Vec<&str> = source.lines().collect();
let mut result = String::with_capacity(source.len());
let mut i = 0;
let mut doc_buffer: Vec<&str> = Vec::new();
while i < lines.len() {
let line = lines[i];
let trimmed = line.trim_start();
if trimmed.starts_with("///")
|| trimmed.starts_with("//")
|| (trimmed.starts_with("*") && !trimmed.starts_with("**/"))
{
doc_buffer.push(line);
i += 1;
continue;
}
let mut should_skip_function = false;
if !trimmed.is_empty() && !trimmed.starts_with("class") && !trimmed.starts_with("enum") {
should_skip_function = exclude_functions.iter().any(|&excluded| {
let camel_excluded = snake_to_camel(excluded);
let pattern = format!(" {}(", camel_excluded);
line.contains(&pattern)
});
}
if should_skip_function {
doc_buffer.clear();
loop {
i += 1;
if i >= lines.len() {
break;
}
let check_line = lines[i];
if check_line.contains(';') {
i += 1;
break;
}
}
} else {
for doc_line in &doc_buffer {
result.push_str(doc_line);
result.push('\n');
}
doc_buffer.clear();
result.push_str(line);
result.push('\n');
i += 1;
}
}
for doc_line in &doc_buffer {
result.push_str(doc_line);
result.push('\n');
}
result
}
fn snake_to_camel(name: &str) -> String {
let mut result = String::new();
let mut capitalize_next = false;
for c in name.chars() {
if c == '_' {
capitalize_next = true;
} else if capitalize_next {
for upper_c in c.to_uppercase() {
result.push(upper_c);
}
capitalize_next = false;
} else if result.is_empty() {
for lower_c in c.to_lowercase() {
result.push(lower_c);
}
} else {
result.push(c);
}
}
result
}
pub fn make_struct_fields_with_defaults_optional(source: &str) -> String {
source.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn named_struct_payload_preserves_field0() {
let input = r#"sealed class FormatMetadata with _$FormatMetadata {
const FormatMetadata._();
const factory FormatMetadata.pdf({required PdfMetadata field0}) =
FormatMetadata_Pdf;
const factory FormatMetadata.docx({required DocxMetadata field0}) =
FormatMetadata_Docx;
}
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required PdfMetadata field0"),
"PdfMetadata payload should keep `field0`, got:\n{out}"
);
assert!(
out.contains("required DocxMetadata field0"),
"DocxMetadata payload should keep `field0`, got:\n{out}"
);
}
#[test]
fn primitive_payload_preserves_field0() {
let input = r#" const factory OutputFormat.custom({required String field0}) =
OutputFormat_Custom;
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required String field0"),
"String payload should keep `field0`, got:\n{out}"
);
}
#[test]
fn multi_field_tuple_preserves_field_indices() {
let input = r#" const factory Point.xy({required int field0, required int field1}) =
Point_Xy;
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required int field0"),
"first tuple field should keep `field0`, got:\n{out}"
);
assert!(
out.contains("required int field1"),
"second tuple field should keep `field1`, got:\n{out}"
);
}
#[test]
fn named_struct_field_is_preserved() {
let input = r#" const factory Shape.rect({required double width, required double height}) =
Shape_Rect;
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required double width"),
"named field `width` must be preserved, got:\n{out}"
);
assert!(
out.contains("required double height"),
"named field `height` must be preserved, got:\n{out}"
);
}
#[test]
fn non_variant_lines_are_untouched() {
let input = r#"// This file is automatically generated.
import 'package:freezed_annotation/freezed_annotation.dart';
Future<int> extractBytes({required List<int> content}) =>
RustLib.instance.api.crateExtractBytes(content: content);
class Foo {
final int field0;
Foo({required this.field0});
}
"#;
let out = rewrite_frb_sealed_variants(input);
assert_eq!(out, input, "non-variant code must round-trip unchanged");
}
#[test]
fn unrelated_payload_preserves_field0() {
let input = r#" const factory Drawable.image({required Bitmap field0}) =
Drawable_Image;
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required Bitmap field0"),
"unrelated payload type should keep `field0`, got:\n{out}"
);
}
#[test]
fn nullable_payload_preserves_field0() {
let input = r#" const factory Either.left({required LeftValue? field0}) =
Either_Left;
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required LeftValue? field0"),
"nullable payload should keep `field0`, got:\n{out}"
);
}
#[test]
fn realistic_sample_crate_format_metadata_block_preserves_field0() {
let input = r#"sealed class FormatMetadata with _$FormatMetadata {
const FormatMetadata._();
const factory FormatMetadata.pdf({required PdfMetadata field0}) =
FormatMetadata_Pdf;
const factory FormatMetadata.docx({required DocxMetadata field0}) =
FormatMetadata_Docx;
const factory FormatMetadata.excel({required ExcelMetadata field0}) =
FormatMetadata_Excel;
const factory FormatMetadata.code({required String field0}) =
FormatMetadata_Code;
}
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(out.contains("required PdfMetadata field0"));
assert!(out.contains("required DocxMetadata field0"));
assert!(out.contains("required ExcelMetadata field0"));
assert!(out.contains("required String field0"));
assert!(out.contains("sealed class FormatMetadata"));
assert!(out.contains("FormatMetadata_Pdf"));
}
#[test]
fn idempotent_when_run_twice() {
let input = r#" const factory FormatMetadata.pdf({required PdfMetadata field0}) =
FormatMetadata_Pdf;
"#;
let once = rewrite_frb_sealed_variants(input);
let twice = rewrite_frb_sealed_variants(&once);
assert_eq!(once, twice, "rewriter must be idempotent");
}
#[test]
fn multiple_distinct_sealed_class_variants_preserve_field0() {
let input = r#"sealed class FormatMetadata with _$FormatMetadata {
const FormatMetadata._();
const factory FormatMetadata.pdf({required PdfMetadata field0}) =
FormatMetadata_Pdf;
const factory FormatMetadata.docx({required DocxMetadata field0}) =
FormatMetadata_Docx;
}
sealed class OutputFormat with _$OutputFormat {
const OutputFormat._();
const factory OutputFormat.custom({required String field0}) =
OutputFormat_Custom;
const factory OutputFormat.json({required JsonConfig field0}) =
OutputFormat_Json;
}
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(out.contains("required PdfMetadata field0"));
assert!(out.contains("required DocxMetadata field0"));
assert!(out.contains("required String field0"));
assert!(out.contains("required JsonConfig field0"));
}
fn frb_generated_fixture() -> &'static str {
r#"// @generated by `flutter_rust_bridge`@ 2.12.0.
import 'dart:async';
import 'dart:convert';
import 'frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
RustLib._();
/// Initialize flutter_rust_bridge
static Future<void> init({
RustLibApi? api,
BaseHandler? handler,
ExternalLibrary? externalLibrary,
bool forceSameCodegenVersion = true,
}) async {
await instance.initImpl(
api: api,
handler: handler,
externalLibrary: externalLibrary,
forceSameCodegenVersion: forceSameCodegenVersion,
);
}
static const kDefaultExternalLibraryLoaderConfig =
ExternalLibraryLoaderConfig(
stem: 'sample_router_dart',
ioDirectory: 'rust/target/release/',
webPrefix: 'pkg/',
wasmBindgenName: 'wasm_bindgen',
);
}
"#
}
#[test]
fn loader_rewrite_injecsample_package_relative_resolution() {
let out = rewrite_frb_external_library_loader(
frb_generated_fixture(),
"sample_router",
"sample_router",
"sample_router_dart",
);
assert!(
out.contains("externalLibrary ??= await _alefResolveExternalLibrary();"),
"init must resolve the package-relative library, got:\n{out}"
);
assert!(
out.contains("Isolate.resolvePackageUri(_DartCore.Uri.parse('package:sample_router/sample_router.dart'))"),
"loader must resolve the package URI, got:\n{out}"
);
assert!(
out.contains("src/sample_router_bridge_generated/"),
"loader must target the bridge-generated dir, got:\n{out}"
);
assert!(
out.contains("'libsample_router_dart.dylib'"),
"missing macOS candidate, got:\n{out}"
);
assert!(
out.contains("'libsample_router_dart.so'"),
"missing linux candidate, got:\n{out}"
);
assert!(
out.contains("'sample_router_dart.dll'"),
"missing windows candidate, got:\n{out}"
);
assert!(out.contains("import 'dart:io';"), "must import dart:io, got:\n{out}");
assert!(
out.contains("import 'dart:isolate';"),
"must import dart:isolate, got:\n{out}"
);
}
#[test]
fn loader_rewrite_is_idempotent() {
let once = rewrite_frb_external_library_loader(
frb_generated_fixture(),
"sample_router",
"sample_router",
"sample_router_dart",
);
let twice = rewrite_frb_external_library_loader(&once, "sample_router", "sample_router", "sample_router_dart");
assert_eq!(once, twice, "loader rewrite must be idempotent");
assert_eq!(
twice.matches("import 'dart:io';").count(),
1,
"imports must not duplicate"
);
assert_eq!(
twice.matches("_alefResolveExternalLibrary() async").count(),
1,
"helper must not be injected twice"
);
}
#[test]
fn loader_rewrite_is_noop_without_init_prologue() {
let input = "// just some dart\nFuture<int> foo() async => 1;\n";
assert_eq!(
rewrite_frb_external_library_loader(input, "sample_router", "sample_router", "sample_router_dart"),
input
);
}
#[test]
fn sealed_variant_rewrite_also_applies_loader_fix_via_stem() {
let out = rewrite_frb_sealed_variants(frb_generated_fixture());
assert!(
out.contains("externalLibrary ??= await _alefResolveExternalLibrary();"),
"sealed-variant pass must also inject the loader, got:\n{out}"
);
assert!(
out.contains("Isolate.resolvePackageUri(_DartCore.Uri.parse('package:sample_router/sample_router.dart'))"),
"package derived from stem must be `sample_router`, got:\n{out}"
);
}
#[test]
fn sealed_variant_rewrite_leaves_lib_dart_loader_untouched() {
let input = r#"import 'frb_generated.dart';
Future<int> extractBytes({required List<int> content}) =>
RustLib.instance.api.crateExtractBytes(content: content);
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
!out.contains("_alefResolveExternalLibrary"),
"lib.dart must not get a loader, got:\n{out}"
);
}
#[test]
fn loader_rewrite_includes_rid_aware_path() {
let out = rewrite_frb_external_library_loader(
frb_generated_fixture(),
"sample_router",
"sample_router",
"sample_router_dart",
);
assert!(
out.contains("src/native/"),
"loader must check RID-aware path (src/native/<rid>/), got:\n{out}"
);
assert!(
out.contains("computeRid()"),
"loader must compute RID from platform and arch, got:\n{out}"
);
assert!(
out.contains("Platform.operatingSystem"),
"loader must detect operating system, got:\n{out}"
);
assert!(
out.contains("'linux-x64'") || out.contains("linux-"),
"loader must support linux RID variants, got:\n{out}"
);
assert!(
out.contains("'macos-arm64'") || out.contains("macos-"),
"loader must support macos RID variants, got:\n{out}"
);
assert!(
out.contains("'windows-x64'") || out.contains("windows-"),
"loader must support windows RID variants, got:\n{out}"
);
let rid_pos = out.find("src/native/").expect("RID path must exist");
let legacy_pos = out
.find("src/sample_router_bridge_generated/")
.expect("legacy path must exist");
assert!(
rid_pos < legacy_pos,
"RID-aware check must come before legacy fallback, got:\n{out}"
);
}
#[test]
fn filter_excluded_functions_removes_multiline_functions() {
let input = r#"/// Pass `metadata` as `null` when the caller has no extraction metadata available;
/// the metadata bonus simply isn't applied in that case. Texts shorter than
/// `MIN_TEXT_LENGTH` short-circuit to `0.1` regardless of metadata.
Future<double> calculateQualityScore({
required String text,
Map<String, String>? metadata,
}) => RustLib.instance.api.crateCalculateQualityScore(
text: text,
metadata: metadata,
);
Future<ExtractionResult> extractBytes(
{required Uint8List content, required String mimeType}) =>
RustLib.instance.api.crateExtractBytes(content: content, mimeType: mimeType);
"#;
let exclude_set = std::collections::HashSet::from(["calculate_quality_score"]);
let out = filter_excluded_functions(input, &exclude_set);
assert!(
!out.contains("calculateQualityScore"),
"excluded function calculateQualityScore must be removed, got:\n{out}"
);
assert!(
!out.contains("MIN_TEXT_LENGTH"),
"doc comments for excluded function must be removed, got:\n{out}"
);
assert!(
out.contains("extractBytes"),
"non-excluded function extractBytes must remain, got:\n{out}"
);
}
#[test]
fn fix_handler_executor_calls_adds_async_to_closures() {
let input = r#"Future<String> processRequest(Request req, FutureOr<String> Function(String) handler) {
return handler.executeNormal(
SyncTask(request: req),
);
}
int handleRoute(RouteData route, FutureOr<int> Function(String) handler) {
return handler.executeSync(
RouteTask(data: route),
);
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
out.contains(".executeNormal();") || out.contains(".executeSync();"),
"expected task executor method calls in output, got:\n{out}"
);
assert!(
!out.contains("handler("),
"handler callback should NOT be invoked directly (it's serialized), got:\n{out}"
);
assert!(
!out.contains("handler.executeSync(") && !out.contains("handler.executeNormal("),
"handler.executeSync/executeNormal method calls should be removed (moved to task), got:\n{out}"
);
assert!(
!out.contains("await await"),
"should not have duplicate awaits, got:\n{out}"
);
}
#[test]
fn fix_handler_executor_calls_preserves_super_handler_without_parameter() {
let input = r#" @override
App crateServiceApiAppNew() {
return handler.executeSync(
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 5)!;
},
codec: SseCodec(
decodeSuccessData: sse_decode_app,
decodeErrorData: null,
),
constMeta: kConstMeta,
argValues: [],
apiImpl: this,
),
);
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
out.contains("handler.executeSync("),
"handler.executeSync should be preserved when handler is not a parameter, got:\n{out}"
);
assert!(
!out.contains("handler.executeNormal("),
"no executeNormal should be present in input"
);
}
#[test]
fn fix_handler_executor_calls_rewrites_when_handler_is_parameter() {
let input = r#" @override
int crateServiceApiAppConnect({
required App that,
required String path,
required FutureOr<String> Function(String) handler,
}) {
return handler.executeSync(
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_App(that, serializer);
sse_encode_String(path, serializer);
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 1)!;
},
codec: SseCodec(
decodeSuccessData: sse_decode_i_32,
decodeErrorData: null,
),
constMeta: kConstMeta,
argValues: [that, path, handler],
apiImpl: this,
),
);
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
!out.contains("handler.executeSync("),
"handler.executeSync should be rewritten when handler is a parameter, got:\n{out}"
);
assert!(
out.contains(".executeSync();"),
"expected `.executeSync();` on the task in output (rewritten), got:\n{out}"
);
assert!(
!out.contains("handler("),
"handler callback should NOT be invoked directly (it's serialized), got:\n{out}"
);
}
#[test]
fn fix_handler_executor_calls_strips_trailing_comma_in_wrapper() {
let input = r#" Future<DartHandlerHandler> crateServiceApiDartHandlerHandlerNew({
required FutureOr<String> Function(String) handler,
}) {
return await handler.executeNormal(
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_DartFn_Inputs_String_Output_String_AnyhowException(
handler,
serializer,
);
},
codec: SseCodec(
decodeSuccessData: sse_decode_Auto_Owned_RustOpaque,
decodeErrorData: null,
),
constMeta: kConstMeta,
argValues: [handler],
apiImpl: this,
),
);
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
out.contains("return await") && out.contains("NormalTask("),
"NormalTask should follow return await, got:\n{out}"
);
assert!(
!out.contains("handler.executeNormal("),
"handler.executeNormal should be removed, got:\n{out}"
);
assert!(
out.contains(".executeNormal();"),
"should have .executeNormal(); on the task, got:\n{out}"
);
assert!(
!out.contains(")).executeNormal();"),
"should not have double closing parens, got:\n{out}"
);
}
#[test]
fn fix_handler_executor_calls_removes_invalid_async_from_class_declaration() {
let input = r#"class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi async {
RustLibApiImpl({
required super.handler,
required super.wire,
required super.generalizedFrbRustBinding,
required super.portManager,
});
@override
int crateServiceApiAppConnect({
required App that,
required String path,
required FutureOr<String> Function(String) handler,
}) async {
return await handler("test");
}
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
!out.contains("implements RustLibApi async {"),
"class declaration should not have `async` keyword, got:\n{out}"
);
assert!(
out.contains("implements RustLibApi {"),
"class declaration should have closing brace without `async`, got:\n{out}"
);
assert!(
out.contains(") async {") || out.contains("async {"),
"method signatures should still be able to use `async`, got:\n{out}"
);
}
#[test]
fn default_field_rewriter_is_identity_without_ir_metadata() {
let input = r#"class GenericOptions {
final String mode;
final bool enabled;
final PlatformInt64 retryCount;
const GenericOptions({
required this.mode,
required this.enabled,
required this.retryCount,
});
}
"#;
let out = make_struct_fields_with_defaults_optional(input);
assert_eq!(out, input, "post-FRB rewriter must not infer defaults by class name");
}
#[test]
fn prologue_replacement_helper_closes_before_init_opens() {
let replacement = frb_init_prologue_replacement("test_pkg", "test_mod", "test_stem");
let escaped_open = replacement.matches("{{").count();
let escaped_close = replacement.matches("}}").count();
assert_eq!(
escaped_open, escaped_close,
"escaped brace mismatch (must be in pairs for format escaping): {} {{ vs {} }}",
escaped_open, escaped_close
);
assert!(
replacement.contains("static Future<ExternalLibrary?> _alefResolveExternalLibrary()"),
"helper method signature must exist"
);
assert!(
replacement.contains("static Future<void> init({"),
"init method signature must exist"
);
let helper_ret_null = replacement.find("return null;").expect("helper must have return null");
let init_sig = replacement
.find("static Future<void> init({")
.expect("init sig must exist");
assert!(helper_ret_null < init_sig, "helper return must precede init signature");
}
#[test]
fn fix_handler_executor_strips_orphaned_paren_sync_task() {
let input = r#" @override
int crateServiceApiAppConnect({
required App that,
required String path,
required FutureOr<String> Function(String) handler,
}) {
return
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_App(that, serializer);
sse_encode_String(path, serializer);
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 1)!;
},
codec: SseCodec(
decodeSuccessData: sse_decode_i_32,
decodeErrorData: null,
),
constMeta: kConstMeta,
argValues: [that, path, handler],
apiImpl: this,
),
).executeSync();
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
out.contains(").executeSync();"),
"should have ).executeSync(); without orphaned paren, got:\n{out}"
);
let lint_check = out.matches(")).executeSync()").count();
assert_eq!(
lint_check, 0,
"should not have )).executeSync() (double paren), got:\n{out}"
);
}
#[test]
fn fix_handler_executor_strips_orphaned_paren_async_task() {
let input = r#" @override
Future<String> crateServiceApiAppHandlerCall({
required App that,
required String path,
required FutureOr<String> Function(String) handler,
}) async {
return await
AsyncTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_App(that, serializer);
sse_encode_String(path, serializer);
return pdeCallFfiAsync(generalizedFrbRustBinding, serializer, funcId: 42);
},
codec: SseCodec(
decodeSuccessData: sse_decode_String,
decodeErrorData: null,
),
constMeta: kConstMeta,
argValues: [that, path, handler],
apiImpl: this,
),
).executeNormal();
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
out.contains(").executeNormal();"),
"should have ).executeNormal(); without orphaned paren, got:\n{out}"
);
let lint_check = out.matches(")).executeNormal()").count();
assert_eq!(
lint_check, 0,
"should not have )).executeNormal() (double paren), got:\n{out}"
);
}
}