forge_lint/sol/info/
screaming_snake_case.rs

1use super::ScreamingSnakeCase;
2use crate::{
3    linter::{EarlyLintPass, LintContext, Suggestion},
4    sol::{Severity, SolLint},
5};
6use solar::ast::{VarMut, VariableDefinition};
7
8declare_forge_lint!(
9    SCREAMING_SNAKE_CASE_CONSTANT,
10    Severity::Info,
11    "screaming-snake-case-const",
12    "constants should use SCREAMING_SNAKE_CASE"
13);
14
15declare_forge_lint!(
16    SCREAMING_SNAKE_CASE_IMMUTABLE,
17    Severity::Info,
18    "screaming-snake-case-immutable",
19    "immutables should use SCREAMING_SNAKE_CASE"
20);
21
22impl<'ast> EarlyLintPass<'ast> for ScreamingSnakeCase {
23    fn check_variable_definition(
24        &mut self,
25        ctx: &LintContext,
26        var: &'ast VariableDefinition<'ast>,
27    ) {
28        if let (Some(name), Some(mutability)) = (var.name, var.mutability)
29            && let Some(expected) = check_screaming_snake_case(name.as_str())
30        {
31            let suggestion = Suggestion::fix(
32                expected,
33                solar::interface::diagnostics::Applicability::MachineApplicable,
34            )
35            .with_desc("consider using");
36
37            match mutability {
38                VarMut::Constant => {
39                    ctx.emit_with_suggestion(&SCREAMING_SNAKE_CASE_CONSTANT, name.span, suggestion)
40                }
41                VarMut::Immutable => {
42                    ctx.emit_with_suggestion(&SCREAMING_SNAKE_CASE_IMMUTABLE, name.span, suggestion)
43                }
44            }
45        }
46    }
47}
48
49/// If the string `s` is not SCREAMING_SNAKE_CASE, returns a `Some(String)` with the suggested
50/// conversion. Otherwise, returns `None`.
51pub fn check_screaming_snake_case(s: &str) -> Option<String> {
52    if s.len() <= 1 {
53        return None;
54    }
55
56    // Handle leading/trailing underscores like `heck` does
57    let expected = format!(
58        "{prefix}{name}{suffix}",
59        prefix = if s.starts_with("_") { "_" } else { "" },
60        name = heck::AsShoutySnakeCase(s),
61        suffix = if s.ends_with("_") { "_" } else { "" }
62    );
63    if s == expected { None } else { Some(expected) }
64}