use cratestack_core::SourceSpan;
use cratestack_core::route_naming::to_snake_case;
use crate::diagnostics::{SchemaError, span_error};
pub(super) fn validate_no_build_setter_collision<'a>(
names: impl IntoIterator<Item = (&'a str, SourceSpan)>,
owner_kind: &str,
owner_name: &str,
) -> Result<(), SchemaError> {
let mut build_span: Option<SourceSpan> = None;
let mut set_build_span: Option<SourceSpan> = None;
for (name, span) in names {
match to_snake_case(name).as_str() {
"build" => build_span = Some(span),
"set_build" => set_build_span = Some(span),
_ => {}
}
}
if let (Some(_), Some(set_build_span)) = (build_span, set_build_span) {
return Err(span_error(
format!(
"{owner_kind} `{owner_name}` declares both a `build` field and a `set_build` \
field — the generated builder renames `build`'s own setter to `set_build` \
(so it doesn't collide with the terminal `build()` method), which then \
collides with the setter for the real `set_build` field (the Dart \
generator has the same clash as `setBuild`). Rename one of them.",
),
set_build_span,
));
}
Ok(())
}
pub(super) fn validate_no_add_setter_collision<'a>(
fields: impl IntoIterator<Item = (&'a str, SourceSpan, bool)> + Clone,
owner_kind: &str,
owner_name: &str,
) -> Result<(), SchemaError> {
for (list_name, _list_span, is_list) in fields.clone() {
if !is_list {
continue;
}
let reserved = format!("add_{}", to_snake_case(list_name));
for (other_name, other_span, _) in fields.clone() {
if to_snake_case(other_name) != reserved {
continue;
}
return Err(span_error(
format!(
"{owner_kind} `{owner_name}` declares list field `{list_name}` alongside a \
field named `{other_name}` — the generated append setter for `{list_name}` \
is `.add_{}(item)` in Rust and `.add{}(item)` in Dart (issue #661, derived \
mechanically from the field name, no singularization), which collides with \
the setter `{other_name}` already generates. Rename `{other_name}`.",
to_snake_case(list_name),
capitalize_first(&to_camel_ish(list_name)),
),
other_span,
));
}
}
Ok(())
}
fn to_camel_ish(name: &str) -> String {
let mut output = String::new();
let mut upper_next = false;
for ch in name.chars() {
if ch == '_' {
upper_next = true;
continue;
}
if upper_next {
output.extend(ch.to_uppercase());
upper_next = false;
} else {
output.push(ch);
}
}
output
}
fn capitalize_first(value: &str) -> String {
let mut chars = value.chars();
match chars.next() {
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
None => String::new(),
}
}