Foundry Lint 规则详解:immutable 变量必须使用 `SCREAMING_SNAKE_CASE`(screaming-snake-case-immutable)
2026/9/16 17:53:49 网站建设 项目流程

Foundry Lint 规则详解:immutable 变量必须使用SCREAMING_SNAKE_CASE(screaming-snake-case-immutable)

【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry

本篇文章围绕 Foundry 自带的 Solidity Lint 规则screaming-snake-case-immutable展开,深入讲解它检查什么、为什么推荐 immutable 变量使用全大写蛇形命名,以及这条规则在源码中的判定逻辑与测试验证方式。读完本文,你将掌握该规则的完整语义(含单字符豁免与下划线保留等边界行为)、如何用forge lint复现并修复告警,以及如何在真实项目中权衡重命名与兼容性。

规则概览

screaming-snake-case-immutable是 Foundry Lint 体系(位于 crates/lint 目录)中的一条命名规范类规则,元信息定义在 规则文档 中:

  • 严重级别(Severity)Info
  • 规则 IDscreaming-snake-case-immutable
  • 作用对象:声明为immutable的状态变量

这条规则与同族的screaming-snake-case-const(针对constant变量,见 对应文档)共享同一套底层检查逻辑,只是对immutableconstant分别发出不同的告警 ID。

它检查什么

该规则报告所有标识符偏离SCREAMING_SNAKE_CASE命名约定的immutable状态变量,并带有两条明确的豁免边界:

  1. 单字符名称不检查:单个字符的变量名(如address immutable X;)不会触发告警。这是因为单字符命名在循环索引、临时变量等场景中极为常见,且无法形成有意义的全大写形态。
  2. 首尾下划线保留:命名转换时会保留变量名最前面和最后面的下划线。也就是说_OWNEROWNER_这样的写法仍被视为合规,内部下划线则由转换逻辑统一处理。

示例如下(来自 规则文档):

// 不合规:小驼峰 / 大驼峰 address immutable owner; address immutable Owner;

应改为:

// 合规:SCREAMING_SNAKE_CASE address immutable OWNER;

为什么推荐这种命名

规则文档给出了两条核心理由:

  1. 视觉上与constant对齐:Foundry 推荐 immutable 变量使用SCREAMING_SNAKE_CASE,使它们在代码中与constant常量在视觉上保持一致。由于 immutable 与 constant 都在部署后不可再修改(只是时机不同——constant 在编译期内联,immutable 在构造时赋值),用同一套命名风格能让读者一眼识别出"这些值不会变"。
  2. 在调用点与可变状态区分开:状态变量(mutable)遵循 Solidity 风格指南的mixedCase(小驼峰),而 immutable/constant 使用全大写蛇形,这样在函数体内引用这些变量时,无需追溯声明即可从命名上区分可变与不可变语义,显著降低阅读成本。

另外,规则文档也给出了一条重要的例外原则:如果项目已经有一套区分 immutable 与 constant 的既定命名惯例,或者变量存在已对外公开的 getter 名称,而重命名会造成破坏性影响(如影响 ABI 中 getter 的 selector、破坏其他合约的引用),则应保留原有命名。这是一条Info级别的风格建议,而非强制约束。

源码实现:判定逻辑与自动修复

规则的实际实现位于 crates/lint/src/sol/info/screaming_snake_case.rs,它通过 solar 的 AST 遍历对每个变量定义执行检查:

impl<'ast> EarlyLintPass<'ast> for ScreamingSnakeCase { fn check_variable_definition( &mut self, ctx: &LintContext, var: &'ast VariableDefinition<'ast>, ) { if let (Some(name), Some(mutability)) = (var.name, var.mutability) && let Some(expected) = check_screaming_snake_case(name.as_str()) { let lint = match mutability { VarMut::Constant => &SCREAMING_SNAKE_CASE_CONSTANT, VarMut::Immutable => &SCREAMING_SNAKE_CASE_IMMUTABLE, }; emit_rename(ctx, lint, name.span, expected); } } }

从源码结构可以看出几个关键设计:

  • 单一 Pass 双规则ScreamingSnakeCase这一个 Lint Pass 同时注册了两条告警SCREAMING_SNAKE_CASE_CONSTANTSCREAMING_SNAKE_CASE_IMMUTABLE,通过VarMut枚举区分ConstantImmutable并分发到不同的规则 ID(注册关系见 crates/lint/src/sol/info/mod.rs)。
  • 自动修复emit_rename会生成一条MachineApplicable级别的建议修复(见 crates/lint/src/sol/naming.rs),提示语为consider using: <期望名称>,也就是说forge lint --fix这类机器可应用修复可以直接把不合规的命名改写为正确形式。
  • 转换算法:底层使用heck库的AsShoutySnakeCase把标识符转换为全大写蛇形(见 naming.rs),例如screamingSnakeCaseSCREAMING_SNAKE_CASEScreamingSnakeCase0SCREAMING_SNAKE_CASE0

