Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.io.Serial;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.Locale;
import java.util.Objects;

import com.diffplug.spotless.FormatterFunc;
Expand All @@ -27,34 +28,61 @@

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

/** Default fallback indent style ({@code "space"}) for Java/Kotlin files. */
public static final String DEFAULT_INDENT_STYLE = "space";
/** Default fallback indent size ({@code 4}) for Java/Kotlin files, matching {@code Config.SPACES_4}. */
public static final int DEFAULT_INDENT_SIZE = 4;

private final JarState.Promised jarState;
private final String version;
private final String indentStyle;
private final int indentSize;

private TableTestFormatterStep(JarState.Promised jarState, String version) {
private TableTestFormatterStep(JarState.Promised jarState, String version, String indentStyle, int indentSize) {
this.jarState = jarState;
this.version = version;
this.indentStyle = validateIndentStyle(indentStyle);
this.indentSize = validateIndentSize(indentSize);
}

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

/** Creates a step which formats {@code @TableTest} tables using the given version. */
/** Creates a step which formats {@code @TableTest} tables using the given version and default indent config. */
public static FormatterStep create(String version, Provisioner provisioner) {
return create(version, provisioner, DEFAULT_INDENT_STYLE, DEFAULT_INDENT_SIZE);
}

/**
* Creates a step which formats {@code @TableTest} tables using the given version and fallback indent config.
* <p>
* The fallback config is used when no {@code .editorconfig} is found or the lookup fails.
*
* @param version the tabletest-formatter-core version
* @param provisioner the jar provisioner
* @param indentStyle fallback indent style: {@code "space"} or {@code "tab"} (case-insensitive)
* @param indentSize fallback indent size (must be &gt;= 0)
*/
public static FormatterStep create(String version, Provisioner provisioner, String indentStyle, int indentSize) {
Objects.requireNonNull(version, "version");
Objects.requireNonNull(provisioner, "provisioner");
return FormatterStep.create(NAME,
new TableTestFormatterStep(JarState.promise(() -> JarState.from(MAVEN_COORDINATE + version, provisioner)), version),
new TableTestFormatterStep(JarState.promise(() -> JarState.from(MAVEN_COORDINATE + version, provisioner)), version, indentStyle, indentSize),
TableTestFormatterStep::equalityState,
State::createFormat);
}
Expand All @@ -65,26 +93,46 @@ public static String defaultVersion() {
}

private State equalityState() {
return new State(jarState.get(), version);
return new State(jarState.get(), version, indentStyle, indentSize);
}

public static String validateIndentStyle(String indentStyle) {
Objects.requireNonNull(indentStyle, "indentStyle");
String lower = indentStyle.toLowerCase(Locale.ROOT);
if (!lower.equals("space") && !lower.equals("tab")) {
throw new IllegalArgumentException("indentStyle must be 'space' or 'tab', got: " + indentStyle);
}
return lower;
}

public static int validateIndentSize(int indentSize) {
if (indentSize < 0) {
throw new IllegalArgumentException("indentSize must be >= 0, got: " + indentSize);
}
return indentSize;
}

private static final class State implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;

private final JarState jarState;
private final String version;
private final String indentStyle;
private final int indentSize;

State(JarState jarState, String version) {
State(JarState jarState, String version, String indentStyle, int indentSize) {
this.jarState = jarState;
this.version = version;
this.indentStyle = indentStyle;
this.indentSize = indentSize;
}

FormatterFunc createFormat() throws Exception {
ClassLoader classLoader = jarState.getClassLoader();
Class<?> formatterClazz = classLoader.loadClass("com.diffplug.spotless.glue.java.TableTestFormatterFunc");
Constructor<?> constructor = formatterClazz.getConstructor();
return (FormatterFunc.NeedsFile) constructor.newInstance();
Constructor<?> constructor = formatterClazz.getConstructor(String.class, int.class);
return (FormatterFunc.NeedsFile) constructor.newInstance(indentStyle, indentSize);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,57 @@
package com.diffplug.spotless.glue.java;

import java.io.File;
import java.util.Locale;

import org.tabletest.formatter.config.Config;
import org.tabletest.formatter.config.EditorConfigProvider;
import org.tabletest.formatter.config.IndentStyle;
import org.tabletest.formatter.core.SourceFileFormatter;
import org.tabletest.formatter.core.TableTestFormatter;

import com.diffplug.spotless.FormatterFunc;

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

private static final EditorConfigProvider CONFIG_PROVIDER = new EditorConfigProvider();
private final EditorConfigProvider CONFIG_PROVIDER = new EditorConfigProvider();
private final Config sourceFallbackConfig;

private final SourceFileFormatter sourceFormatter = new SourceFileFormatter();
private final TableTestFormatter tableFormatter = new TableTestFormatter();

/** Creates a formatter using the built-in default fallback ({@code Config.SPACES_4}). */
public TableTestFormatterFunc() {
this.sourceFallbackConfig = Config.SPACES_4;
}

/**
* Creates a formatter with a configured fallback indent style and size for Java/Kotlin files.
* Used when {@code .editorconfig} is absent or the lookup fails.
*
* @param indentStyle {@code "space"} or {@code "tab"} (case-insensitive)
* @param indentSize indent size (&gt;= 0)
*/
public TableTestFormatterFunc(String indentStyle, int indentSize) {
this.sourceFallbackConfig = new Config(IndentStyle.valueOf(indentStyle.toUpperCase(Locale.ROOT)), indentSize);
}

@Override
public String applyWithFile(String unix, File file) throws Exception {
String fileName = file.getName();

if (fileName.endsWith(".java") || fileName.endsWith(".kt")) {
Config config = CONFIG_PROVIDER.lookupConfig(file.toPath(), Config.SPACES_4);
Config config = CONFIG_PROVIDER.lookupConfig(file.toPath(), sourceFallbackConfig);
String formatted = sourceFormatter.format(unix, config);
return formatted.equals(unix) ? unix : formatted;
}
Expand Down
4 changes: 4 additions & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).

## [Unreleased]
### Added
- 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))
### Fixed
- 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))

