forge_lint/sol/info/
mixed_case.rs

1use super::{MixedCaseFunction, MixedCaseVariable};
2use crate::{
3    linter::{EarlyLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use solar_ast::{ItemFunction, VariableDefinition};
7
8declare_forge_lint!(
9    MIXED_CASE_FUNCTION,
10    Severity::Info,
11    "mixed-case-function",
12    "function names should use mixedCase"
13);
14
15impl<'ast> EarlyLintPass<'ast> for MixedCaseFunction {
16    fn check_item_function(&mut self, ctx: &LintContext<'_>, func: &'ast ItemFunction<'ast>) {
17        if let Some(name) = func.header.name
18            && !is_mixed_case(name.as_str(), true)
19        {
20            ctx.emit(&MIXED_CASE_FUNCTION, name.span);
21        }
22    }
23}
24
25declare_forge_lint!(
26    MIXED_CASE_VARIABLE,
27    Severity::Info,
28    "mixed-case-variable",
29    "mutable variables should use mixedCase"
30);
31
32impl<'ast> EarlyLintPass<'ast> for MixedCaseVariable {
33    fn check_variable_definition(
34        &mut self,
35        ctx: &LintContext<'_>,
36        var: &'ast VariableDefinition<'ast>,
37    ) {
38        if var.mutability.is_none()
39            && let Some(name) = var.name
40            && !is_mixed_case(name.as_str(), false)
41        {
42            ctx.emit(&MIXED_CASE_VARIABLE, name.span);
43        }
44    }
45}
46
47/// Check if a string is mixedCase
48///
49/// To avoid false positives like `fn increment()` or `uint256 counter`,
50/// lowercase strings are treated as mixedCase.
51pub fn is_mixed_case(s: &str, is_fn: bool) -> bool {
52    if s.len() <= 1 {
53        return true;
54    }
55
56    // Remove leading/trailing underscores like `heck` does
57    if s.trim_matches('_') == format!("{}", heck::AsLowerCamelCase(s)).as_str() {
58        return true;
59    }
60
61    // Ignore `fn test*`, `fn invariant_*`, and `fn statefulFuzz*` patterns, as they usually contain
62    // (allowed) underscores.
63    is_fn && (s.starts_with("test") || s.starts_with("invariant_") || s.starts_with("statefulFuzz"))
64}