单字符豁免与下划线保留的实现

边界行为在 naming.rs 中体现得十分明确:

/// Single-character names are exempt from every convention. fn suggest(s: &str, expected: String) -> Option<String> { (s.len() > 1 && s != expected).then_some(expected) } fn preserve_underscores(s: &str, body: String) -> String { let prefix = if s.starts_with('_') { "_" } else { "" }; let suffix = if s.ends_with('_') { "_" } else { "" }; format!("{prefix}{body}{suffix}") }
  • suggests.len() > 1的判断正是"单字符名称不检查"的实现:长度不大于 1 时直接返回None,不产生告警。
  • preserve_underscores负责"首尾下划线保留":转换前先记录首/尾下划线,转换完成后重新拼回去。例如_SCREAMING_SNAKE_CASE_1会被判定为合规(首下划线保留、主体已合规),而SCREAMING_snake_case_0会被建议改为SCREAMING_SNAKE_CASE_0(见下文测试用例)。

测试用例验证

规则在 crates/lint/testdata/ScreamingSnakeCase.sol 中配有完整的测试夹具(fixture),通过行内注释//~NOTE: immutable name is notSCREAMING_SNAKE_CASE`` 标记期望告警的位置:

//@compile-flags: --severity info ... uint256 immutable _SCREAMING_SNAKE_CASE_1 = 0; // 合规:首下划线保留 uint256 immutable SCREAMING_SNAKE_CASE_1 = 0; // 合规 uint256 immutable SCREAMINGSNAKECASE0 = 0; // 合规:全大写连续字母允许 uint256 immutable SCREAMINGSNAKECASE_ = 0; // 合规:尾下划线保留 uint256 immutable screamingSnakeCase0 = 0; // ~NOTE: 不合规 uint256 immutable screaming_snake_case0 = 0; // ~NOTE: 不合规 uint256 immutable ScreamingSnakeCase0 = 0; // ~NOTE: 不合规 uint256 immutable SCREAMING_snake_case_0 = 0; // ~NOTE: 不合规

对应的期望输出在 ScreamingSnakeCase.stderr 中,展示了实际的告警渲染效果与自动修复建议,例如:

note[screaming-snake-case-immutable]: immutable name is not `SCREAMING_SNAKE_CASE` LL │ uint256 immutable SCREAMING_snake_case_0 = 0; │ ━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE_0`

这些测试用例同时验证了文档中提到的所有边界条件:小驼峰、全小写下划线、大驼峰、混合大小写均会被报告;而全大写(无论是否含数字、下划线)以及带首尾下划线的写法均被放行。注意测试夹具通过//@compile-flags: --severity info显式将严重级别阈值降到Info,因为该规则默认级别为Info,需要相应配置才能在输出中展示。

在项目中启用与修复

由于该规则属于Info级别,在默认严重级别配置下可能不会在forge lint输出中展示,需要将级别阈值调低(例如测试中使用的--severity info)。典型的使用方式:

# 以 Info 级别运行全部 lint,输出包含本规则 forge lint --severity info # 只运行某一条规则(通过规则 ID 过滤) forge lint --severity info --path path/to/Contract.sol

也可以参考 crates/config/src/lint.rs 中定义的 lint 配置结构,在foundry.toml中调整规则级别或按路径忽略告警。修复时既可以直接手动改写命名,也可以依赖机器可应用的自动修复建议一键替换为SCREAMING_SNAKE_CASE形式。

小结

screaming-snake-case-immutable是 Foundry 代码质量体系中一条轻量但实用的命名规范规则:它要求immutable状态变量采用与constant一致的SCREAMING_SNAKE_CASE命名,从而在调用点快速区分不可变与可变状态。规则实现上通过共享的命名检查助手(crates/lint/src/sol/naming.rs)同时服务 constant 与 immutable 两类变量,并内置了"单字符豁免"与"首尾下划线保留"两个贴心边界;测试夹具(ScreamingSnakeCase.sol)则完整覆盖了这些语义。如果你的项目已有成熟且稳定的 immutable 命名惯例或公开 getter,规则文档也明确建议保留既有命名,避免重命名带来的兼容性破坏——这正是这条Info级别规则"建议而非强制"的定位所在。

【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询