agustafson/prince-of-space
GitHub: agustafson/prince-of-space
一款受 Prettier 启发的 Java 代码格式化工具,以最少配置和合理默认值终结团队代码风格争论。
Stars: 0 | Forks: 0
# 太空王子
[](https://central.sonatype.com/artifact/io.github.agustafson.princeofspace/prince-of-space-core)
[](https://github.com/agustafson/prince-of-space/actions/workflows/ci.yml)
[](LICENSE)
一个简单且可配置的 Java 代码格式化工具,能够生成排列整齐的代码。([`io.github.agustafson.princeofspace`](https://central.sonatype.com/namespace/io.github.agustafson.princeofspace))。
## 为什么需要另一个格式化工具?
Java 几乎是主流语言中唯一没有公认默认格式化工具的语言。JavaScript 有 [Prettier](https://prettier.io/)。Kotlin 有 [ktlint](https://pinterest.github.io/ktlint/)。Go 在标准工具链中附带了 `gofmt`。Java 却只有……无休止的争论。
现有的选项并不能很好地填补这一空白。这里有一篇很好的总结:https://jqno.nl/post/2024/08/24/why-are-there-no-decent-code-formatters-for-java/。Google-java-format 完全不可配置,而且它对双缩进的偏好使得生成的代码难以阅读。Eclipse 和 IntelliJ 内置的格式化工具提供了数百个调整旋钮,这听起来很灵活,但实际上意味着每个团队都会以不同的方式配置它们,而“格式化工具”反而成为了风格争论的另一个源头,而不是平息争论的终点。
Prince of Space 汲取了 Prettier 和 ktlint 的理念:强大、易读的默认设置,仅提供足够的配置来解决团队真正存在分歧的少数问题。以合理的默认值提供最少的选项。
## 功能
- **7 个配置选项**,涵盖缩进、行长、换行和尾随逗号
- **单阈值行长** — 一个 `lineLength` 目标使得换行更具可预测性,并缩小了配置范围
- **幂等(不动点输出)** — 格式化一次后,再次运行格式化工具不会做任何进一步修改(`format(format(x)) == format(x)`)。默认引擎在内部**收敛**(在预算范围内重复遍历直到稳定),因此通常一次调用即可满足要求;参见 [`docs/canonical-formatting-rules.md`](docs/canonical-formatting-rules.md) 中的规则 1。
- **支持 Java 8 到 25+** — 可以解析任何 Java 语言级别;运行环境需 JDK 17+
- **多种集成方式** — 库 API、CLI、Spotless 插件、IntelliJ 插件、VS Code 扩展
## 快速开始
### 库 (Gradle)
```
dependencies {
implementation("io.github.agustafson.princeofspace:prince-of-space-core:2.1.2")
}
```
```
import io.princeofspace.Formatter;
import io.princeofspace.model.FormatterConfig;
Formatter formatter = new Formatter(FormatterConfig.defaults());
String formatted = formatter.format(sourceCode);
```
### 库 (Maven)
```
io.github.agustafson.princeofspace
prince-of-space-core
2.1.2
```
### CLI
```
./gradlew :cli:shadowJar
java -jar modules/cli/build/libs/prince-of-space-cli-*.jar --help
```
常用标志:
| 标志 | 描述 |
|------|-------------|
| `--check` | 如果任何文件会被更改,则以退出码 1 退出(不写入) |
| `--stdin` | 从标准输入读取,写入标准输出 |
| `--java-version N` | Java 语言级别 (8, 11, 17, 21, 25 等) |
| `-r` | 递归进入目录 |
| `-v` | 在标准错误输出显示详细进度 |
### Spotless
```
import io.princeofspace.model.FormatterConfig
import io.princeofspace.spotless.PrinceOfSpaceStep
spotless {
java {
target("src/**/*.java")
addStep(PrinceOfSpaceStep.create(FormatterConfig.defaults()))
}
}
```
将 Spotless 模块放在你的构建导入 `PrinceOfSpaceStep` 的 classpath 上 — 例如 `buildSrc` / `implementation`,或根据你的 Gradle 布局使用 `buildscript { dependencies { classpath(...) } }`。使用 `io.github.agustafson.princeofspace:prince-of-space-spotless:2.1.2`(固定为 Maven Central 上的版本)。Maven:将相同的坐标添加为 `spotless-maven-plugin` 的依赖,然后在插件配置中使用 `PrinceOfSpaceStep.create(...)`。
### IntelliJ 插件
**Settings > Tools > Prince of Space** — 配置全部 7 个选项,选择固定的 Java 级别或从模块继承,并可选择在保存时格式化。通过 **Code > Reformat with Prince of Space...** 进行格式化
```
./gradlew :intellij-plugin:runIde # develop
./gradlew :intellij-plugin:buildPlugin # package
```
### VS Code 扩展
`modules/vscode-extension/` 目录包含一个注册了 Java 格式化提供程序的 TypeScript 扩展。它委托给 CLI shadow JAR,从工作区解析 `modules/cli/build/libs/prince-of-space-cli-*.jar`,除非设置了 `princeOfSpace.cliJar`。
## 配置
| 选项 | 默认值 | 描述 |
|--------|---------|-------------------------------------------------------|
| `wrapStyle` | `balanced` | `wide`、`balanced` 或 `narrow` 换行 |
| `indentStyle` | `spaces` | `spaces` 或 `tabs` |
| `indentSize` | `4` | 每缩进级别的单位(空格或制表符) |
| `lineLength` | `120` | 目标行宽 — 换行在此触发 |
| `closingParenOnNewLine` | `true` | 参数换行时将 `)` 放在单独的一行 |
| `trailingCommas` | `false` | 多行 enums/arrays 中的尾随逗号 |
| `javaLanguageLevel` | `17` | 解析器接受的 Java 语法级别 |
此表中的数字和枚举默认值与源代码中的公共 `FormatterConfig.DEFAULT_*` 常量相匹配(单一事实来源)。
延续缩进始终为 `2 * indentSize`(默认为 8 个空格),用于带分隔符的列表延续(参数、实参、二元表达式、三元表达式等)。这遵循了 Oracle/IntelliJ 的惯例,并确保参数在视觉上始终与方法体有所区分。编程访问:`FormatterConfig#continuationIndentSize()`。换行的链式方法调用则使用单个 `indentSize` 步进 — 参见 [`docs/formatting-rules.md`](docs/formatting-rules.md) 中的“方法链”和 TDR-015。
### 换行风格
`wrapStyle` 控制触发换行后元素在各行中的分布方式。这是影响最大的选项 — 同样的代码在不同风格下看起来会大相径庭。
**`balanced`**(默认) — 要么全有要么全无:要么所有内容都放在同一行,要么每个元素独占一行。这就是 [Prettier 的方法](https://prettier.io/docs/option-philosophy):它避免了某些参数在同一行而其他参数在下一行的混乱中间状态。
```
// fits on one line — left alone
doSomething(name, age, active);
// does not fit — every element gets its own line
doSomething(
name,
age,
active
);
```
**`wide`** — 尽量将内容保留在同一行;仅对为保持在行长限制内所必需的部分进行换行。
```
doSomething(name, age,
active, extraParam);
```
**`narrow`** — 如果需要任何换行,则立即将每个元素放在单独的一行。
```
doSomething(
name,
age,
active
);
```
`javaLanguageLevel`(默认值:`17`)控制解析器接受哪种 Java 语法。在 API 中通过 `FormatterConfig.builder().javaLanguageLevel(JavaLanguageLevel.of(21))` 设置,或在 CLI 中通过 `--java-version 21` 设置。
## 示例
`examples/` 目录是评估各项选项如何影响实际输出的最佳方式:
- **`examples/inputs/java{8,17,21,25}/FormatterShowcase.java`** — 一个涵盖 46+ 种场景的单一未格式化源文件:构造函数、方法链、lambdas、二元运算符、泛型、switch 表达式、records、sealed 类型、文本块等。
- **`examples/outputs/java{8,17,21,25}/`** — 每个 Java 级别有 6 个格式化版本(共 24 个),分别对应 `wrapStyle` 和 `closingParenOnNewLine` 的每种组合。
- **`examples/external/outputs/`** — 通过 [Spotless](https://github.com/diffplug/spotless) 使用其他流行的 Java 格式化工具(Google Java Format AOSP、Eclipse JDT、Palantir Java Format AOSP、带有 prettier-plugin-java 的 Prettier)处理的相同展示文件,每个格式化工具每个 Java 级别一个文件 — 在比较格式化工具时非常有用。
在浏览器中打开 **[`examples/compare.html`](examples/compare.html)**(或此仓库的 GitHub Pages 副本)以查看交互式的并排对比:选择一个 Java 级别,然后在左侧和右侧各选任意两个输出 — Prince of Space 配置 **或** 上述基于 Spotless 的替代方案之一 — 以查看它们在相同输入上的差异。要获取侧重于 Prince of Space 选项的带解说演练,请参阅 **[docs/output-showcase.md](docs/output-showcase.md)**。
## 性能 (Spring Framework)
在此语料库上,**strict**(不动点)模式的 Prince of Space 比 Eclipse JDT 慢,并且比 Google Java Format 和 Palantir 稍慢;**fast** 单次处理模式的速度则与这些 JVM 格式化工具大致相当。默认引擎优先保证幂等性(规则 1)而不是原始吞吐量。在实践中,Spotless 通常只格式化更改过的文件,这使实际执行时间保持在较短水平。
_从 [`docs/perf-results/spring-framework.md`](docs/perf-results/spring-framework.md) 自动生成 — 请勿在标记之间编辑;运行 `./gradlew refreshSpringBenchmarkReadme` 或在 CI 中执行。_
[Spring Framework](https://github.com/spring-projects/spring-framework) 语料库上的吞吐量(**39ff8e4**,**9204** 个文件,与外部评估工具使用相同的路径过滤器)。**平均毫秒/文件** 是该次运行的总 JVM 实际时间除以文件数量。每个 JVM 格式化工具在一个进程中运行并进行预热;JVM:**21.0.10+7-LTS**
Prince of Space 使用 `FormatterConfig.defaults()`(行长 **120**,换行 **BALANCED**,Java 语言级别 **17**)。Google Java Format 和 Palantir Java Format 使用 **AOSP** 风格,以与 `examples/external/outputs/` 的 Spotless 对比保持一致。Eclipse JDT 使用 Spotless `eclipse()` 默认设置(`examples/external/outputs/eclipse/`)。
## 结果
| 格式化工具 | 平均毫秒/文件 | 失败数 |
|-----------|-----------------|----------|
| Prince of Space (strict, 不动点) | 6.24 | 0 |
| Prince of Space (fast, 单次处理) | 2.51 | 0 |
| Google Java Format (AOSP) | 2.69 | 0 |
| Palantir Java Format (AOSP) | 2.73 | 0 |
| Eclipse JDT (Spotless default) | 2.21 | 0 |
完整细节和重新生成方法:运行 `./gradlew :formatter-benchmark:run`,并使用 `PRINCE_BENCH_ROOT` 指向一个代码库检出副本。_此运行中跳过了 Prettier 吞吐量测试 (`PRINCE_BENCH_SKIP_PRETTIER=true`)。_ Eclipse JDT 使用了与 [`examples/external/outputs/eclipse/`](examples/external/outputs/eclipse/) 相同的 Spotless `eclipse()` 栈(首次运行使用 Equo P2 + Maven Central)。
_报告日期:**2026-05-03**。_
## API
公共 API 由四种类型组成:
| 类型 | 描述 |
|------|-------------|
| `Formatter` | 入口点 — `format(String)` 在失败时抛出异常,`formatResult(String)` 返回一个密封结果 |
| `FormatterConfig` | 不可变记录,包含所有 7 个选项 + 语言级别的构建器 |
| `FormatResult` | 密封接口:`Success` 或 `Failure`(`ParseFailure`、`EmptyCompilationUnit`) |
| `FormatterException` | 在解析或管道失败时由 `Formatter.format()` 抛出 |
支持值类型:`IndentStyle`、`WrapStyle`、`JavaLanguageLevel`。
### strict 默认模式与 fast 单次处理模式(`Formatter` 重载)
`new Formatter(FormatterConfig)` 会运行引擎,直到输出**收敛**(在 `prince.maxConvergencePasses` 范围内的不动点),这是使**规则 1** 幂等性在日常使用中得以成立的保证。
带有 `fastSinglePass=true` 的 `new Formatter(FormatterConfig, boolean fastSinglePass)` 执行**一次**解析→打印过程 — 适用于与其他单次调用的 JVM 格式化工具进行吞吐量基准测试(`:formatter-benchmark`)。它**不是**七个格式化选项之外的第二个公共配置旋钮:在 `FormatterConfig`、Spotless 或 IDE 插件中暴露“fast”模式会诱使集成工具为了速度而输出非不动点的结果。建议将快速模式保留用于实验和愿意接受这种权衡的高级调用者(参见 [`docs/technical-decision-register.md`](docs/technical-decision-register.md) 中的 TDR-026)。
### 非抛出异常型 API
```
FormatResult result = formatter.formatResult(sourceCode);
if (result instanceof FormatResult.Success success) {
System.out.println(success.formattedSource());
} else if (result instanceof FormatResult.ParseFailure failure) {
System.err.println(failure.message());
}
```
## 构件 (Maven Central)
Group ID:**`io.agustafson.princeofspace`**。发布的版本显示在 [Maven Central](https://central.sonatype.com/namespace/io.github.agustafson.princeofspace) 上。快速开始的坐标从 `gradle.properties` 中的 **`readmeMavenCoordinatesVersion`** 同步(最后一次 Central 发布 — 更改后需运行 `./gradlew syncReadmeVersions`)。开发构建使用同一文件中的 `version=`(通常是 `*-SNAPSHOT`),并且不会出现在 README 代码片段中。
| 构件 | 坐标 | 何时使用 |
|----------|------------|-------------|
| `prince-of-space-core` | `io.github.agustafson.princeofspace:prince-of-space-core:2.1.2` | 默认 — 体积小;JavaParser + SLF4J 作为常规传递依赖 |
| `prince-of-space-bundled` | `io.github.agustafson.princeofspace:prince-of-space-bundled:2.1.2` | 单个胖 JAR,依赖项已重定位 — 无 classpath 冲突 |
| `prince-of-space-spotless` | `io.github.agustafson.princeofspace:prince-of-space-spotless:2.1.2` | Spotless `FormatterStep` (`PrinceOfSpaceStep`) |
| CLI (shadow JAR) | 从仓库构建或附加至 [GitHub Releases](https://github.com/agustafson/prince-of-space/releases) | 命令行格式化;并不总是发布到 Central |
## 非目标
- 组织 Java 导入(委托给 Spotless)
- 第一方的 Maven/Gradle 插件(由 Spotless 提供)
- 类型解析(格式化不需要)
## 从源码构建
需要 JDK 21+。通过 `--release 17` 将发布的字节码目标定为 Java 17。
```
./gradlew build # full build: compile, test, Spotless, Checkstyle, SpotBugs
./gradlew :core:test # fast feedback loop for core changes
```
有关提交约定和 PR 要求,请参阅 [docs/contributing.md](docs/contributing.md)。
## 文档
完整索引请参阅 **[docs/index.md](docs/index.md)**。主要文档:
| 文档 | 内容 |
|----------|----------|
| [docs/architecture.md](docs/architecture.md) | 包布局、编码约定、模块结构 |
| [docs/contributing.md](docs/contributing.md) | 提交约定、构建要求、PR 检查 |
| [CHANGELOG.md](CHANGELOG.md) | 发布历史 |
| [SECURITY.md](SECURITY.md) | 漏洞报告 |
| [docs/formatting-rules.md](docs/formatting-rules.md) | 所有格式化规则和配置选项 |
| [docs/evaluation.md](docs/evaluation.md) | 真实世界评估工具及最新结果 (Guava + Spring) |
| [docs/benchmarks.md](docs/benchmarks.md) | 吞吐量工具 (`:formatter-benchmark`) 和冒烟测试时间 |
| [docs/perf-results/spring-framework.md](docs/perf-results/spring-framework.md) | 最新 Spring Framework 语料库与其他格式化工具的对比 |
| [docs/technical-decision-register.md](docs/technical-decision-register.md) | 架构决策日志 |
## 许可证
[Apache License 2.0](LICENSE)
[](https://central.sonatype.com/artifact/io.github.agustafson.princeofspace/prince-of-space-core)
[](https://github.com/agustafson/prince-of-space/actions/workflows/ci.yml)
[](LICENSE)
一个简单且可配置的 Java 代码格式化工具,能够生成排列整齐的代码。([`io.github.agustafson.princeofspace`](https://central.sonatype.com/namespace/io.github.agustafson.princeofspace))。
## 为什么需要另一个格式化工具?
Java 几乎是主流语言中唯一没有公认默认格式化工具的语言。JavaScript 有 [Prettier](https://prettier.io/)。Kotlin 有 [ktlint](https://pinterest.github.io/ktlint/)。Go 在标准工具链中附带了 `gofmt`。Java 却只有……无休止的争论。
现有的选项并不能很好地填补这一空白。这里有一篇很好的总结:https://jqno.nl/post/2024/08/24/why-are-there-no-decent-code-formatters-for-java/。Google-java-format 完全不可配置,而且它对双缩进的偏好使得生成的代码难以阅读。Eclipse 和 IntelliJ 内置的格式化工具提供了数百个调整旋钮,这听起来很灵活,但实际上意味着每个团队都会以不同的方式配置它们,而“格式化工具”反而成为了风格争论的另一个源头,而不是平息争论的终点。
Prince of Space 汲取了 Prettier 和 ktlint 的理念:强大、易读的默认设置,仅提供足够的配置来解决团队真正存在分歧的少数问题。以合理的默认值提供最少的选项。
## 功能
- **7 个配置选项**,涵盖缩进、行长、换行和尾随逗号
- **单阈值行长** — 一个 `lineLength` 目标使得换行更具可预测性,并缩小了配置范围
- **幂等(不动点输出)** — 格式化一次后,再次运行格式化工具不会做任何进一步修改(`format(format(x)) == format(x)`)。默认引擎在内部**收敛**(在预算范围内重复遍历直到稳定),因此通常一次调用即可满足要求;参见 [`docs/canonical-formatting-rules.md`](docs/canonical-formatting-rules.md) 中的规则 1。
- **支持 Java 8 到 25+** — 可以解析任何 Java 语言级别;运行环境需 JDK 17+
- **多种集成方式** — 库 API、CLI、Spotless 插件、IntelliJ 插件、VS Code 扩展
## 快速开始
### 库 (Gradle)
```
dependencies {
implementation("io.github.agustafson.princeofspace:prince-of-space-core:2.1.2")
}
```
```
import io.princeofspace.Formatter;
import io.princeofspace.model.FormatterConfig;
Formatter formatter = new Formatter(FormatterConfig.defaults());
String formatted = formatter.format(sourceCode);
```
### 库 (Maven)
```
标签:IDE插件, Java代码格式化工具, JS文件枚举, Prettier替代, 代码排版, 代码格式化, 代码美化, 代码规范, 代码风格配置, 后台面板检测, 域名枚举, 威胁情报, 开发者工具, 开源, 开源框架, 持续集成, 格式化器, 错误基检测, 静态代码分析