diff --git a/.editorconfig b/.editorconfig
index 1c19210c40..4042d549fd 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -25,3 +25,13 @@ indent_style = space
[*.{yml,yaml}]
indent_style = space
indent_size = 2
+
+# Prevent unexpected automatic indentation when crafting test-cases
+[/testlib/src/main/resources/**]
+charset = unset
+end_of_line = unset
+insert_final_newline = unset
+trim_trailing_whitespace = unset
+indent_style = unset
+indent_size = unset
+ij_formatter_enabled = false
diff --git a/CHANGES.md b/CHANGES.md
index 89c99b7841..70200b092f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -13,6 +13,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
### Added
* Support configuration of mirrors for P2 repositories in `EquoBasedStepBuilder` ([#1629](https://github.com/diffplug/spotless/issues/1629)).
* The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)).
+* Added formatter for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)).
### Changes
* **POTENTIALLY BREAKING** Converted `googleJavaFormat` to a compile-only dependency and drop support for versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630))
* Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630))
diff --git a/README.md b/README.md
index 662c624bdf..52628574d9 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ lib('generic.TrimTrailingWhitespaceStep') +'{{yes}} | {{yes}}
lib('antlr4.Antlr4FormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('cpp.ClangFormatStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
extra('cpp.EclipseFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
+lib('gherkin.GherkinUtilsStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
extra('groovy.GrEclipseFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
lib('java.GoogleJavaFormatStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
lib('java.ImportOrderStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
@@ -125,6 +126,7 @@ lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}}
| [`antlr4.Antlr4FormatterStep`](lib/src/main/java/com/diffplug/spotless/antlr4/Antlr4FormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`cpp.ClangFormatStep`](lib/src/main/java/com/diffplug/spotless/cpp/ClangFormatStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
| [`cpp.EclipseFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/cpp/EclipseFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
+| [`gherkin.GherkinUtilsStep`](lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`groovy.GrEclipseFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
| [`java.GoogleJavaFormatStep`](lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
| [`java.ImportOrderStep`](lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
diff --git a/lib/build.gradle b/lib/build.gradle
index a8eca18a57..3c8b6e75e4 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -18,7 +18,8 @@ def NEEDS_GLUE = [
'scalafmt',
'jackson',
'gson',
- 'cleanthat'
+ 'cleanthat',
+ 'gherkin'
]
for (glue in NEEDS_GLUE) {
sourceSets.register(glue) {
@@ -115,6 +116,9 @@ dependencies {
cleanthatCompileOnly 'io.github.solven-eu.cleanthat:java:2.6'
compatCleanthat2Dot1CompileAndTestOnly 'io.github.solven-eu.cleanthat:java:2.6'
+
+ gherkinCompileOnly 'io.cucumber:gherkin-utils:8.0.2'
+ gherkinCompileOnly 'org.slf4j:slf4j-api:2.0.0'
}
// we'll hold the core lib to a high standard
diff --git a/lib/src/gherkin/java/com/diffplug/spotless/glue/gherkin/GherkinUtilsFormatterFunc.java b/lib/src/gherkin/java/com/diffplug/spotless/glue/gherkin/GherkinUtilsFormatterFunc.java
new file mode 100644
index 0000000000..4b79347b29
--- /dev/null
+++ b/lib/src/gherkin/java/com/diffplug/spotless/glue/gherkin/GherkinUtilsFormatterFunc.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.glue.gherkin;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.diffplug.spotless.FormatterFunc;
+import com.diffplug.spotless.gherkin.GherkinUtilsConfig;
+
+import io.cucumber.gherkin.GherkinParser;
+import io.cucumber.gherkin.utils.pretty.Pretty;
+import io.cucumber.gherkin.utils.pretty.Syntax;
+import io.cucumber.messages.types.Envelope;
+import io.cucumber.messages.types.GherkinDocument;
+import io.cucumber.messages.types.Source;
+import io.cucumber.messages.types.SourceMediaType;
+
+public class GherkinUtilsFormatterFunc implements FormatterFunc {
+ private static final Logger LOGGER = LoggerFactory.getLogger(GherkinUtilsFormatterFunc.class);
+
+ private final GherkinUtilsConfig gherkinSimpleConfig;
+
+ public GherkinUtilsFormatterFunc(GherkinUtilsConfig gherkinSimpleConfig) {
+ this.gherkinSimpleConfig = gherkinSimpleConfig;
+ }
+
+ // Follows https://github.com/cucumber/gherkin-utils/blob/main/java/src/test/java/io/cucumber/gherkin/utils/pretty/PrettyTest.java
+ private GherkinDocument parse(String gherkin) {
+ GherkinParser parser = GherkinParser
+ .builder()
+ .includeSource(false)
+ .build();
+ return parser.parse(Envelope.of(new Source("test.feature", gherkin, SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN)))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("No envelope"))
+ .getGherkinDocument()
+ .orElseThrow(() -> new IllegalArgumentException("No gherkin document"));
+ }
+
+ @Override
+ public String apply(String inputString) {
+ GherkinDocument gherkinDocument = parse(inputString);
+
+ return Pretty.prettyPrint(gherkinDocument, Syntax.gherkin);
+ }
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsConfig.java b/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsConfig.java
new file mode 100644
index 0000000000..d7962c6543
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsConfig.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.gherkin;
+
+import java.io.Serializable;
+
+public class GherkinUtilsConfig implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ public static int defaultIndentSpaces() {
+ // https://cucumber.io/docs/gherkin/reference/
+ // Recommended indentation is 2 spaces
+ return 2;
+ }
+
+ final int indentSpaces;
+
+ public GherkinUtilsConfig(int indentSpaces) {
+ this.indentSpaces = indentSpaces;
+ }
+
+ public int getIndentSpaces() {
+ return indentSpaces;
+ }
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsStep.java b/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsStep.java
new file mode 100644
index 0000000000..78f5eacd32
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsStep.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021-2023 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.gherkin;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Objects;
+
+import com.diffplug.spotless.FormatterFunc;
+import com.diffplug.spotless.FormatterStep;
+import com.diffplug.spotless.JarState;
+import com.diffplug.spotless.Provisioner;
+
+public class GherkinUtilsStep {
+ private static final String MAVEN_COORDINATE = "io.cucumber:gherkin-utils:";
+ private static final String DEFAULT_VERSION = "8.0.2";
+
+ public static String defaultVersion() {
+ return DEFAULT_VERSION;
+ }
+
+ public static FormatterStep create(GherkinUtilsConfig gherkinSimpleConfig,
+ String formatterVersion, Provisioner provisioner) {
+ Objects.requireNonNull(provisioner, "provisioner cannot be null");
+ return FormatterStep.createLazy("gherkin", () -> new GherkinUtilsStep.State(gherkinSimpleConfig, formatterVersion, provisioner), GherkinUtilsStep.State::toFormatter);
+ }
+
+ private static final class State implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final GherkinUtilsConfig gherkinSimpleConfig;
+ private final JarState jarState;
+
+ private State(GherkinUtilsConfig gherkinSimpleConfig, String formatterVersion, Provisioner provisioner) throws IOException {
+ this.gherkinSimpleConfig = gherkinSimpleConfig;
+ this.jarState = JarState.from(MAVEN_COORDINATE + formatterVersion, provisioner);
+ }
+
+ FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
+ InstantiationException, IllegalAccessException {
+ Class> formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.gherkin.GherkinUtilsFormatterFunc");
+ Constructor> constructor = formatterFunc.getConstructor(GherkinUtilsConfig.class);
+ return (FormatterFunc) constructor.newInstance(gherkinSimpleConfig);
+ }
+ }
+
+ private GherkinUtilsStep() {
+ // cannot be directly instantiated
+ }
+}
diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md
index 0d339cc58a..6c7268eab6 100644
--- a/plugin-gradle/CHANGES.md
+++ b/plugin-gradle/CHANGES.md
@@ -15,6 +15,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
Mirrors are selected by prefix match, for example `https://download.eclipse.org/eclipse/updates/4.26/` will be redirected to `https://some.internal.mirror/eclipse/eclipse/updates/4.26/`.
The same configuration exists for `greclipse` and `eclipseCdt`.
* The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)).
+* Added support for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)).
### Changes
* **POTENTIALLY BREAKING** Drop support for `googleJavaFormat` versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630))
* Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630))
diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md
index 95ca3af5ff..5ae9d2bd95 100644
--- a/plugin-gradle/README.md
+++ b/plugin-gradle/README.md
@@ -66,6 +66,8 @@ Spotless supports all of Gradle's built-in performance features (incremental bui
- [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier), [ESLint](#eslint-typescript))
- [Javascript](#javascript) ([prettier](#prettier), [ESLint](#eslint-javascript))
- [JSON](#json)
+ - [YAML](#yaml)
+ - [Gherkin](#gherkin)
- Multiple languages
- [Prettier](#prettier) ([plugins](#prettier-plugins), [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection), [caching `npm install` results](#caching-results-of-npm-install))
- javascript, jsx, angular, vue, flow, typescript, css, less, scss, html, json, graphql, markdown, ymaml
@@ -850,7 +852,34 @@ spotless {
}
```
-
+## Gherkin
+
+- `com.diffplug.gradle.spotless.GherkinExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.17.0/com/diffplug/gradle/spotless/GherkinExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GherkinExtension.java)
+
+```gradle
+spotless {
+ gherkin {
+ target 'src/**/*.feature' // you have to set the target manually
+ gherkinUtils() // has its own section below
+ }
+}
+```
+
+### gherkinUtils
+
+[homepage](https://github.com/cucumber/gherkin-utils). [changelog](https://github.com/cucumber/gherkin-utils/blob/main/CHANGELOG.md).
+
+Uses a Gherkin pretty-printer that optionally allows configuring the number of spaces that are used to pretty print objects:
+
+```gradle
+spotless {
+ gherkin {
+ target 'src/**/*.feature' // required to be set explicitly
+ gherkinUtils()
+ .version('8.0.2') // optional: custom version of 'io.cucumber:gherkin-utils'
+ }
+}
+```
## Prettier
diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GherkinExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GherkinExtension.java
new file mode 100644
index 0000000000..6305289818
--- /dev/null
+++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GherkinExtension.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016-2023 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.gradle.spotless;
+
+import javax.inject.Inject;
+
+import com.diffplug.spotless.FormatterStep;
+import com.diffplug.spotless.gherkin.GherkinUtilsStep;
+
+public class GherkinExtension extends FormatExtension {
+ static final String NAME = "gherkin";
+
+ @Inject
+ public GherkinExtension(SpotlessExtension spotless) {
+ super(spotless);
+ }
+
+ @Override
+ protected void setupTask(SpotlessTask task) {
+ if (target == null) {
+ throw noDefaultTargetException();
+ }
+ super.setupTask(task);
+ }
+
+ public GherkinUtilsConfig gherkinUtils() {
+ return new GherkinUtilsConfig();
+ }
+
+ public class GherkinUtilsConfig {
+ private String version;
+ private int indent;
+
+ public GherkinUtilsConfig() {
+ this.version = GherkinUtilsStep.defaultVersion();
+ this.indent = com.diffplug.spotless.gherkin.GherkinUtilsConfig.defaultIndentSpaces();
+ addStep(createStep());
+ }
+
+ public void version(String version) {
+ this.version = version;
+ replaceStep(createStep());
+ }
+
+ private FormatterStep createStep() {
+ return GherkinUtilsStep.create(new com.diffplug.spotless.gherkin.GherkinUtilsConfig(indent), version, provisioner());
+ }
+ }
+
+}
diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java
index 0a31a15764..e13e408cfd 100644
--- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java
+++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java
@@ -199,6 +199,12 @@ public void yaml(Action closure) {
format(YamlExtension.NAME, YamlExtension.class, closure);
}
+ /** Configures the special Gherkin-specific extension. */
+ public void gherkin(Action closure) {
+ requireNonNull(closure);
+ format(GherkinExtension.NAME, GherkinExtension.class, closure);
+ }
+
/** Configures a custom extension. */
public void format(String name, Action closure) {
requireNonNull(name, "name");
diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GherkinExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GherkinExtensionTest.java
new file mode 100644
index 0000000000..b78094cf18
--- /dev/null
+++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GherkinExtensionTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021-2023 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.gradle.spotless;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+
+public class GherkinExtensionTest extends GradleIntegrationHarness {
+ @Test
+ public void defaultFormatting() throws IOException {
+ setFile("build.gradle").toLines(
+ "plugins {",
+ " id 'java'",
+ " id 'com.diffplug.spotless'",
+ "}",
+ "repositories { mavenCentral() }",
+ "spotless {",
+ " gherkin {",
+ " target 'examples/**/*.feature'",
+ " gherkinUtils()",
+ " }",
+ "}");
+ setFile("src/main/resources/example.feature").toResource("gherkin/minimalBefore.feature");
+ setFile("examples/main/resources/example.feature").toResource("gherkin/minimalBefore.feature");
+ gradleRunner().withArguments("spotlessApply").build();
+ assertFile("src/main/resources/example.feature").sameAsResource("gherkin/minimalBefore.feature");
+ assertFile("examples/main/resources/example.feature").sameAsResource("gherkin/minimalAfter.feature");
+ }
+
+}
diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md
index b20978647a..231d68e0e7 100644
--- a/plugin-maven/CHANGES.md
+++ b/plugin-maven/CHANGES.md
@@ -5,10 +5,12 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
## [Unreleased]
### Added
* The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)).
+* Added support for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)).
+### Fixed
+* Fix non deterministic computation of cache fingerprint when using multiple formatters. ([#1643](https://github.com/diffplug/spotless/pull/1643) fixes [#1642](https://github.com/diffplug/spotless/pull/1642))
### Changes
* **POTENTIALLY BREAKING** Drop support for `googleJavaFormat` versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630))
* Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630))
-* Fix non deterministic computation of cache fingerprint when using multiple formatters. ([#1643](https://github.com/diffplug/spotless/pull/1643) fixes [#1642](https://github.com/diffplug/spotless/pull/1642))
## [2.35.0] - 2023-03-13
### Added
diff --git a/plugin-maven/README.md b/plugin-maven/README.md
index 12ade2ccf5..b12d777b29 100644
--- a/plugin-maven/README.md
+++ b/plugin-maven/README.md
@@ -53,6 +53,7 @@ user@machine repo % mvn spotless:check
- [Javascript](#javascript) ([prettier](#prettier), [ESLint](#eslint-javascript))
- [JSON](#json)
- [YAML](#yaml)
+ - [Gherkin](#gherkin)
- Multiple languages
- [Prettier](#prettier) ([plugins](#prettier-plugins), [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection), [caching `npm install` results](#caching-results-of-npm-install))
- [eclipse web tools platform](#eclipse-web-tools-platform)
@@ -974,7 +975,33 @@ Uses Jackson and YAMLFactory to pretty print objects:
```
-
+## Gherkin
+
+- `com.diffplug.spotless.maven.FormatterFactory.addStepFactory(FormatterStepFactory)` [code](https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/Gherkin.java)
+
+```gradle
+
+
+
+ src/**/*.feature
+
+
+
+
+
+```
+
+### gherkinUtils
+
+[homepage](https://github.com/cucumber/gherkin-utils). [changelog](https://github.com/cucumber/gherkin-utils/blob/main/CHANGELOG.md).
+
+Uses a Gherkin pretty-printer that optionally allows configuring the number of spaces that are used to pretty print objects:
+
+```xml
+
+ 8.0.2
+
+```
## Prettier
diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java
index 0c49a32ab2..0b019ea844 100644
--- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java
+++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java
@@ -61,6 +61,7 @@
import com.diffplug.spotless.maven.cpp.Cpp;
import com.diffplug.spotless.maven.generic.Format;
import com.diffplug.spotless.maven.generic.LicenseHeader;
+import com.diffplug.spotless.maven.gherkin.Gherkin;
import com.diffplug.spotless.maven.groovy.Groovy;
import com.diffplug.spotless.maven.incremental.UpToDateChecker;
import com.diffplug.spotless.maven.incremental.UpToDateChecking;
@@ -180,6 +181,9 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo {
@Parameter
private Yaml yaml;
+ @Parameter
+ private Gherkin gherkin;
+
@Parameter(property = "spotlessFiles")
private String filePatterns;
@@ -354,7 +358,7 @@ private FileLocator getFileLocator() {
}
private List getFormatterFactories() {
- return Stream.concat(formats.stream(), Stream.of(groovy, java, scala, kotlin, cpp, typescript, javascript, antlr4, pom, sql, python, markdown, json, yaml))
+ return Stream.concat(formats.stream(), Stream.of(groovy, java, scala, kotlin, cpp, typescript, javascript, antlr4, pom, sql, python, markdown, json, yaml, gherkin))
.filter(Objects::nonNull)
.collect(toList());
}
diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/Gherkin.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/Gherkin.java
new file mode 100644
index 0000000000..60181d048a
--- /dev/null
+++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/Gherkin.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.maven.gherkin;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.maven.project.MavenProject;
+
+import com.diffplug.spotless.maven.FormatterFactory;
+
+/**
+ * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element.
+ */
+public class Gherkin extends FormatterFactory {
+ @Override
+ public Set defaultIncludes(MavenProject project) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public String licenseHeaderDelimiter() {
+ return null;
+ }
+
+ public void addGherkinUtils(GherkinUtils gherkinUtils) {
+ addStepFactory(gherkinUtils);
+ }
+
+}
diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/GherkinUtils.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/GherkinUtils.java
new file mode 100644
index 0000000000..2eeabb9ab5
--- /dev/null
+++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/GherkinUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.maven.gherkin;
+
+import org.apache.maven.plugins.annotations.Parameter;
+
+import com.diffplug.spotless.FormatterStep;
+import com.diffplug.spotless.gherkin.GherkinUtilsConfig;
+import com.diffplug.spotless.gherkin.GherkinUtilsStep;
+import com.diffplug.spotless.maven.FormatterFactory;
+import com.diffplug.spotless.maven.FormatterStepConfig;
+import com.diffplug.spotless.maven.FormatterStepFactory;
+
+/**
+ * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element.
+ */
+public class GherkinUtils implements FormatterStepFactory {
+
+ @Parameter
+ private String version = GherkinUtilsStep.defaultVersion();
+
+ @Parameter
+ private int indentWithSpaces = GherkinUtilsConfig.defaultIndentSpaces();
+
+ @Override
+ public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) {
+ return GherkinUtilsStep.create(new GherkinUtilsConfig(indentWithSpaces), version, stepConfig.getProvisioner());
+ }
+}
diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java
index 2891ee3b2c..216502bc07 100644
--- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java
+++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java
@@ -174,6 +174,10 @@ protected void writePomWithYamlSteps(String... steps) throws IOException {
writePom(groupWithSteps("yaml", including("**/*.yaml"), steps));
}
+ protected void writePomWithGherkinSteps(String... steps) throws IOException {
+ writePom(groupWithSteps("gherkin", including("**/*.feature"), steps));
+ }
+
protected void writePom(String... configuration) throws IOException {
writePom(null, configuration, null, null);
}
diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/gherkin/GherkinTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/gherkin/GherkinTest.java
new file mode 100644
index 0000000000..95a60097d0
--- /dev/null
+++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/gherkin/GherkinTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2023 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.maven.gherkin;
+
+import org.junit.jupiter.api.Test;
+
+import com.diffplug.spotless.maven.MavenIntegrationHarness;
+
+public class GherkinTest extends MavenIntegrationHarness {
+ @Test
+ public void testFormatJson_WithSimple_defaultConfig_sortByKeys() throws Exception {
+ writePomWithGherkinSteps("");
+
+ setFile("examples/main/resources/example.feature").toResource("gherkin/minimalBefore.feature");
+ mavenRunner().withArguments("spotless:apply").runNoError();
+ assertFile("examples/main/resources/example.feature").sameAsResource("gherkin/minimalAfter.feature");
+ }
+
+}
diff --git a/testlib/src/main/resources/gherkin/complex_backgroundAfter.feature b/testlib/src/main/resources/gherkin/complex_backgroundAfter.feature
new file mode 100644
index 0000000000..5a9553d98d
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/complex_backgroundAfter.feature
@@ -0,0 +1,24 @@
+Feature: Complex background
+ We want to ensure PickleStep all have different IDs
+
+ Background: a simple background
+ Given the minimalism inside a background
+
+ Scenario: minimalistic
+ Given the minimalism
+
+ Scenario: also minimalistic
+ Given the minimalism
+
+ Rule: My Rule
+
+ Background:
+ Given a rule background step
+
+ Scenario: with examples
+ Given the minimalism
+
+ Examples:
+ | value |
+ | 1 |
+ | 2 |
diff --git a/testlib/src/main/resources/gherkin/complex_backgroundBefore.feature b/testlib/src/main/resources/gherkin/complex_backgroundBefore.feature
new file mode 100644
index 0000000000..fde7c94d9b
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/complex_backgroundBefore.feature
@@ -0,0 +1,24 @@
+Feature: Complex background
+ We want to ensure PickleStep all have different IDs
+
+ Background: a simple background
+ Given the minimalism inside a background
+
+ Scenario: minimalistic
+ Given the minimalism
+
+ Scenario: also minimalistic
+ Given the minimalism
+
+ Rule: My Rule
+
+ Background:
+ Given a rule background step
+
+ Scenario: with examples
+ Given the minimalism
+
+ Examples:
+ | value |
+ | 1 |
+ | 2 |
diff --git a/testlib/src/main/resources/gherkin/descriptionsAfter.feature b/testlib/src/main/resources/gherkin/descriptionsAfter.feature
new file mode 100644
index 0000000000..9c0eb7e303
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/descriptionsAfter.feature
@@ -0,0 +1,55 @@
+Feature: Descriptions everywhere
+ This is a single line description
+
+ Scenario: two lines
+ This description
+ has two lines and indented with two spaces
+
+ Given the minimalism
+
+ Scenario: without indentation
+This is a description without indentation
+
+ Given the minimalism
+
+ Scenario: empty lines in the middle
+ This description
+
+ has an empty line in the middle
+
+ Given the minimalism
+
+ Scenario: empty lines around
+ This description
+ has an empty lines around
+
+ Given the minimalism
+
+ Scenario: comment after description
+ This description
+ has a comment after
+
+ # this is a comment
+ Given the minimalism
+
+ Scenario: comment right after description
+ This description
+ has a comment right after
+
+ # this is another comment
+ Given the minimalism
+
+ Scenario: description with escaped docstring separator
+ This description has an \"\"\" (escaped docstring sparator)
+
+ Given the minimalism
+
+ Scenario Outline: scenario outline with a description
+This is a scenario outline description
+
+ Given the minimalism
+
+ Examples: examples with description
+This is an examples description
+ | foo |
+ | bar |
diff --git a/testlib/src/main/resources/gherkin/descriptionsBefore.feature b/testlib/src/main/resources/gherkin/descriptionsBefore.feature
new file mode 100644
index 0000000000..690ae3a754
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/descriptionsBefore.feature
@@ -0,0 +1,51 @@
+Feature: Descriptions everywhere
+ This is a single line description
+
+ Scenario: two lines
+ This description
+ has two lines and indented with two spaces
+ Given the minimalism
+
+Scenario: without indentation
+This is a description without indentation
+ Given the minimalism
+
+ Scenario: empty lines in the middle
+ This description
+
+ has an empty line in the middle
+ Given the minimalism
+
+ Scenario: empty lines around
+
+ This description
+ has an empty lines around
+
+ Given the minimalism
+
+ Scenario: comment after description
+ This description
+ has a comment after
+
+# this is a comment
+ Given the minimalism
+
+ Scenario: comment right after description
+ This description
+ has a comment right after
+ # this is another comment
+ Given the minimalism
+
+ Scenario: description with escaped docstring separator
+ This description has an \"\"\" (escaped docstring sparator)
+
+ Given the minimalism
+
+ Scenario Outline: scenario outline with a description
+This is a scenario outline description
+ Given the minimalism
+
+ Examples: examples with description
+This is an examples description
+ | foo |
+ | bar |
diff --git a/testlib/src/main/resources/gherkin/emptyAfter.feature b/testlib/src/main/resources/gherkin/emptyAfter.feature
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/testlib/src/main/resources/gherkin/emptyBefore.feature b/testlib/src/main/resources/gherkin/emptyBefore.feature
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/emptyBefore.feature
@@ -0,0 +1 @@
+
diff --git a/testlib/src/main/resources/gherkin/invalidGherkinAfter.feature b/testlib/src/main/resources/gherkin/invalidGherkinAfter.feature
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/testlib/src/main/resources/gherkin/invalidGherkinBefore.feature b/testlib/src/main/resources/gherkin/invalidGherkinBefore.feature
new file mode 100644
index 0000000000..665bf227ca
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/invalidGherkinBefore.feature
@@ -0,0 +1,9 @@
+
+invalid line here
+
+Feature: Multiple parser errors
+
+ Scenario: minimalistic
+ Given the minimalism
+
+ another invalid line here
diff --git a/testlib/src/main/resources/gherkin/minimalAfter.feature b/testlib/src/main/resources/gherkin/minimalAfter.feature
new file mode 100644
index 0000000000..9a62d86f80
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/minimalAfter.feature
@@ -0,0 +1,4 @@
+Feature: Minimal
+
+ Scenario: minimalistic
+ Given the minimalism
diff --git a/testlib/src/main/resources/gherkin/minimalAfter0Spaces.feature b/testlib/src/main/resources/gherkin/minimalAfter0Spaces.feature
new file mode 100644
index 0000000000..aa94821bbc
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/minimalAfter0Spaces.feature
@@ -0,0 +1,4 @@
+Feature: Minimal
+
+Scenario: minimalistic
+Given the minimalism
diff --git a/testlib/src/main/resources/gherkin/minimalAfter6Spaces.feature b/testlib/src/main/resources/gherkin/minimalAfter6Spaces.feature
new file mode 100644
index 0000000000..cbad451b45
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/minimalAfter6Spaces.feature
@@ -0,0 +1,4 @@
+Feature: Minimal
+
+ Scenario: minimalistic
+ Given the minimalism
diff --git a/testlib/src/main/resources/gherkin/minimalBefore.feature b/testlib/src/main/resources/gherkin/minimalBefore.feature
new file mode 100644
index 0000000000..a474cf1418
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/minimalBefore.feature
@@ -0,0 +1,3 @@
+Feature: Minimal
+Scenario: minimalistic
+Given the minimalism
diff --git a/testlib/src/main/resources/gherkin/notGherkinAfter.feature b/testlib/src/main/resources/gherkin/notGherkinAfter.feature
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/testlib/src/main/resources/gherkin/notGherkinBefore.feature b/testlib/src/main/resources/gherkin/notGherkinBefore.feature
new file mode 100644
index 0000000000..517db4bd1e
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/notGherkinBefore.feature
@@ -0,0 +1 @@
+not gherkin
diff --git a/testlib/src/main/resources/gherkin/rule_with_tagAfter.feature b/testlib/src/main/resources/gherkin/rule_with_tagAfter.feature
new file mode 100644
index 0000000000..bba6b044ac
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/rule_with_tagAfter.feature
@@ -0,0 +1,19 @@
+@tag_feature
+Feature: Some tagged rules
+
+ Rule: Untagged rule
+ The untagged rule description
+
+ Scenario: Scenario with only a feature tag
+ Given a
+
+ @tag_rule
+ Rule: Tagged rule
+ The tagged rule description
+
+ Scenario: Scenario with feature and rule tags
+ Given b
+
+ @tag_scenario
+ Scenario: Scenario with feature, rule and scenario tags
+ Given b
diff --git a/testlib/src/main/resources/gherkin/rule_with_tagBefore.feature b/testlib/src/main/resources/gherkin/rule_with_tagBefore.feature
new file mode 100644
index 0000000000..bba6b044ac
--- /dev/null
+++ b/testlib/src/main/resources/gherkin/rule_with_tagBefore.feature
@@ -0,0 +1,19 @@
+@tag_feature
+Feature: Some tagged rules
+
+ Rule: Untagged rule
+ The untagged rule description
+
+ Scenario: Scenario with only a feature tag
+ Given a
+
+ @tag_rule
+ Rule: Tagged rule
+ The tagged rule description
+
+ Scenario: Scenario with feature and rule tags
+ Given b
+
+ @tag_scenario
+ Scenario: Scenario with feature, rule and scenario tags
+ Given b
diff --git a/testlib/src/test/java/com/diffplug/spotless/gherkin/GherkinUtilsStepTest.java b/testlib/src/test/java/com/diffplug/spotless/gherkin/GherkinUtilsStepTest.java
new file mode 100644
index 0000000000..6bc478027b
--- /dev/null
+++ b/testlib/src/test/java/com/diffplug/spotless/gherkin/GherkinUtilsStepTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2021-2023 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.gherkin;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import com.diffplug.spotless.FormatterStep;
+import com.diffplug.spotless.SerializableEqualityTester;
+import com.diffplug.spotless.StepHarness;
+import com.diffplug.spotless.TestProvisioner;
+
+public class GherkinUtilsStepTest {
+
+ private static final String VERSION = GherkinUtilsStep.defaultVersion();
+ private static final int INDENT = GherkinUtilsConfig.defaultIndentSpaces();
+
+ private final FormatterStep step = GherkinUtilsStep.create(new GherkinUtilsConfig(INDENT), VERSION, TestProvisioner.mavenCentral());
+ private final StepHarness stepHarness = StepHarness.forStep(step);
+
+ @Test
+ public void cannotProvidedNullProvisioner() {
+ assertThatThrownBy(() -> GherkinUtilsStep.create(new GherkinUtilsConfig(INDENT), VERSION, null)).isInstanceOf(NullPointerException.class).hasMessage("provisioner cannot be null");
+ }
+
+ @Test
+ public void handlesEmptyFeature() throws Exception {
+ doWithResource(stepHarness, "empty");
+ }
+
+ @Test
+ public void handlesComplexBackground() throws Exception {
+ doWithResource(stepHarness, "complex_background");
+ }
+
+ @Test
+ public void handlesDescriptions() throws Exception {
+ doWithResource(stepHarness, "descriptions");
+ }
+
+ @Test
+ public void handlesRuleWithTag() throws Exception {
+ doWithResource(stepHarness, "rule_with_tag");
+ }
+
+ @Test
+ public void handlesInvalidGherkin() {
+ assertThatThrownBy(() -> doWithResource(stepHarness, "invalidGherkin"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("No gherkin document");
+ }
+
+ @Test
+ public void handlesNotGherkin() {
+ assertThatThrownBy(() -> doWithResource(stepHarness, "notGherkin"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("No gherkin document");
+ }
+
+ @Disabled("gherkin-utils does not allow custom indentation")
+ @Test
+ public void canSetCustomIndentationLevel() throws Exception {
+ FormatterStep step = GherkinUtilsStep.create(new GherkinUtilsConfig(6), VERSION, TestProvisioner.mavenCentral());
+ StepHarness stepHarness = StepHarness.forStep(step);
+
+ String before = "gherkin/minimalBefore.feature";
+ String after = "gherkin/minimalAfter6Spaces.feature";
+ stepHarness.testResource(before, after);
+ }
+
+ @Disabled("gherkin-utils does not allow custom indentation")
+ @Test
+ public void canSetIndentationLevelTo0() throws Exception {
+ FormatterStep step = GherkinUtilsStep.create(new GherkinUtilsConfig(0), VERSION, TestProvisioner.mavenCentral());
+ StepHarness stepHarness = StepHarness.forStep(step);
+
+ String before = "gherkin/minimalBefore.feature";
+ String after = "gherkin/minimalAfter0Spaces.feature";
+ stepHarness.testResource(before, after);
+ }
+
+ @Test
+ public void equality() {
+ new SerializableEqualityTester() {
+ int spaces = 0;
+
+ @Override
+ protected void setupTest(API api) {
+ // no changes, are the same
+ api.areDifferentThan();
+
+ // with different spacing
+ spaces = 1;
+ api.areDifferentThan();
+ }
+
+ @Override
+ protected FormatterStep create() {
+ return GherkinUtilsStep.create(new GherkinUtilsConfig(spaces), VERSION, TestProvisioner.mavenCentral());
+ }
+ }.testEquals();
+ }
+
+ private static void doWithResource(StepHarness stepHarness, String name) {
+ String before = String.format("gherkin/%sBefore.feature", name);
+ String after = String.format("gherkin/%sAfter.feature", name);
+ stepHarness.testResource(before, after);
+ }
+}