Skip to content

Commit 83b5011

Browse files
authored
Fix TableTest editorconfig caching and adds config fallbacks (#2893)
2 parents 708a1b0 + 1e147af commit 83b5011

7 files changed

Lines changed: 213 additions & 20 deletions

File tree

lib/src/main/java/com/diffplug/spotless/java/TableTestFormatterStep.java

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.Serial;
1919
import java.io.Serializable;
2020
import java.lang.reflect.Constructor;
21+
import java.util.Locale;
2122
import java.util.Objects;
2223

2324
import com.diffplug.spotless.FormatterFunc;
@@ -27,34 +28,61 @@
2728

2829
/**
2930
* Formats {@code @TableTest} annotation tables in Java and Kotlin source files.
30-
* Configuration is read from {@code .editorconfig} files.
31+
* <p>
32+
* Configuration is read from {@code .editorconfig} files. When no editorconfig is found
33+
* or the lookup fails, the configured fallback indent style and size are used.
34+
* If no fallback is configured, defaults matching {@code Config.SPACES_4} and
35+
* {@code Config.NO_INDENT} are applied.
3136
*/
3237
public final class TableTestFormatterStep implements Serializable {
3338
@Serial
34-
private static final long serialVersionUID = 1L;
39+
private static final long serialVersionUID = 2L;
3540
private static final String NAME = "tableTestFormatter";
3641
private static final String MAVEN_COORDINATE = "org.tabletest:tabletest-formatter-core:";
3742
private static final String DEFAULT_VERSION = "1.1.1";
3843

44+
/** Default fallback indent style ({@code "space"}) for Java/Kotlin files. */
45+
public static final String DEFAULT_INDENT_STYLE = "space";
46+
/** Default fallback indent size ({@code 4}) for Java/Kotlin files, matching {@code Config.SPACES_4}. */
47+
public static final int DEFAULT_INDENT_SIZE = 4;
48+
3949
private final JarState.Promised jarState;
4050
private final String version;
51+
private final String indentStyle;
52+
private final int indentSize;
4153

42-
private TableTestFormatterStep(JarState.Promised jarState, String version) {
54+
private TableTestFormatterStep(JarState.Promised jarState, String version, String indentStyle, int indentSize) {
4355
this.jarState = jarState;
4456
this.version = version;
57+
this.indentStyle = validateIndentStyle(indentStyle);
58+
this.indentSize = validateIndentSize(indentSize);
4559
}
4660

47-
/** Creates a step which formats {@code @TableTest} tables using the default version. */
61+
/** Creates a step which formats {@code @TableTest} tables using the default version and indent config. */
4862
public static FormatterStep create(Provisioner provisioner) {
4963
return create(defaultVersion(), provisioner);
5064
}
5165

52-
/** Creates a step which formats {@code @TableTest} tables using the given version. */
66+
/** Creates a step which formats {@code @TableTest} tables using the given version and default indent config. */
5367
public static FormatterStep create(String version, Provisioner provisioner) {
68+
return create(version, provisioner, DEFAULT_INDENT_STYLE, DEFAULT_INDENT_SIZE);
69+
}
70+
71+
/**
72+
* Creates a step which formats {@code @TableTest} tables using the given version and fallback indent config.
73+
* <p>
74+
* The fallback config is used when no {@code .editorconfig} is found or the lookup fails.
75+
*
76+
* @param version the tabletest-formatter-core version
77+
* @param provisioner the jar provisioner
78+
* @param indentStyle fallback indent style: {@code "space"} or {@code "tab"} (case-insensitive)
79+
* @param indentSize fallback indent size (must be &gt;= 0)
80+
*/
81+
public static FormatterStep create(String version, Provisioner provisioner, String indentStyle, int indentSize) {
5482
Objects.requireNonNull(version, "version");
5583
Objects.requireNonNull(provisioner, "provisioner");
5684
return FormatterStep.create(NAME,
57-
new TableTestFormatterStep(JarState.promise(() -> JarState.from(MAVEN_COORDINATE + version, provisioner)), version),
85+
new TableTestFormatterStep(JarState.promise(() -> JarState.from(MAVEN_COORDINATE + version, provisioner)), version, indentStyle, indentSize),
5886
TableTestFormatterStep::equalityState,
5987
State::createFormat);
6088
}
@@ -65,26 +93,46 @@ public static String defaultVersion() {
6593
}
6694

6795
private State equalityState() {
68-
return new State(jarState.get(), version);
96+
return new State(jarState.get(), version, indentStyle, indentSize);
97+
}
98+
99+
public static String validateIndentStyle(String indentStyle) {
100+
Objects.requireNonNull(indentStyle, "indentStyle");
101+
String lower = indentStyle.toLowerCase(Locale.ROOT);
102+
if (!lower.equals("space") && !lower.equals("tab")) {
103+
throw new IllegalArgumentException("indentStyle must be 'space' or 'tab', got: " + indentStyle);
104+
}
105+
return lower;
106+
}
107+
108+
public static int validateIndentSize(int indentSize) {
109+
if (indentSize < 0) {
110+
throw new IllegalArgumentException("indentSize must be >= 0, got: " + indentSize);
111+
}
112+
return indentSize;
69113
}
70114

71115
private static final class State implements Serializable {
72116
@Serial
73-
private static final long serialVersionUID = 1L;
117+
private static final long serialVersionUID = 2L;
74118

75119
private final JarState jarState;
76120
private final String version;
121+
private final String indentStyle;
122+
private final int indentSize;
77123

78-
State(JarState jarState, String version) {
124+
State(JarState jarState, String version, String indentStyle, int indentSize) {
79125
this.jarState = jarState;
80126
this.version = version;
127+
this.indentStyle = indentStyle;
128+
this.indentSize = indentSize;
81129
}
82130

83131
FormatterFunc createFormat() throws Exception {
84132
ClassLoader classLoader = jarState.getClassLoader();
85133
Class<?> formatterClazz = classLoader.loadClass("com.diffplug.spotless.glue.java.TableTestFormatterFunc");
86-
Constructor<?> constructor = formatterClazz.getConstructor();
87-
return (FormatterFunc.NeedsFile) constructor.newInstance();
134+
Constructor<?> constructor = formatterClazz.getConstructor(String.class, int.class);
135+
return (FormatterFunc.NeedsFile) constructor.newInstance(indentStyle, indentSize);
88136
}
89137
}
90138
}

lib/src/tableTestFormatter/java/com/diffplug/spotless/glue/java/TableTestFormatterFunc.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,57 @@
1616
package com.diffplug.spotless.glue.java;
1717

1818
import java.io.File;
19+
import java.util.Locale;
1920

2021
import org.tabletest.formatter.config.Config;
2122
import org.tabletest.formatter.config.EditorConfigProvider;
23+
import org.tabletest.formatter.config.IndentStyle;
2224
import org.tabletest.formatter.core.SourceFileFormatter;
2325
import org.tabletest.formatter.core.TableTestFormatter;
2426

2527
import com.diffplug.spotless.FormatterFunc;
2628

2729
/**
2830
* Formats {@code @TableTest} annotation tables in Java, Kotlin, and standalone {@code .table} files.
31+
* <p>
32+
* For Java/Kotlin files, the indent config priority is:
33+
* <ol>
34+
* <li>Settings from {@code .editorconfig}</li>
35+
* <li>Configured fallback ({@code indentStyle}/{@code indentSize} constructor parameters)</li>
36+
* <li>Built-in default: {@code Config.SPACES_4}</li>
37+
* </ol>
38+
* For {@code .table} files, {@code Config.NO_INDENT} is always used.
2939
*/
3040
public class TableTestFormatterFunc implements FormatterFunc.NeedsFile {
3141

32-
private static final EditorConfigProvider CONFIG_PROVIDER = new EditorConfigProvider();
42+
private final EditorConfigProvider CONFIG_PROVIDER = new EditorConfigProvider();
43+
private final Config sourceFallbackConfig;
3344

3445
private final SourceFileFormatter sourceFormatter = new SourceFileFormatter();
3546
private final TableTestFormatter tableFormatter = new TableTestFormatter();
3647

48+
/** Creates a formatter using the built-in default fallback ({@code Config.SPACES_4}). */
49+
public TableTestFormatterFunc() {
50+
this.sourceFallbackConfig = Config.SPACES_4;
51+
}
52+
53+
/**
54+
* Creates a formatter with a configured fallback indent style and size for Java/Kotlin files.
55+
* Used when {@code .editorconfig} is absent or the lookup fails.
56+
*
57+
* @param indentStyle {@code "space"} or {@code "tab"} (case-insensitive)
58+
* @param indentSize indent size (&gt;= 0)
59+
*/
60+
public TableTestFormatterFunc(String indentStyle, int indentSize) {
61+
this.sourceFallbackConfig = new Config(IndentStyle.valueOf(indentStyle.toUpperCase(Locale.ROOT)), indentSize);
62+
}
63+
3764
@Override
3865
public String applyWithFile(String unix, File file) throws Exception {
3966
String fileName = file.getName();
4067

4168
if (fileName.endsWith(".java") || fileName.endsWith(".kt")) {
42-
Config config = CONFIG_PROVIDER.lookupConfig(file.toPath(), Config.SPACES_4);
69+
Config config = CONFIG_PROVIDER.lookupConfig(file.toPath(), sourceFallbackConfig);
4370
String formatted = sourceFormatter.format(unix, config);
4471
return formatted.equals(unix) ? unix : formatted;
4572
}

plugin-gradle/CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
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 `withIndentStyle` and `withIndentSize` configuration to `tableTestFormatter` for setting the fallback indent when no `.editorconfig` is found. ([#2893](https://github.com/diffplug/spotless/pull/2893))
8+
### Fixed
9+
- Fix `tableTestFormatter` editorconfig cache not honoring `.editorconfig` changes across Gradle daemon runs due to a shared static `EditorConfigProvider`. ([#2893](https://github.com/diffplug/spotless/pull/2893))
610

711
## [8.4.0] - 2026-03-18
812
### Added

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TableTestExtension.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,37 @@ public TableTestFormatterConfig tableTestFormatter(String version) {
4646

4747
public class TableTestFormatterConfig {
4848
private final String version;
49+
private String indentStyle = TableTestFormatterStep.DEFAULT_INDENT_STYLE;
50+
private int indentSize = TableTestFormatterStep.DEFAULT_INDENT_SIZE;
4951

5052
TableTestFormatterConfig(String version) {
5153
this.version = version;
5254
addStep(createStep());
5355
}
5456

57+
/**
58+
* Sets the fallback indent style used when no {@code .editorconfig} is found.
59+
* Must be {@code "space"} or {@code "tab"} (case-insensitive).
60+
* Defaults to {@code "space"}.
61+
*/
62+
public TableTestFormatterConfig withIndentStyle(String indentStyle) {
63+
this.indentStyle = TableTestFormatterStep.validateIndentStyle(indentStyle);
64+
replaceStep(createStep());
65+
return this;
66+
}
67+
68+
/**
69+
* Sets the fallback indent size used when no {@code .editorconfig} is found.
70+
* Must be &gt;= 0. Defaults to {@code 4}.
71+
*/
72+
public TableTestFormatterConfig withIndentSize(int indentSize) {
73+
this.indentSize = TableTestFormatterStep.validateIndentSize(indentSize);
74+
replaceStep(createStep());
75+
return this;
76+
}
77+
5578
private FormatterStep createStep() {
56-
return TableTestFormatterStep.create(version, provisioner());
79+
return TableTestFormatterStep.create(version, provisioner(), indentStyle, indentSize);
5780
}
5881
}
5982
}

plugin-maven/src/main/java/com/diffplug/spotless/maven/java/TableTestFormatter.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,37 @@
2323
import com.diffplug.spotless.maven.FormatterStepFactory;
2424

2525
/**
26-
* Formats {@code @TableTest} annotation tables. Configuration is read from {@code .editorconfig} files.
26+
* Formats {@code @TableTest} annotation tables. Configuration is read from {@code .editorconfig} files,
27+
* falling back to the configured {@code indentStyle} and {@code indentSize} when no editorconfig is found.
2728
*/
2829
public class TableTestFormatter implements FormatterStepFactory {
2930

3031
@Parameter
3132
private String version;
3233

34+
/**
35+
* Fallback indent style when no {@code .editorconfig} is found: {@code space} or {@code tab}.
36+
* Defaults to {@code space}.
37+
*/
38+
@Parameter
39+
private String indentStyle;
40+
41+
/**
42+
* Fallback indent size when no {@code .editorconfig} is found. Must be &gt;= 0.
43+
* Defaults to {@code 4}.
44+
*/
45+
@Parameter
46+
private Integer indentSize;
47+
3348
@Override
3449
public FormatterStep newFormatterStep(FormatterStepConfig config) {
35-
String version = this.version != null ? this.version : TableTestFormatterStep.defaultVersion();
36-
return TableTestFormatterStep.create(version, config.getProvisioner());
50+
String resolvedVersion = this.version != null ? this.version : TableTestFormatterStep.defaultVersion();
51+
String resolvedStyle = this.indentStyle != null
52+
? TableTestFormatterStep.validateIndentStyle(this.indentStyle)
53+
: TableTestFormatterStep.DEFAULT_INDENT_STYLE;
54+
int resolvedSize = this.indentSize != null
55+
? TableTestFormatterStep.validateIndentSize(this.indentSize)
56+
: TableTestFormatterStep.DEFAULT_INDENT_SIZE;
57+
return TableTestFormatterStep.create(resolvedVersion, config.getProvisioner(), resolvedStyle, resolvedSize);
3758
}
3859
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import org.tabletest.junit.TableTest;
2+
3+
class CalculatorTest {
4+
5+
@TableTest("""
6+
scenario | a | b | sum?
7+
positive | 1 | 2 | 3
8+
negative | -1 | -2 | -3
9+
""")
10+
void shouldAddNumbers(int a, int b, int sum) {
11+
assert a + b == sum;
12+
}
13+
}

testlib/src/test/java/com/diffplug/spotless/java/TableTestFormatterStepTest.java

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,81 @@ void behaviorTableFile() {
4343
}
4444
}
4545

46+
@Test
47+
void editorConfigChangesAreHonored() {
48+
// Verifies that a new FormatterStep picks up .editorconfig changes rather than
49+
// returning stale results from a shared cache.
50+
//
51+
// Two steps with the same version share a classloader (via SpotlessCache's
52+
// serialization-based key). If EditorConfigProvider were stored as a static field,
53+
// its permanent ec4j cache would survive across FormatterFunc instances, and the
54+
// second step would still see the indent_size=4 result cached by the first step.
55+
// Making EditorConfigProvider an instance field ensures each FormatterFunc gets a
56+
// fresh provider with an empty cache.
57+
58+
// Step 1: .editorconfig with indent_size=4
59+
setFile(".editorconfig").toContent("[*.java]\nindent_size = 4\n");
60+
FormatterStep step1 = TableTestFormatterStep.create(VERSION, TestProvisioner.mavenCentral());
61+
try (StepHarnessWithFile harness = StepHarnessWithFile.forStep(this, step1)) {
62+
harness.testResource("CalculatorTest.java",
63+
"java/tableTestFormatter/JavaCodeUnformatted.test",
64+
"java/tableTestFormatter/JavaCodeFormatted.test");
65+
}
66+
67+
// Step 2: change .editorconfig to indent_size=2, create a new step
68+
setFile(".editorconfig").toContent("[*.java]\nindent_size = 2\n");
69+
FormatterStep step2 = TableTestFormatterStep.create(VERSION, TestProvisioner.mavenCentral());
70+
try (StepHarnessWithFile harness = StepHarnessWithFile.forStep(this, step2)) {
71+
harness.testResource("CalculatorTest.java",
72+
"java/tableTestFormatter/JavaCodeUnformatted.test",
73+
"java/tableTestFormatter/JavaCodeFormattedWith2SpaceIndent.test");
74+
}
75+
}
76+
77+
@Test
78+
void behaviorWithConfiguredFallback() {
79+
// When no .editorconfig is present, the configured indentSize is used as the fallback
80+
// instead of the built-in default (Config.SPACES_4 = 4 spaces).
81+
// Configuring indentSize=2 should produce the same output as when editorconfig sets
82+
// indent_size=2.
83+
FormatterStep step = TableTestFormatterStep.create(VERSION, TestProvisioner.mavenCentral(), "space", 2);
84+
try (StepHarnessWithFile harness = StepHarnessWithFile.forStep(this, step)) {
85+
harness.testResource("CalculatorTest.java",
86+
"java/tableTestFormatter/JavaCodeUnformatted.test",
87+
"java/tableTestFormatter/JavaCodeFormattedWith2SpaceIndent.test");
88+
}
89+
}
90+
4691
@Test
4792
void equality() {
4893
new SerializableEqualityTester() {
4994
String version = VERSION;
95+
String indentStyle = TableTestFormatterStep.DEFAULT_INDENT_STYLE;
96+
int indentSize = TableTestFormatterStep.DEFAULT_INDENT_SIZE;
5097

5198
@Override
5299
protected void setupTest(API api) {
53-
// same version == same
100+
// same version + defaults == same
54101
api.areDifferentThan();
55102

56-
// change the version, and it's different
103+
// change the version
57104
version = "1.0.0";
58105
api.areDifferentThan();
106+
107+
// restore version, change indent size
108+
version = VERSION;
109+
indentSize = 2;
110+
api.areDifferentThan();
111+
112+
// change indent style
113+
indentSize = TableTestFormatterStep.DEFAULT_INDENT_SIZE;
114+
indentStyle = "tab";
115+
api.areDifferentThan();
59116
}
60117

61118
@Override
62119
protected FormatterStep create() {
63-
return TableTestFormatterStep.create(version, TestProvisioner.mavenCentral());
120+
return TableTestFormatterStep.create(version, TestProvisioner.mavenCentral(), indentStyle, indentSize);
64121
}
65122
}.testEquals();
66123
}

0 commit comments

Comments
 (0)