use ahash::AHashSet;
use super::escape_rust_keyword;
use crate::codegen::naming::to_node_name;
use crate::core::ir::FieldDef;
pub(super) struct PayloadField {
pub(super) field: FieldDef,
pub(super) binding_type: String,
pub(super) class_type: Option<String>,
}
fn field_ident(field: &FieldDef) -> String {
escape_rust_keyword(&field.name)
}
fn method_stem(field: &FieldDef) -> String {
let name = field.name.as_str();
if name.starts_with('_') && name.len() > 1 && name[1..].chars().all(|c| c.is_ascii_digit()) {
format!("field_{}", &name[1..])
} else {
escape_rust_keyword(name)
}
}
pub(super) fn reserved_payload_idents(
tag_ident: &str,
tag_setter_ident: &str,
fields: &[PayloadField],
) -> AHashSet<String> {
let mut reserved: AHashSet<String> = ["new".to_string(), "default".to_string()].into_iter().collect();
reserved.insert(tag_ident.to_string());
reserved.insert(tag_setter_ident.to_string());
for payload in fields {
let stem = method_stem(&payload.field);
reserved.insert(format!("set_{stem}"));
reserved.insert(stem);
}
reserved
}
pub(super) fn gen_payload_accessors(
fields: &[PayloadField],
flattened_only: &std::collections::BTreeSet<&str>,
reserved_idents: &AHashSet<String>,
) -> Vec<String> {
let mut lines = Vec::new();
for payload in fields {
if flattened_only.contains(payload.field.name.as_str()) {
continue;
}
lines.push(String::new());
lines.extend(indented(&gen_payload_getter(payload)));
lines.extend(indented(&gen_payload_setter(payload)));
if let Some(clear) = gen_payload_clear(payload, reserved_idents) {
lines.extend(indented(&clear));
}
}
lines
}
fn gen_payload_getter(payload: &PayloadField) -> String {
crate::backends::wasm::template_env::render(
"gen_payload_field_getter",
minijinja::context! {
js_name => to_node_name(&payload.field.name),
getter_ident => method_stem(&payload.field),
field_ident => field_ident(&payload.field),
binding_type => payload.binding_type,
},
)
}
fn gen_payload_setter(payload: &PayloadField) -> String {
let Some(class_type) = payload.class_type.as_deref() else {
return crate::backends::wasm::template_env::render(
"gen_payload_field_setter",
minijinja::context! {
js_name => to_node_name(&payload.field.name),
setter_ident => format!("set_{}", method_stem(&payload.field)),
field_ident => field_ident(&payload.field),
binding_type => payload.binding_type,
},
);
};
crate::backends::wasm::template_env::render(
"gen_class_field_setter",
minijinja::context! {
js_name_attr => format!(", js_name = \"{}\"", to_node_name(&payload.field.name)),
setter_ident => format!("set_{}", method_stem(&payload.field)),
field_ident => field_ident(&payload.field),
class_type => class_type,
optional => true,
},
)
}
fn gen_payload_clear(payload: &PayloadField, reserved_idents: &AHashSet<String>) -> Option<String> {
payload.class_type.as_ref()?;
let clear_ident = format!("clear_{}", method_stem(&payload.field));
if reserved_idents.contains(&clear_ident) {
return None;
}
Some(crate::backends::wasm::template_env::render(
"gen_class_field_clear",
minijinja::context! {
js_name => to_node_name(&clear_ident),
clear_ident => clear_ident,
field_ident => field_ident(&payload.field),
},
))
}
fn indented(block: &str) -> Vec<String> {
block
.trim_end()
.lines()
.map(|line| {
if line.is_empty() {
String::new()
} else {
crate::codegen::template_env::render(
"builders/indented_line.jinja",
minijinja::context! { line => line },
)
.trim_end()
.to_string()
}
})
.collect()
}