## [8.4.0] - 2026-03-18
### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,37 @@ public TableTestFormatterConfig tableTestFormatter(String version) {

public class TableTestFormatterConfig {
private final String version;
private String indentStyle = TableTestFormatterStep.DEFAULT_INDENT_STYLE;
private int indentSize = TableTestFormatterStep.DEFAULT_INDENT_SIZE;

TableTestFormatterConfig(String version) {
this.version = version;
addStep(createStep());
}

/**
* Sets the fallback indent style used when no {@code .editorconfig} is found.
* Must be {@code "space"} or {@code "tab"} (case-insensitive).
* Defaults to {@code "space"}.
*/
public TableTestFormatterConfig withIndentStyle(String indentStyle) {
this.indentStyle = TableTestFormatterStep.validateIndentStyle(indentStyle);
replaceStep(createStep());
return this;
}

/**
* Sets the fallback indent size used when no {@code .editorconfig} is found.
* Must be &gt;= 0. Defaults to {@code 4}.
*/
public TableTestFormatterConfig withIndentSize(int indentSize) {
this.indentSize = TableTestFormatterStep.validateIndentSize(indentSize);
replaceStep(createStep());
return this;
}

private FormatterStep createStep() {
return TableTestFormatterStep.create(version, provisioner());
return TableTestFormatterStep.create(version, provisioner(), indentStyle, indentSize);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,37 @@
import com.diffplug.spotless.maven.FormatterStepFactory;

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

@Parameter
private String version;

/**
* Fallback indent style when no {@code .editorconfig} is found: {@code space} or {@code tab}.
* Defaults to {@code space}.
*/
@Parameter
private String indentStyle;

/**
* Fallback indent size when no {@code .editorconfig} is found. Must be &gt;= 0.
* Defaults to {@code 4}.
*/
@Parameter
private Integer indentSize;

@Override
public FormatterStep newFormatterStep(FormatterStepConfig config) {
String version = this.version != null ? this.version : TableTestFormatterStep.defaultVersion();
return TableTestFormatterStep.create(version, config.getProvisioner());
String resolvedVersion = this.version != null ? this.version : TableTestFormatterStep.defaultVersion();
String resolvedStyle = this.indentStyle != null
? TableTestFormatterStep.validateIndentStyle(this.indentStyle)
: TableTestFormatterStep.DEFAULT_INDENT_STYLE;
int resolvedSize = this.indentSize != null
? TableTestFormatterStep.validateIndentSize(this.indentSize)
: TableTestFormatterStep.DEFAULT_INDENT_SIZE;
return TableTestFormatterStep.create(resolvedVersion, config.getProvisioner(), resolvedStyle, resolvedSize);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import org.tabletest.junit.TableTest;

class CalculatorTest {

@TableTest("""
scenario | a | b | sum?
positive | 1 | 2 | 3
negative | -1 | -2 | -3
""")
void shouldAddNumbers(int a, int b, int sum) {
assert a + b == sum;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,81 @@ void behaviorTableFile() {
}
}

@Test
void editorConfigChangesAreHonored() {
// Verifies that a new FormatterStep picks up .editorconfig changes rather than
// returning stale results from a shared cache.
//
// Two steps with the same version share a classloader (via SpotlessCache's
// serialization-based key). If EditorConfigProvider were stored as a static field,
// its permanent ec4j cache would survive across FormatterFunc instances, and the
// second step would still see the indent_size=4 result cached by the first step.
// Making EditorConfigProvider an instance field ensures each FormatterFunc gets a
// fresh provider with an empty cache.

// Step 1: .editorconfig with indent_size=4
setFile(".editorconfig").toContent("[*.java]\nindent_size = 4\n");
FormatterStep step1 = TableTestFormatterStep.create(VERSION, TestProvisioner.mavenCentral());
try (StepHarnessWithFile harness = StepHarnessWithFile.forStep(this, step1)) {
harness.testResource("CalculatorTest.java",
"java/tableTestFormatter/JavaCodeUnformatted.test",
"java/tableTestFormatter/JavaCodeFormatted.test");
}

// Step 2: change .editorconfig to indent_size=2, create a new step
setFile(".editorconfig").toContent("[*.java]\nindent_size = 2\n");
FormatterStep step2 = TableTestFormatterStep.create(VERSION, TestProvisioner.mavenCentral());
try (StepHarnessWithFile harness = StepHarnessWithFile.forStep(this, step2)) {
harness.testResource("CalculatorTest.java",
"java/tableTestFormatter/JavaCodeUnformatted.test",
"java/tableTestFormatter/JavaCodeFormattedWith2SpaceIndent.test");
}
}

@Test
void behaviorWithConfiguredFallback() {
// When no .editorconfig is present, the configured indentSize is used as the fallback
// instead of the built-in default (Config.SPACES_4 = 4 spaces).
// Configuring indentSize=2 should produce the same output as when editorconfig sets
// indent_size=2.
FormatterStep step = TableTestFormatterStep.create(VERSION, TestProvisioner.mavenCentral(), "space", 2);
try (StepHarnessWithFile harness = StepHarnessWithFile.forStep(this, step)) {
harness.testResource("CalculatorTest.java",
"java/tableTestFormatter/JavaCodeUnformatted.test",
"java/tableTestFormatter/JavaCodeFormattedWith2SpaceIndent.test");
}
}

@Test
void equality() {
new SerializableEqualityTester() {
String version = VERSION;
String indentStyle = TableTestFormatterStep.DEFAULT_INDENT_STYLE;
int indentSize = TableTestFormatterStep.DEFAULT_INDENT_SIZE;

@Override
protected void setupTest(API api) {
// same version == same
// same version + defaults == same
api.areDifferentThan();

// change the version, and it's different
// change the version
version = "1.0.0";
api.areDifferentThan();

// restore version, change indent size
version = VERSION;
indentSize = 2;
api.areDifferentThan();

// change indent style
indentSize = TableTestFormatterStep.DEFAULT_INDENT_SIZE;
indentStyle = "tab";
api.areDifferentThan();
}

@Override
protected FormatterStep create() {
return TableTestFormatterStep.create(version, TestProvisioner.mavenCentral());
return TableTestFormatterStep.create(version, TestProvisioner.mavenCentral(), indentStyle, indentSize);
}
}.testEquals();
}
Expand Down
Loading