1use crate::{
7 linter::{LintContext, Suggestion},
8 sol::SolLint,
9};
10use solar::interface::{Span, diagnostics::Applicability};
11
12pub fn check_pascal_case(s: &str) -> Option<String> {
14 suggest(s, preserve_underscores(s, heck::AsPascalCase(s).to_string()))
15}
16
17pub fn check_screaming_snake_case(s: &str) -> Option<String> {
19 suggest(s, preserve_underscores(s, heck::AsShoutySnakeCase(s).to_string()))
20}
21
22pub fn check_mixed_case(s: &str) -> Option<String> {
25 suggest(s, preserve_underscores(s, heck::AsLowerCamelCase(s).to_string()))
26}
27
28pub fn has_acronym_exception(s: &str, patterns: &[String], pre_is_valid: fn(&str) -> bool) -> bool {
32 patterns.iter().any(|pattern| {
33 let Some((pre, post)) = s.split_once(pattern.as_str()) else { return false };
34 let pre = pre.strip_prefix('_').unwrap_or(pre);
35 let post = post.trim_start_matches(|c: char| c.is_numeric());
36 let post = post.strip_suffix('_').unwrap_or(post);
37 pre_is_valid(pre) && post == heck::AsUpperCamelCase(post).to_string()
38 })
39}
40
41pub fn emit_rename(ctx: &LintContext, lint: &'static SolLint, span: Span, expected: String) {
43 let suggestion =
44 Suggestion::fix(expected, Applicability::MachineApplicable).with_desc("consider using");
45 ctx.emit_with_suggestion(lint, span, suggestion);
46}
47
48fn suggest(s: &str, expected: String) -> Option<String> {
50 (s.len() > 1 && s != expected).then_some(expected)
51}
52
53fn preserve_underscores(s: &str, body: String) -> String {
54 let prefix = if s.starts_with('_') { "_" } else { "" };
55 let suffix = if s.ends_with('_') { "_" } else { "" };
56 format!("{prefix}{body}{suffix}")
57}