mod module_facade;
mod trait_interfaces;
pub use trait_interfaces::format_method_signature;
use std::collections::BTreeSet;
use std::path::Path;
use crate::backends::kotlin::{
emit_enum_pub, emit_error_type_pub, emit_jni_bridge_object, emit_jni_client_class,
emit_type_pub_with_defaults_sealed_and_constructible,
};
use crate::backends::kotlin_android::naming::kotlin_package;
use crate::backends::kotlin_android::template_env;
use crate::core::backend::GeneratedFile;
use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::ApiSurface;
use crate::core::jni::bridge_class_name;
pub fn emit(api: &ApiSurface, config: &ResolvedCrateConfig, kotlin_source_dir: &Path) -> Vec<GeneratedFile> {
let package = kotlin_package(config);
let mut files = Vec::new();
let kotlin_android_excluded_function_names: std::collections::HashSet<&str> = config
.kotlin_android
.as_ref()
.map(|c| c.exclude_functions.iter().map(String::as_str).collect())
.unwrap_or_default();
let mut effective_excluded_types: std::collections::HashSet<String> = config
.kotlin_android
.as_ref()
.map(|c| c.exclude_types.iter().cloned().collect())
.unwrap_or_default();
for bridge in &config.trait_bridges {
if bridge.exclude_languages.iter().any(|l| l == "kotlin_android") {
if let Some(alias) = &bridge.type_alias {
effective_excluded_types.insert(alias.clone());
}
}
if let Some(name) = bridge.param_name.as_deref() {
if kotlin_android_excluded_function_names.contains(name) {
if let Some(alias) = &bridge.type_alias {
effective_excluded_types.insert(alias.clone());
}
}
}
}
for (name, path) in &config.opaque_types {
if path.contains('<') {
effective_excluded_types.insert(name.clone());
}
}
let enum_defaults: std::collections::HashMap<String, String> = api
.enums
.iter()
.filter(|en| en.serde_tag.is_none() && !en.serde_untagged && en.variants.iter().all(|v| v.fields.is_empty()))
.map(|en| {
let default_variant = en
.variants
.iter()
.find(|v| v.is_default)
.map(|v| v.name.clone())
.unwrap_or_default();
(en.name.clone(), default_variant)
})
.collect();
let sealed_class_names: std::collections::HashSet<String> = api
.enums
.iter()
.filter(|en| en.serde_tag.is_some() || en.serde_untagged)
.map(|en| en.name.clone())
.collect();
let default_constructible_types: std::collections::HashSet<String> = api
.types
.iter()
.filter(|t| !t.is_trait && !t.is_opaque && t.has_default)
.map(|t| t.name.clone())
.collect();
let mut bridge_file = emit_jni_bridge_object(api, config);
bridge_file.path = kotlin_source_dir.join(bridge_file.path.file_name().expect("bridge file must have a filename"));
files.push(bridge_file);
let bridge = bridge_class_name(&config.name);
let exception_class = format!("{bridge}Exception");
let exception_content = format!(
"// Generated by alef. Do not edit by hand.\n\n\
package {package}\n\n\
class {exception_class}(message: String?, cause: Throwable?) : RuntimeException(message, cause) {{\n\
{}\n\
}}\n",
" constructor(message: String?) : this(message, null)"
);
files.push(GeneratedFile {
path: kotlin_source_dir.join(format!("{exception_class}.kt")),
content: exception_content,
generated_header: false,
});
if let Some(mut client_file) = emit_jni_client_class(api, config, Some(&package)) {
client_file.path = kotlin_source_dir.join("DefaultClient.kt");
files.push(client_file);
}
for ty in &api.types {
if ty.is_opaque || ty.is_trait || ty.binding_excluded {
continue;
}
if effective_excluded_types.contains(&ty.name) {
continue;
}
let mut imports: BTreeSet<String> = BTreeSet::new();
let mut body = String::new();
let needs_field_filter = ty
.fields
.iter()
.any(|f| effective_excluded_types.iter().any(|name| f.ty.references_named(name)));
if needs_field_filter {
let mut filtered = ty.clone();
filtered
.fields
.retain(|f| !effective_excluded_types.iter().any(|name| f.ty.references_named(name)));
emit_type_pub_with_defaults_sealed_and_constructible(
&filtered,
&mut body,
&mut imports,
&enum_defaults,
&sealed_class_names,
&default_constructible_types,
);
} else {
emit_type_pub_with_defaults_sealed_and_constructible(
ty,
&mut body,
&mut imports,
&enum_defaults,
&sealed_class_names,
&default_constructible_types,
);
}
if body.trim().is_empty() {
continue;
}
let content = assemble_kt_content(&package, &imports, &body);
files.push(GeneratedFile {
path: kotlin_source_dir.join(format!("{}.kt", ty.name)),
content,
generated_header: false,
});
}
for en in &api.enums {
if en.binding_excluded {
continue;
}
let mut body = String::new();
emit_enum_pub(en, &mut body, &package);
if body.trim().is_empty() {
continue;
}
let content = assemble_kt_content(&package, &BTreeSet::new(), &body);
files.push(GeneratedFile {
path: kotlin_source_dir.join(format!("{}.kt", en.name)),
content,
generated_header: false,
});
}
for error in &api.errors {
let mut imports: BTreeSet<String> = BTreeSet::new();
let mut body = String::new();
emit_error_type_pub(error, &mut body, &mut imports);
if body.trim().is_empty() {
continue;
}
let content = assemble_kt_content(&package, &imports, &body);
files.push(GeneratedFile {
path: kotlin_source_dir.join(format!("{}.kt", error.name)),
content,
generated_header: false,
});
}
trait_interfaces::emit_trait_interfaces(api, config, kotlin_source_dir, &package, &mut files);
module_facade::emit_module_kt(api, config, kotlin_source_dir, &package, &mut files);
files
}
pub(super) fn assemble_kt_content(package: &str, imports: &BTreeSet<String>, body: &str) -> String {
let suppressions = vec![
"ktlint:standard:trailing-comma-on-call-site",
"ktlint:standard:trailing-comma-on-declaration-site",
"ktlint:standard:spacing-between-declarations-with-comments",
"ktlint:standard:spacing-between-declarations-with-annotations",
"ktlint:standard:when-entry-bracing",
"ktlint:standard:blank-line-between-when-conditions",
"ktlint:standard:blank-line-before-declaration",
"ktlint:standard:chain-method-continuation",
"ktlint:standard:annotation",
"ktlint:standard:max-line-length",
"ktlint:standard:no-semi",
"ktlint:standard:statement-wrapping",
"MaxLineLength",
"TooManyFunctions",
"FunctionParameterNaming",
"LongParameterList",
"CyclomaticComplexMethod",
"LongMethod",
"MagicNumber",
"NestedBlockDepth",
"ReturnCount",
];
let imports = imports.iter().cloned().collect::<Vec<_>>();
template_env::render(
"kt_file.jinja",
minijinja::context! {
package => package,
imports => imports,
suppressions => suppressions,
body => body,
},
)
}