Scala语言检查工具选型与落地建议
1. 工具选型表
| 工具 | 简介 | 优先级 | 误报率 | 告警抑制(屏蔽)方式 | 适用场景 |
|---|---|---|---|---|---|
| Scalafmt | 格式化:Scalameta 解析,HOCON 配置 | 必选 | 无 | 工具配置 | 本地增量、本地全量、PR增量、PR全量 |
| Scalafix | 代码质量:SemanticDB 语义规则 | 必选 | 低 | 行级注释、块级注释、工具配置 | 本地全量、PR全量、主干全量 |
| pre-commit | 调度框架:声明式 YAML,依赖隔离 | 推荐 | 无 | 工具配置 | 本地增量、PR全量 |
| Spotless | 格式化:多引擎调度,幂等保护 | 推荐 | 无 | 块级注释、工具配置 | 本地增量、本地全量、PR增量、PR全量 |
| Gitleaks | 密钥检测:正则+熵分析,递归解码 | 推荐 | 低 | 行级注释、工具配置 | 本地增量、PR增量、PR全量、主干全量 |
| Codespell | 拼写检查:Wikipedia 常见错误字典 | 推荐 | 低 | 行级注释、工具配置 | 本地增量、PR增量、PR全量 |
2. 主流社区参考
2.1 Apache Spark
Spark 是“多语言项目中 pre-commit 的精准使用”样本——pre-commit 仅配置 Python 钩子(通过 repo: local),Scala/Java 通过 sbt/Maven 插件处理。这避免了 pre-commit 在多语言项目中过度配置的复杂性。
- 仓库地址:https://github.com/apache/spark (⭐ 43.7k)
- 使用工具:Scalastyle(Scala 风格检查)、sbt-scalafmt(Scala 格式化)、Ruff(Python)、pre-commit(仅 Python 钩子)
- 工作流:多语言项目(Scala/Java/Python/R/SQL);主构建系统为 Maven;pre-commit 仅配置 Python 钩子,通过 dev/lint-python 与 dev/reformat-python 脚本执行;CI 通过 GitHub Actions
- 关键配置:scalastyle-config.xml、.pre-commit-config.yaml(仅 Python 钩子)、pom.xml、pyproject.toml
- 借鉴价值:多语言项目中 pre-commit 应精准使用(仅配置 pre-commit 擅长的语言),其他语言通过构建工具插件处理;scalastyle-config.xml 是 Scala 生态老牌风格检查工具
2.2 Apache Pekko
Pekko(Akka 的 Apache 基金会分支)是“Scala + Java 双语言项目工具链”的完整范本——scalafmt 处理 Scala、sbt-java-formatter 处理 Java、scalafix 进行语义化重写。Scala Steward 是 Scala 生态独有的自动化依赖管理机器人。
- 仓库地址:https://github.com/apache/incubator-pekko (⭐ 1.6k)
- 使用工具:Scalafmt(格式化)、Scalafix(语义化重写/lint)、sbt-java-formatter(Java 格式化)、Scala Steward(自动化依赖更新)
- 工作流:sbt 构建(多模块);Scala + Java 双语言;.scalafix.conf 配置语义化检查规则;Scala Steward 自动提交依赖更新 PR;多 JDK 测试;CI 通过 GitHub Actions
- 关键配置:.scalafmt.conf、.scalafix.conf、.sbt-java-formatter.conf、.scala-steward.conf、.sbtopts、.jvmopts-ci
- 借鉴价值:Scala + Java 双语言项目应分别用 scalafmt 和 sbt-java-formatter 处理;Scala Steward 是 Scala 生态独有的依赖管理机器人(等价于 Dependabot 但更懂 sbt);.jvmopts-ci 与本地配置分离是大型 JVM 项目的常见做法
2.3 Play Framework
Play 展示了“成熟 Scala 项目的极简配置”——仅 .scalafmt.conf 一个核心配置文件。这说明对历史悠久的 Web 框架,scalafmt 已足够覆盖格式化需求,scalafix 是可选增强。
- 仓库地址:https://github.com/playframework/playframework (⭐ 12.6k)
- 使用工具:Scalafmt、sbt
- 工作流:sbt 构建(build.sbt + common.sbt 公共配置);多子项目;CI 通过 GitHub Actions;.git-blame-ignore-revs 屏蔽格式化提交
- 关键配置:.scalafmt.conf、build.sbt、common.sbt、.sbtopts、.git-blame-ignore-revs
- 借鉴价值:成熟 Scala 项目一个 .scalafmt.conf 即可;scalafix 是可选增强而非必选;.git-blame-ignore-revs 屏蔽格式化提交是引入 formatter 后的标准实践
2.4 Cats Effect
Cats Effect 是 Typelevel 生态的“标准实践样本”——prePR 脚本一键完成 scalafmt + scalafix,避免开发者遗忘。sbt-typelevel 是 Typelevel 生态的“一站式”插件,统一构建、发布、CI 配置。
- 仓库地址:https://github.com/typelevel/cats-effect (⭐ 2.2k)
- 使用工具:Scalafmt 3.11.0、Scalafix、sbt-typelevel(Typelevel 生态构建插件)、Mergify(PR 自动合并)、Nix(可复现开发环境)
- 工作流:prePR 脚本执行 scalafmt + scalafix 后再提交 PR;sbt-typelevel 插件统一构建和 CI 配置;跨平台编译(JVM、Scala.js、Scala Native);Mergify 自动合并符合条件的 PR;CI 通过 GitHub Actions
- 关键配置:.scalafmt.conf、.scalafix.conf、build.sbt、.mergify.yml、.jvmopts、flake.nix
- 借鉴价值:prePR 脚本一键化避免开发者遗忘格式化和 lint;sbt-typelevel 是 Typelevel 生态的一站式插件;Nix flake 提供可复现开发环境是函数式编程社区的偏好
2.5 Scala 3 编译器
Scala 3 编译器仓库展示了“语言自身的工具链自举”——类型检查即编译器自身。community-build 对下游 Scala 库进行兼容性测试是语言级项目的独特实践。
- 仓库地址:https://github.com/scala/scala3 (⭐ 6.3k)
- 使用工具:sbt、Scalafmt、GitHub Actions、community-build(下游兼容性测试)
- 工作流:sbt 构建;类型检查即编译过程;大量测试套件(tests/pos、tests/neg、tests/run);community-build/ 对下游库进行兼容性测试;CI workflow 近期被拆分为多个文件
- 关键配置:build.sbt、.github/workflows/、.vscode-template/
- 借鉴价值:community-build 对下游库进行回归测试是语言级项目的独特实践;大型项目的 CI 配置会随时间膨胀,需要定期重构
3. 工程配置建议
Scala 生态主流项目(Play Framework、Cats Effect)以 .scalafmt.conf + .scalafix.conf 为核心配置,Apache Pekko 展示了 Scala + Java 双语言项目的完整工具链。工程配置以 sbt 插件(sbt-scalafmt、sbt-scalafix)为主门禁,Gradle/Maven 项目通过 Spotless 集成 Scalafmt;pre-commit 仅补充密钥检测和拼写检查等语言无关检查。
3.1 工程配置文件汇总
| 配置文件 | 配置内容/作用 | 使用场景 | 维护建议 |
|---|---|---|---|
.scalafmt.conf |
Scalafmt 格式规则(含版本号、列宽、对齐等) | 本地构建、PR 门禁、全量检查 | 由仓库统一维护;新项目可基于 preset = default 起步 |
.scalafix.conf |
Scalafix 规则集(启用的规则列表) | 本地构建、PR 门禁、全量检查 | 按规则信噪比分层,先启用语法规则再逐步加语义规则 |
project/plugins.sbt |
接入 sbt-scalafmt 和 sbt-scalafix 插件 | 本地构建、PR 门禁、全量检查 | 插件版本在 project/plugins.sbt 中统一管理;规则升级单独发 PR |
build.sbt |
编译选项、SemanticDB 启用、scalafixOnCompile 等 | 本地构建、PR 门禁、全量检查 | 语义规则需 semanticdbEnabled := true,CI 中不建议开启 scalafixOnCompile |
build.gradle.kts |
Gradle 项目通过 Spotless 集成 Scalafmt | Gradle 项目的本地构建、PR 门禁 | 仅适用于 Gradle 项目;sbt 项目不需要此文件 |
.pre-commit-config.yaml |
官方基础 hook、密钥检测、拼写检查(可选) | 本地提交前、PR 轻量门禁 | Scala 项目可选配置,不作为强制门禁 |
.gitignore / 工具 ignore 文件 |
排除构建产物、依赖目录、生成代码 | 本地、PR、全量检查 | 生成代码和 target 目录优先集中排除 |
.github/workflows/* |
PR 门禁、主干全量、夜间任务 | PR、主干、发布前、夜间 | CI 命令应尽量复用本地命令,避免本地和 CI 两套规则漂移 |
3.2 .scalafmt.conf
Scalafmt 格式规则配置,含版本号、列宽、对齐等。
# .scalafmt.conf
version = "3.11.1"
runner.dialect = scala213
maxColumn = 120
preset = default
rewrite.rules = [SortImports, RedundantParens, SortModifiers]
docstrings.style = Asterisk
3.3 .scalafix.conf
Scalafix 规则集配置,启用语法和语义检查规则。
# .scalafix.conf
rules = [
DisableSyntax,
OrganizeImports,
RedundantSyntax,
NoAutoTupling,
ProcedureSyntax
]
语义规则(
ExplicitResultTypes、RemoveUnused)需在build.sbt中启用semanticdbEnabled := true,建议在团队熟悉语法规则后再逐步引入。
3.4 project/plugins.sbt
接入 sbt-scalafmt 和 sbt-scalafix 插件,使 sbt 构建命令可用。
// project/plugins.sbt:Scalafmt + Scalafix
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.1")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.7")
3.5 build.sbt
启用 SemanticDB(Scalafix 语义规则前置条件),配置编译选项。
// build.sbt:启用 SemanticDB(Scalafix 语义规则前置条件)
ThisBuild / semanticdbEnabled := true
ThisBuild / semanticdbVersion := scalafixSemanticdb.revision
ThisBuild / scalafixOnCompile := false // CI 中不建议编译时自动运行
3.6 build.gradle.kts
Gradle 项目通过 Spotless 集成 Scalafmt,仅适用于使用 Gradle 构建的 Scala 项目。
// build.gradle.kts:Spotless + Scalafmt
plugins {
id("com.diffplug.spotless") version "7.0.2"
scala
}
spotless {
scala {
scalafmt()
}
}
3.7 .pre-commit-config.yaml
Scala 项目主门禁应放在 sbt 生命周期中,不建议把 Scalafmt、Scalafix 直接作为默认 pre-commit 阻断项。pre-commit 在 Scala 仓库中主要承担工程级轻量检查:官方基础 hook、密钥检测和拼写检查。
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace # 移除行尾空白字符
- id: end-of-file-fixer # 确保文件以换行符结尾
- id: check-yaml # 校验 YAML 文件语法
- id: check-json # 校验 JSON 文件语法
- id: check-xml # 校验 XML 文件语法
- id: check-merge-conflict # 检查未解决的合并冲突标记
- id: check-added-large-files # 拦截超大文件提交(默认 500KB)
# args: ["--maxkb", "500"] # 支持自定义修改
- id: detect-private-key # 检测私钥/敏感信息泄露
# ===== 以下为可选工具,按需启用 =====
# - repo: https://github.com/gitleaks/gitleaks
# rev: v8.24.3
# hooks:
# - id: gitleaks # 密钥泄露检测
# - repo: https://github.com/codespell-project/codespell
# rev: v2.4.1
# hooks:
# - id: codespell # 拼写检查
# Scala 格式化(Gradle/Maven 项目通过 Spotless 集成 Scalafmt)
# - repo: local
# hooks:
# - id: spotless-scala-apply # Scala 格式化(修复模式,通过 Spotless)
# name: Spotless Scala format
# entry: ./gradlew spotlessApply
# language: system
# files: \.scala$
# pass_filenames: false
# ===== Scala 必选工具:默认启用 =====
- repo: local
hooks:
- id: scalafmt # Scala 代码格式化(修复模式)
name: Scalafmt
entry: sbt scalafmt
language: system
files: \.scala$
pass_filenames: false
- id: scalafix # Scala 代码质量检查(修复模式)
name: Scalafix
entry: sbt scalafix
language: system
files: \.scala$
pass_filenames: false
Scala 语言级检查(Scalafmt、Scalafix)不适合放入 pre-commit,应在本地手动命令或 PR 门禁中执行,详见后续章节。
4. 本地开发检查场景
本地开发检查使用上文的工程配置,目标是在提交前尽快发现可自动修复或低成本问题。Scala 项目首选入口是 sbt 构建命令,pre-commit 仅补充密钥检测和拼写检查。
4.1 本地检查流程
| 步骤 | 开发人员操作 | 依赖的工程配置 | 失败后怎么处理 |
|---|---|---|---|
| 安装工具 | 安装 JDK、sbt、pre-commit 和项目依赖 | project/plugins.sbt、build.sbt |
安装失败先确认 JDK 版本和 sbt 镜像源 |
| 自动修复 | 运行 sbt scalafmt 或 sbt scalafix |
.scalafmt.conf、.scalafix.conf |
自动修复后重新查看 diff,避免格式化混入无关文件 |
| 提交前检查 | 执行 pre-commit run --all-files(可选)或提交时自动触发 |
.pre-commit-config.yaml |
根据 hook 名称定位失败工具,先修复问题 |
| 语言级检查 | 运行编译、测试、scalafmtCheck、scalafix --check | 规则集配置、构建文件 | 本地无法复现时先同步依赖和 CI 环境版本 |
4.2 本地拦截与处理
| 拦截场景 | 常见原因 | 处理方式 | 是否可屏蔽 |
|---|---|---|---|
| 格式检查失败 | 未运行 Scalafmt、编辑器格式规则不一致 | 运行 sbt scalafmt 并提交修复后的文件 |
通常不屏蔽,生成文件用 project.excludeFilters 排除 |
| 基础语法失败 | JSON/YAML/XML 不合法 | 修正语法或排除模板文件 | 模板文件可用 exclude 精确排除 |
| 密钥检测失败 | 提交了 token、私钥、连接串或测试凭据 | 删除密钥、轮换凭据、更新历史基线 | 只有确认假阳性时可用 allowlist 或 baseline |
| 拼写检查失败 | 术语、品牌名、缩写未加入词典 | 修正拼写或加入项目词典 | 业务术语可集中加入 .codespellrc |
| Scalafmt 失败 | 格式规则违规 | 运行 sbt scalafmt 自动修复 |
在 .scalafmt.conf 中用 project.excludeFilters 排除特定文件 |
| Scalafix 失败 | 规则违规 | 优先修复代码;规则不合理时调整 .scalafix.conf |
单行屏蔽用 // scalafix:ok,块级用 // scalafix:off / // scalafix:on |
| 编译或测试失败 | 类型错误、依赖缺失、测试用例失败 | 优先修复代码 | 不建议屏蔽 |
4.3 Scala 本地命令
以下命令中,Scalafmt 可自动修复格式;密钥检测和拼写检查已由 pre-commit 覆盖(如项目配置了 pre-commit),不需要再单独运行原生命令。
sbt 项目:
# 格式化(自动修复)
sbt scalafmt
# 格式检查(不修改,CI 用)
sbt scalafmtCheck
# Scalafix 自动修复
sbt scalafix
# Scalafix 检查模式(不修改,CI 用)
sbt 'scalafix --check'
# 编译 + 测试 + 格式检查 + Scalafix 检查
sbt compile test scalafmtCheck 'scalafix --check'
Gradle 项目(通过 Spotless):
# 格式化(自动修复)
./gradlew spotlessApply
# 格式检查
./gradlew spotlessCheck
误报优先通过 .scalafix.conf 规则配置或 project.excludeFilters 集中处理,行级屏蔽只用于局部、可解释的问题。详见"告警抑制"章节。
5. PR 门禁检查场景
PR 门禁应复用同一套工程配置,确保本地检查和 CI 检查口径一致。Scala 的编译、测试和语义规则检查具有跨文件上下文,PR 中不建议只检查变更文件,应按受影响模块执行完整构建和质量检查。大仓库可先识别受影响模块,只对受影响模块全量执行。
5.1 PR 门禁使用的工程配置
| 配置 | PR 中的用途 | 开发人员如何复现 |
|---|---|---|
project/plugins.sbt / build.sbt |
Scalafmt、Scalafix 插件配置和编译选项 | 本地运行同名 sbt 命令 |
.scalafmt.conf / .scalafix.conf |
格式规则和 Scalafix 规则集 | 本地确认规则配置是否与 CI 一致 |
| CI workflow | 固定运行环境、JDK 版本、sbt 版本、缓存、检查顺序 | 对照 workflow 的 run 命令逐条执行 |
.pre-commit-config.yaml |
全仓轻量检查、密钥检测、拼写检查 | 本地运行 pre-commit run --all-files |
5.2 PR 拦截与修复
| 拦截场景 | PR 中如何表现 | 开发人员处理方式 | 评审关注点 |
|---|---|---|---|
| pre-commit 失败 | CI 显示具体 hook 失败 | 本地运行同一 hook,提交修复结果 | 不接受直接跳过 hook 的提交 |
| 格式或 lint 失败 | CI 输出文件路径和规则名称 | 运行 sbt scalafmt 自动修复优先;Scalafix 问题按规则改代码 |
屏蔽必须限于最小范围 |
| 编译或测试失败 | 构建或测试阶段失败 | 本地复现失败命令,补充测试或修复依赖 | 不把环境问题误判为工具问题 |
| 安全或密钥失败 | 安全工具报告高风险问题 | 删除敏感内容、轮换凭据、解释假阳性 | 高风险问题必须修复或经安全确认 |
| 历史问题暴露 | 全量任务发现大量旧问题 | 新增问题阻断,历史问题进入 baseline 或治理任务 | 不能让新代码扩大历史问题范围 |
5.3 Scala PR 门禁 GitHub Actions 示例
name: Scala Quality
on: [pull_request, push]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- uses: sbt/setup-sbt@v1
- run: sbt compile test scalafmtCheck 'scalafix --check'
PR 中建议按受影响模块全量执行构建和质量检查,因为编译、测试和语义规则检查都具有跨文件上下文。大型多模块仓库可先定位受影响模块,再对受影响模块执行完整构建和质量检查。
6. 告警抑制
| 工具 | 优先做法 | 局部屏蔽 |
|---|---|---|
| Scalafmt | 统一格式规则,避免局部例外 | 在 .scalafmt.conf 中用 project.excludeFilters 排除特定文件 |
| Scalafix | 对规则分层,先启用高信噪比规则 | 行级 // scalafix:ok;块级 // scalafix:off / // scalafix:on;配置中移除规则 |
| Spotless | 统一格式规则,避免局部例外 | Spotless 告警抑制 |
| Gitleaks | 在 .gitleaks.toml 中配置 allowlist |
Gitleaks 告警抑制 |
| Codespell | 在 .codespellrc 或 pyproject.toml 中配置忽略词 |
Codespell 告警抑制 |
屏蔽顺序:先修正代码,再收敛规则(集中配置),最后最小范围屏蔽(行级注释)。
7. 误报处理与屏蔽策略
误报处理遵循"先修正、再收敛规则、最后局部屏蔽"的顺序。能通过代码改写、类型补全、测试样例调整解决的告警,不建议直接屏蔽;确实属于工具误报或历史兼容问题时,优先使用集中配置排除规则,其次使用行级注释,避免整文件关闭。
| 场景 | 建议做法 |
|---|---|
| 新代码误报 | 在 PR 中解释原因,使用最小范围的行级屏蔽(// scalafix:ok),并附规则名 |
| 历史遗留问题 | 建立 baseline 或仅对变更文件/变更行收紧,后续逐步清理 |
| 生成代码 | 在 .scalafmt.conf 的 project.excludeFilters 和 .scalafix.conf 中排除生成目录(如 target/generated-sources) |
| 第三方代码 | 排除 vendored / third_party 目录 |
| 安全扫描误报 | 要求说明风险不可达、测试环境密钥或假阳性依据,必要时安全负责人确认 |
具体屏蔽语法见本文"告警抑制"表格中各工具的说明。
8. 全量检查场景
Scala 的全量检查应以 sbt 生命周期为核心。PR 可以按受影响模块运行完整质量任务;主干、发布前和夜间任务应覆盖全量编译、测试、格式检查和 Scalafix 规则检查。Scala 不在 CodeQL 支持语言范围内,安全分析可考虑 Semgrep 等替代方案。
8.1 全量检查触发时机
| 场景 | 建议范围 | 目标 |
|---|---|---|
| PR | 受影响模块的 scalafmtCheck、scalafix --check、编译和测试 |
避免只检查变更文件导致跨文件类型问题漏检 |
| 主干合并后 | 全模块编译、测试、Scalafix 语义规则 | 捕捉跨模块依赖和类型问题 |
| 发布前 | 全量测试、依赖审计 | 验证发布包和依赖风险 |
| 夜间任务 | Scalafix 全量(含语义规则)、慢速集成测试 | 承载耗时较长的分析 |
| 规则升级 | 单独全量运行 Scalafix | 评估规则变化并批量修复 |
8.2 全量工具选择
| 工具 | 更适合全量的原因 | 建议处理方式 |
|---|---|---|
| sbt compile + test | 编译和测试具有模块上下文 | PR 查受影响模块,主干全量 |
| Scalafmt | 格式检查稳定,适合阻断 | PR 和主干都可运行 |
| Scalafix(语法规则) | 规则多,历史项目可能告警多 | 新项目严格,存量项目分规则治理 |
| Scalafix(语义规则) | 需 SemanticDB 编译上下文,适合构建后全量分析 | 主干或夜间运行,PR 可查受影响模块 |
8.3 全量问题处理
| 问题类型 | 处理方式 |
|---|---|
| Scalafix 历史问题 | 先启用高价值语法规则,语义规则单独治理 |
| Scalafix 误报 | 在 .scalafix.conf 中移除规则或用 // scalafix:ok 最小范围屏蔽 |
| flaky 测试 | 隔离并建 issue,不用质量工具屏蔽测试失败 |
| 生成代码 | 在 .scalafmt.conf 和 .scalafix.conf 中集中排除 |
9. 落地步骤
- 提交工具配置文件和 CI 工作流:先提交
project/plugins.sbt、.scalafmt.conf、.scalafix.conf、build.sbt和.github/workflows/scala-quality.yml,不立即阻断历史问题。 - 区分新项目和存量项目:新项目直接开启严格规则;存量项目先只检查变更文件或建立 baseline。
- 编写开发文档:在 README 或贡献指南中写清本地命令(
sbt scalafmt、sbt scalafmtCheck 'scalafix --check')、PR 门禁命令和误报屏蔽要求。 - 配置编辑器自动格式化:将 Scalafmt 设置为编辑器保存时自动触发(IntelliJ Scala 插件和 Metals 均内置支持),lint 默认不自动改代码。
- 规则升级单独发 PR:每次 Scalafix 规则升级单独发 PR,避免与业务改动混在一起。
- 定期治理:每季度清理一次 baseline、忽略列表和长期存在的屏蔽注释。