forge_lint/sol/info/
screaming_snake_case.rs

1use super::ScreamingSnakeCase;
2use crate::{
3    linter::{EarlyLintPass, LintContext},
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 name_str = name.as_str();
30            if name_str.len() < 2 || is_screaming_snake_case(name_str) {
31                return;
32            }
33
34            match mutability {
35                VarMut::Constant => ctx.emit(&SCREAMING_SNAKE_CASE_CONSTANT, name.span),
36                VarMut::Immutable => ctx.emit(&SCREAMING_SNAKE_CASE_IMMUTABLE, name.span),
37            }
38        }
39    }
40}
41
42/// Check if a string is SCREAMING_SNAKE_CASE. Numbers don't need to be preceded by an underscore.
43pub fn is_screaming_snake_case(s: &str) -> bool {
44    if s.len() <= 1 {
45        return true;
46    }
47
48    // Remove leading/trailing underscores like `heck` does
49    s.trim_matches('_') == format!("{}", heck::AsShoutySnakeCase(s)).as_str()
50}