Scalafmt
相关工具推荐
| 工具 | 说明 |
|---|---|
| Scalafix | Scala 代码质量和重构检查工具,与 Scalafmt 互补 |
| Spotless | 多语言格式化调度插件,可集成 Scalafmt 作为 Scala 格式化引擎 |
| Pre-commit | 多语言 Git 钩子管理框架,可通过 local hook 调用 Scalafmt CLI |
| Prettier | 通用代码格式化工具,不支持 Scala,Scalafmt 是 Scala 对应的格式化工具 |
1. 简介
Scalafmt 是由 Scalameta 团队开发的 Scala 代码格式化工具,是 Scala 生态中的事实标准格式化方案。IntelliJ Scala 插件和 Metals 语言服务器均内置支持,ZIO、Cats、Play Framework 等主流项目均采用。
- 主要检查语言:Scala(Scala 2.12、2.13、Scala 3)
- 主要检查能力:Scala 代码自动格式化,支持 Scala 2.12/2.13/Scala 3 多版本 dialect 切换;涵盖代码格式化(自动修复型)
- 核心检查原理:基于 Scalameta 解析器生成 AST
- 检查规则/选项:提供数百个配置选项(按 indent、align、newlines、binPack、danglingParentheses、rewrite 等分组组织),全量配置选项
- GitHub 仓库:https://github.com/scalameta/scalafmt(1,488 stars)
- 开源协议:Apache-2.0
- 最新稳定版本:3.11.1
- 运行环境要求:需 JDK 11+;通过 sbt 插件或 CLI 运行
- 误报率:无
2. 官方文档
3. 社区优秀实践
3.1 ZIO
ZIO 是 Scala 函数式编程框架,使用 Scalafmt 统一代码格式,.scalafmt.conf 配置精细。
- 仓库地址:https://github.com/zio/zio
- 配置文件:仓库根目录
.scalafmt.conf
ZIO 的实践特点:
- 指定
runner.dialect = scala213统一 Scala 2.13 语法 - 配置
align.preset = most和align.multiline = false控制对齐策略 - 启用
rewrite.rules = [RedundantBraces]在格式化时自动移除冗余大括号 - 通过
rewriteTokens将 Unicode 箭头符号(⇒→←)统一替换为 ASCII 写法
3.2 Cats
Cats 是 Typelevel 生态核心库,使用 Scalafmt 和 Scalafix 管理代码质量。
- 仓库地址:https://github.com/typelevel/cats
- 配置文件:仓库根目录
.scalafmt.conf
Cats 的实践特点:
- 通过
fileOverride按目录切换 dialect,为.sbt、scala-2.12、scala-3目录设置不同语法版本 - 启用
rewrite.rules = [AvoidInfix, SortImports, RedundantParens, SortModifiers]在格式化时自动重写 - 配置
runner.dialect = scala213source3使用 Scala 2.13 + Scala 3 语法兼容模式 - 通过
runner.dialectOverride.allowSignificantIndentation = false禁用 Scala 3 缩进语法
3.3 Play Framework
Play Framework 是 Scala/Web 领域主流 Web 框架,使用 Scalafmt 统一代码格式。
- 仓库地址:https://github.com/playframework/playframework
- 配置文件:仓库根目录
.scalafmt.conf
Play Framework 的实践特点:
- 使用
preset = default作为基础预设,减少自定义配置量 - 配置
rewrite.rules包含AvoidInfix、RedundantParens、SortModifiers、PreferCurlyFors、Imports - 通过
rewrite.sortModifiers.order自定义修饰符排序顺序 - 通过
rewrite.imports配置 import 展开和分组策略 - 使用
fileOverride为 Scala 3 目录切换 dialect
4. 工具配置说明
4.1 配置文件说明
Scalafmt 涉及以下配置文件:
| 配置文件 | 用途 | 使用场景 |
|---|---|---|
.scalafmt.conf |
Scalafmt 主配置文件(HOCON 格式),控制格式化版本、方言、列宽、对齐等行为 | 所有项目,控制代码格式化行为 |
配置文件默认位置:仓库根目录 .scalafmt.conf(HOCON 格式)。
4.2 .scalafmt.conf 配置详解
.scalafmt.conf 是 Scalafmt 的主配置文件,用于控制代码格式化行为。
配置项说明:
| 配置项 | 类型 | 说明 |
|---|---|---|
version |
string | Scalafmt 版本号,建议锁定以避免跨环境格式化差异 |
runner.dialect |
string | Scala 方言(如 scala213、scala3、scala213source3、sbt) |
runner.dialectOverride.allowSignificantIndentation |
bool | 是否允许 Scala 3 显著缩进语法(默认 true) |
maxColumn |
int | 最大列宽(默认 80) |
preset |
string | 预设配置(如 default、defaultWithAlign),减少自定义配置量 |
align.preset |
string | 对齐策略(如 none、some、most、more) |
align.multiline |
bool | 多行结构是否对齐 |
fileOverride |
map | 按目录覆盖配置,键为 glob 模式,值为该目录专属配置块 |
project.excludeFilters |
list[string] | 排除格式化的文件或目录 glob 模式 |
推荐配置示例:
# .scalafmt.conf
version = "3.11.1"
runner.dialect = scala213
maxColumn = 120
preset = default
完整配置选项列表请参阅官方文档:Configuration
5. 主流集成方式
5.1 sbt 集成(生态主流方式)
sbt 是 Scala 生态事实标准构建工具,Scalafmt 通过官方 sbt-scalafmt 插件集成。
在 project/plugins.sbt 中添加插件:
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.1")
常用 sbt 任务:
| 任务 | 作用 |
|---|---|
sbt scalafmt |
格式化所有 Scala 源文件(自动修复) |
sbt scalafmtCheck |
检查格式是否合规(不修改,CI 用) |
sbt scalafmtSbt |
格式化 .sbt 和 project/*.scala 文件 |
sbt scalafmtSbtCheck |
检查 sbt 文件格式(不修改) |
sbt scalafmtAll |
格式化所有配置(含 Test)的源文件 |
sbt scalafmtCheckAll |
检查所有配置的格式 |
sbt scalafmtOnly <file> |
格式化指定文件 |
增量检查:sbt-scalafmt 插件的 scalafmtOnly 任务支持指定文件,实现文件级增量格式化:
sbt "scalafmtOnly src/main/scala/com/example/Foo.scala"
通过 pre-commit local hook 集成 Scalafmt 时(hook 通过 sbt scalafmt 调用 sbt 任务),pre-commit 框架默认只对暂存区中变更的 .scala 文件触发 hook。由于 Scalafmt 的 local hook 使用 pass_filenames: false(让 sbt 自行管理检查范围),实际增量由 sbt 的构建范围决定。
# .pre-commit-config.yaml
- repo: local
hooks:
- id: scalafmt
name: Scalafmt
entry: sbt scalafmt
language: system
files: \.scala$
pass_filenames: false # 由 sbt 控制检查范围
pre-commit 负责"何时触发"(只在有 .scala 文件变更时),sbt 负责"检查什么"。如需更精确的文件级增量,可改用 Scalafmt CLI 的
--mode changed参数:entry: scalafmt --mode changed,此时设置pass_filenames: false让 Scalafmt 自行通过 Git diff 确定变更文件。CI 中可通过pre-commit run --all-files做全量检查,或使用--from-ref/--to-ref做 PR 级增量。
5.2 IDE 集成
| IDE | 集成方式 |
|---|---|
| IntelliJ IDEA | 内置 Scala 插件自带 Scalafmt 支持,在 Settings → Editor → Code Style → Scala 中启用 "Scalafmt" |
| VS Code | 安装 Metals 扩展,Metals 内置 Scalafmt 支持,保存时自动格式化 |
| Neovim | 通过 nvim-metals LSP 集成 Scalafmt |
5.3 CLI 集成
通过 Coursier 安装 CLI:
cs install scalafmt
常用 CLI 命令:
# 格式化当前目录下所有 .scala 文件(读取 .scalafmt.conf)
scalafmt
# 仅检查不修改(CI 用,失败返回非 0)
scalafmt --test
# 只格式化 git diff 中改动的文件
scalafmt --mode changed
# 格式化指定文件
scalafmt path/to/File.scala
增量检查:Scalafmt CLI 原生支持文件级增量参数,无需额外脚本即可只处理变更文件:
| 参数/模式 | 作用 | 示例 |
|---|---|---|
--mode changed |
只格式化 git diff/status 中改动的文件 | scalafmt --mode changed |
--mode diff |
只格式化与 git HEAD 有差异的文件 | scalafmt --mode diff |
--test |
仅检查不修改,失败返回非 0 | scalafmt --test |
| 直接传文件路径 | 只格式化指定文件 | scalafmt File1.scala File2.scala |
也可结合 git diff 筛选变更文件后传递给 scalafmt:
# 只检查 git 暂存区中变更的 .scala 文件
git diff --name-only --diff-filter=ACMR --cached -- '*.scala' | xargs scalafmt --test
CI 脚本调用:在 CI 环境中通过脚本调用 Scalafmt(或 sbt 任务)进行格式化检查。
GitHub Actions 示例:
name: Scalafmt Check
on: [pull_request]
jobs:
format:
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 scalafmtCheckAll
增量检查:GitHub Actions 中按 PR 变更文件触发增量检查:
- name: Check changed files only
run: |
CHANGED=$(git diff --name-only --diff-filter=ACMR ${{ github.event.pull_request.base.sha }} -- '*.scala' | tr '\n' ' ')
if [ -n "$CHANGED" ]; then
scalafmt --test $CHANGED
fi
5.4 Scala CLI 集成
Scala CLI 内置格式化命令:
scala-cli format .
scala-cli format --check .
5.5 Gradle/Maven 集成(通过 Spotless)
Gradle 和 Maven 项目通过 Spotless 集成 Scalafmt:
Gradle build.gradle.kts:
plugins {
id("com.diffplug.spotless") version "7.0.2"
scala
}
spotless {
scala {
scalafmt()
}
}
./gradlew spotlessApply # 格式化
./gradlew spotlessCheck # 检查
6. 告警抑制(屏蔽)方法
6.1 通过工具配置文件屏蔽
在 .scalafmt.conf 中通过 project.excludeFilters 排除特定文件或目录:
# .scalafmt.conf
project.excludeFilters = [
"target/",
"generated-sources/",
".metals/"
]
6.2 通过 fileOverride 按目录切换规则
通过 fileOverride 为不同目录指定不同配置,避免全局调整:
fileOverride {
"glob:**/src/main/scala-3/**" {
runner.dialect = scala3
}
}
官方文档:fileOverride
6.3 通过 CLI 参数屏蔽
# 排除指定文件
scalafmt --exclude "target/**/*.scala" --exclude "generated/**"
# 只处理匹配的文件
scalafmt --include "src/main/**/*.scala"
官方文档:CLI 选项
6.4 通过构建配置屏蔽
sbt 项目中可通过 scalafmtConfig 或 scalafmtFilter 调整作用范围:
// build.sbt:排除特定目录
ThisBuild / scalafmtFilter = "diff:jvm"