Skip to content

Commit d407109

Browse files
authored
Add support for prince-of-space Java formatter (#2991)
2 parents f88db43 + 8b26c40 commit d407109

14 files changed

Lines changed: 526 additions & 2 deletions

File tree

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This document is intended for Spotless developers.
1010
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).
1111

1212
## [Unreleased]
13+
### Added
14+
- Add support for Java formatting via [`prince-of-space`](https://github.com/agustafson/prince-of-space). ([#2991](https://github.com/diffplug/spotless/pull/2991))
1315
### Changes
1416
- Bump default `greclipse` version to latest `4.39` -> `4.40`. ([#2989](https://github.com/diffplug/spotless/pull/2989))
1517
- Bump default `tabletest-formatter` version `1.1.1` -> `1.1.2`.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ extra('groovy.GrEclipseFormatterStep') +'{{yes}} | {{yes}}
8484
lib('java.GoogleJavaFormatStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
8585
lib('java.ImportOrderStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
8686
lib('java.PalantirJavaFormatStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
87+
lib('java.PrinceOfSpaceStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
8788
lib('java.RemoveUnusedImportsStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
8889
lib('java.ExpandWildcardImportsStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
8990
lib('java.ForbidWildcardImportsStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
@@ -143,6 +144,7 @@ lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}}
143144
| [`java.GoogleJavaFormatStep`](lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
144145
| [`java.ImportOrderStep`](lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
145146
| [`java.PalantirJavaFormatStep`](lib/src/main/java/com/diffplug/spotless/java/PalantirJavaFormatStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
147+
| [`java.PrinceOfSpaceStep`](lib/src/main/java/com/diffplug/spotless/java/PrinceOfSpaceStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
146148
| [`java.RemoveUnusedImportsStep`](lib/src/main/java/com/diffplug/spotless/java/RemoveUnusedImportsStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
147149
| [`java.ExpandWildcardImportsStep`](lib/src/main/java/com/diffplug/spotless/java/ExpandWildcardImportsStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
148150
| [`java.ForbidWildcardImportsStep`](lib/src/main/java/com/diffplug/spotless/java/ForbidWildcardImportsStep.java) | :+1: | :+1: | :+1: | :white_large_square: |

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ gson = "com.google.code.gson:gson:2.14.0"
5353
javaparser-symbol-solver-core = "com.github.javaparser:javaparser-symbol-solver-core:3.27.1"
5454
ktfmt = "com.facebook:ktfmt:0.63"
5555
palantir-java-format = "com.palantir.javaformat:palantir-java-format:1.1.0"
56+
prince-of-space-core = "io.github.agustafson.princeofspace:prince-of-space-core:2.2.0"
5657
scalafmt-core = "org.scalameta:scalafmt-core_2.13:3.8.1"
5758
sortpom-sorter = "com.github.ekryd.sortpom:sortpom-sorter:4.0.0"
5859
tabletest-formatter-core = "org.tabletest:tabletest-formatter-core:1.1.2"

lib/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def NEEDS_GLUE = [
2222
'ktfmt',
2323
'ktlint',
2424
'palantirJavaFormat',
25+
'princeOfSpace',
2526
'scalafmt',
2627
'sortPom',
2728
'tableTestFormatter',
@@ -117,6 +118,8 @@ dependencies {
117118
compatKtLint1Dot0Dot0CompileAndTestOnly libs.slf4j.api
118119
// palantirJavaFormat
119120
palantirJavaFormatCompileOnly libs.palantir.java.format // this version needs to stay compilable against Java 8 for CI Job testNpm
121+
// princeOfSpace
122+
princeOfSpaceCompileOnly libs.prince.of.space.core
120123
// scalafmt
121124
scalafmtCompileOnly libs.scalafmt.core
122125
// sortPom
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright 2026 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.java;
17+
18+
import java.io.Serial;
19+
import java.io.Serializable;
20+
import java.lang.reflect.Constructor;
21+
import java.util.Objects;
22+
23+
import javax.annotation.Nullable;
24+
25+
import com.diffplug.spotless.FormatterFunc;
26+
import com.diffplug.spotless.FormatterStep;
27+
import com.diffplug.spotless.JarState;
28+
import com.diffplug.spotless.Jvm;
29+
import com.diffplug.spotless.Provisioner;
30+
31+
/** Wraps up <a href="https://github.com/agustafson/prince-of-space">prince-of-space</a> as a FormatterStep. */
32+
public final class PrinceOfSpaceStep implements Serializable {
33+
@Serial
34+
private static final long serialVersionUID = 1L;
35+
private static final String NAME = "prince-of-space";
36+
public static final String MAVEN_COORDINATE = "io.github.agustafson.princeofspace:prince-of-space-core:";
37+
public static final String DEFAULT_VERSION = "2.2.0";
38+
39+
/** The jar that contains the formatter. */
40+
private final JarState.Promised jarState;
41+
/** Version of the formatter jar. */
42+
private final String formatterVersion;
43+
@Nullable private final Options options;
44+
45+
private PrinceOfSpaceStep(JarState.Promised jarState, String formatterVersion, @Nullable Options options) {
46+
this.jarState = jarState;
47+
this.formatterVersion = formatterVersion;
48+
this.options = options;
49+
}
50+
51+
/** Creates a step which formats Java source with prince-of-space's default options. */
52+
public static FormatterStep create(Provisioner provisioner) {
53+
return create(defaultVersion(), provisioner);
54+
}
55+
56+
/** Creates a step which formats Java source with prince-of-space's default options. */
57+
public static FormatterStep create(String version, Provisioner provisioner) {
58+
return create(version, provisioner, null);
59+
}
60+
61+
/** Creates a step which formats Java source with the given options. */
62+
public static FormatterStep create(String version, Provisioner provisioner, @Nullable Options options) {
63+
Objects.requireNonNull(version, "version");
64+
Objects.requireNonNull(provisioner, "provisioner");
65+
return FormatterStep.create(NAME,
66+
new PrinceOfSpaceStep(JarState.promise(() -> JarState.from(MAVEN_COORDINATE + version, provisioner)), version, options),
67+
PrinceOfSpaceStep::equalityState,
68+
State::createFormat);
69+
}
70+
71+
/** Get default formatter version */
72+
public static String defaultVersion() {
73+
return DEFAULT_VERSION;
74+
}
75+
76+
private State equalityState() {
77+
return new State(jarState.get(), formatterVersion, options);
78+
}
79+
80+
/** Mutable bag of the optional prince-of-space {@code FormatterConfig} knobs; unset (null) fields keep prince-of-space's own defaults. */
81+
public static class Options implements Serializable {
82+
@Serial
83+
private static final long serialVersionUID = 1L;
84+
85+
@Nullable private String indentStyle;
86+
@Nullable private Integer indentSize;
87+
@Nullable private Integer lineLength;
88+
@Nullable private String wrapStyle;
89+
@Nullable private Boolean closingParenOnNewLine;
90+
@Nullable private Boolean trailingCommas;
91+
@Nullable private Integer javaLanguageLevel;
92+
93+
public Options() {}
94+
95+
public void setIndentStyle(String indentStyle) {
96+
this.indentStyle = indentStyle;
97+
}
98+
99+
public void setIndentSize(int indentSize) {
100+
this.indentSize = indentSize;
101+
}
102+
103+
public void setLineLength(int lineLength) {
104+
this.lineLength = lineLength;
105+
}
106+
107+
public void setWrapStyle(String wrapStyle) {
108+
this.wrapStyle = wrapStyle;
109+
}
110+
111+
public void setClosingParenOnNewLine(boolean closingParenOnNewLine) {
112+
this.closingParenOnNewLine = closingParenOnNewLine;
113+
}
114+
115+
public void setTrailingCommas(boolean trailingCommas) {
116+
this.trailingCommas = trailingCommas;
117+
}
118+
119+
public void setJavaLanguageLevel(int javaLanguageLevel) {
120+
this.javaLanguageLevel = javaLanguageLevel;
121+
}
122+
}
123+
124+
private static final class State implements Serializable {
125+
@Serial
126+
private static final long serialVersionUID = 1L;
127+
128+
private final JarState jarState;
129+
private final String formatterVersion;
130+
@Nullable private final Options options;
131+
132+
State(JarState jarState, String formatterVersion, @Nullable Options options) {
133+
if (Jvm.version() < 17) {
134+
throw new IllegalStateException("prince-of-space requires a JDK 17+ host runtime, this JVM is " + Jvm.version());
135+
}
136+
this.jarState = jarState;
137+
this.formatterVersion = formatterVersion;
138+
this.options = options;
139+
}
140+
141+
FormatterFunc createFormat() throws Exception {
142+
final ClassLoader classLoader = jarState.getClassLoader();
143+
final Class<?> formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.pos.PrinceOfSpaceFormatterFunc");
144+
final Constructor<?> constructor = formatterFunc.getConstructor(
145+
String.class, Integer.class, Integer.class, String.class, Boolean.class, Boolean.class, Integer.class);
146+
if (options == null) {
147+
return (FormatterFunc) constructor.newInstance(null, null, null, null, null, null, null);
148+
}
149+
return (FormatterFunc) constructor.newInstance(
150+
options.indentStyle,
151+
options.indentSize,
152+
options.lineLength,
153+
options.wrapStyle,
154+
options.closingParenOnNewLine,
155+
options.trailingCommas,
156+
options.javaLanguageLevel);
157+
}
158+
}
159+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2026 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.glue.pos;
17+
18+
import java.io.File;
19+
20+
import com.diffplug.spotless.FormatterFunc;
21+
22+
import io.princeofspace.Formatter;
23+
import io.princeofspace.model.FormatterConfig;
24+
import io.princeofspace.model.IndentStyle;
25+
import io.princeofspace.model.JavaLanguageLevel;
26+
import io.princeofspace.model.WrapStyle;
27+
28+
public class PrinceOfSpaceFormatterFunc implements FormatterFunc.NeedsFile {
29+
30+
private final Formatter formatter;
31+
32+
public PrinceOfSpaceFormatterFunc(String indentStyle, Integer indentSize, Integer lineLength, String wrapStyle,
33+
Boolean closingParenOnNewLine, Boolean trailingCommas, Integer javaLanguageLevel) {
34+
FormatterConfig.Builder builder = FormatterConfig.builder();
35+
if (indentStyle != null) {
36+
builder.indentStyle(IndentStyle.valueOf(indentStyle));
37+
}
38+
if (indentSize != null) {
39+
builder.indentSize(indentSize);
40+
}
41+
if (lineLength != null) {
42+
builder.lineLength(lineLength);
43+
}
44+
if (wrapStyle != null) {
45+
builder.wrapStyle(WrapStyle.valueOf(wrapStyle));
46+
}
47+
if (closingParenOnNewLine != null) {
48+
builder.closingParenOnNewLine(closingParenOnNewLine);
49+
}
50+
if (trailingCommas != null) {
51+
builder.trailingCommas(trailingCommas);
52+
}
53+
if (javaLanguageLevel != null) {
54+
builder.javaLanguageLevel(JavaLanguageLevel.of(javaLanguageLevel));
55+
}
56+
this.formatter = new Formatter(builder.build());
57+
}
58+
59+
@Override
60+
public String applyWithFile(String unix, File file) throws Exception {
61+
return formatter.format(unix, file.toPath());
62+
}
63+
64+
@Override
65+
public String toString() {
66+
return "PrinceOfSpaceFormatterFunc{formatter=" + formatter + '}';
67+
}
68+
}

plugin-gradle/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).
44

55
## [Unreleased]
6+
### Added
7+
- Add support for Java formatting via [`prince-of-space`](https://github.com/agustafson/prince-of-space) with the new `princeOfSpace()` step. ([#2991](https://github.com/diffplug/spotless/pull/2991))
68
### Fixed
79
- Prevent parallel Gradle input fingerprinting from failing when `toggleOffOn()` wraps a slow lazy formatter step with no matching target files. ([#2994](https://github.com/diffplug/spotless/pull/2994))
810
### Changes

plugin-gradle/README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Spotless supports all of Gradle's built-in performance features (incremental bui
5656
- [Git hook (optional)](#git-hook)
5757
- [Linting](#linting)
5858
- **Languages**
59-
- [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [clang-format](#clang-format), [prettier](#prettier), [palantir-java-format](#palantir-java-format), [formatAnnotations](#formatAnnotations), [cleanthat](#cleanthat), [tabletest-formatter](#tabletest-formatter), [IntelliJ IDEA](#intellij-idea))
59+
- [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [clang-format](#clang-format), [prettier](#prettier), [palantir-java-format](#palantir-java-format), [prince-of-space](#prince-of-space), [formatAnnotations](#formatAnnotations), [cleanthat](#cleanthat), [tabletest-formatter](#tabletest-formatter), [IntelliJ IDEA](#intellij-idea))
6060
- [Groovy](#groovy) ([eclipse groovy](#eclipse-groovy))
6161
- [Kotlin](#kotlin) ([ktfmt](#ktfmt), [ktlint](#ktlint), [diktat](#diktat), [tabletest-formatter](#tabletest-formatter-1), [prettier](#prettier))
6262
- [Scala](#scala) ([scalafmt](#scalafmt))
@@ -312,6 +312,24 @@ spotless {
312312
palantirJavaFormat('2.39.0').formatJavadoc(true)
313313
```
314314

315+
### prince-of-space
316+
317+
[homepage](https://github.com/agustafson/prince-of-space). Requires a JDK 17+ host runtime.
318+
```gradle
319+
spotless {
320+
java {
321+
princeOfSpace()
322+
// optional: configure any of the formatter's options
323+
princeOfSpace('2.2.0')
324+
.indentStyle('SPACES') // or 'TABS'
325+
.indentSize(4)
326+
.lineLength(120)
327+
.wrapStyle('BALANCED') // or 'WIDE', 'NARROW'
328+
.closingParenOnNewLine(true)
329+
.trailingCommas(false)
330+
.javaLanguageLevel(17)
331+
```
332+
315333
### eclipse jdt
316334

317335
[homepage](https://download.eclipse.org/eclipse/downloads/). See [here](../ECLIPSE_SCREENSHOTS.md) for screenshots that demonstrate how to get and install the config file mentioned below.

0 commit comments

Comments
 (0)