use yog_ids::script_problem;
use super::Resolver;
use crate::ast::Name;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Case {
Pascal,
Camel,
Private,
Interface,
TypeParam,
Any,
}
const DISCARD: &str = "_";
const INTERFACE_MARK: char = 'I';
const TYPE_PARAM_MARK: char = 'T';
const SEPARATOR: char = '_';
impl Case {
fn fits(self, name: &str) -> bool {
let mut chars = name.chars();
let (Some(first), second) = (chars.next(), chars.next()) else {
return true;
};
if self == Case::Private {
return first == SEPARATOR && Case::Camel.fits(&name[SEPARATOR.len_utf8()..]);
}
if name.contains(SEPARATOR) && self != Case::Any {
return false;
}
match self {
Case::Pascal => first.is_uppercase(),
Case::Camel | Case::Private => first.is_lowercase(),
Case::Interface => first == INTERFACE_MARK && second.is_some_and(char::is_uppercase),
Case::TypeParam => first == TYPE_PARAM_MARK && second.is_none_or(char::is_uppercase),
Case::Any => true,
}
}
fn describe(self) -> &'static str {
match self {
Case::Pascal => "PascalCase (`ChargeLevel`)",
Case::Camel => "camelCase (`chargeLevel`)",
Case::Private => "`_` and camelCase (`_chargeLevel`): a field that isn't public",
Case::Interface => "`I` and PascalCase (`IChargeable`)",
Case::TypeParam => "`T` or `T` and PascalCase (`TItem`)",
Case::Any => "any case",
}
}
}
impl Resolver<'_, '_> {
pub(super) fn check_case(&mut self, name: &Name, case: Case) {
let written = name
.text
.split(crate::macros::HYGIENE_MARK)
.next()
.unwrap_or_default();
if written == DISCARD || case.fits(written) {
return;
}
self.report.warning(
script_problem::NAMING,
name.span,
format!("`{}` should be written in {}", name.text, case.describe()),
);
}
}