Skip to content

Commit 4ca8189

Browse files
authored
Add support for gherkin (#1649)
2 parents 02b6f60 + 77190d4 commit 4ca8189

36 files changed

+824
-5
lines changed

.editorconfig

+10
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,13 @@ indent_style = space
2525
[*.{yml,yaml}]
2626
indent_style = space
2727
indent_size = 2
28+
29+
# Prevent unexpected automatic indentation when crafting test-cases
30+
[/testlib/src/main/resources/**]
31+
charset = unset
32+
end_of_line = unset
33+
insert_final_newline = unset
34+
trim_trailing_whitespace = unset
35+
indent_style = unset
36+
indent_size = unset
37+
ij_formatter_enabled = false

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1313
### Added
1414
* Support configuration of mirrors for P2 repositories in `EquoBasedStepBuilder` ([#1629](https://github.com/diffplug/spotless/issues/1629)).
1515
* The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)).
16+
* Added formatter for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)).
1617
### Changes
1718
* **POTENTIALLY BREAKING** Converted `googleJavaFormat` to a compile-only dependency and drop support for versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630))
1819
* Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630))

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ lib('generic.TrimTrailingWhitespaceStep') +'{{yes}} | {{yes}}
7777
lib('antlr4.Antlr4FormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
7878
lib('cpp.ClangFormatStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
7979
extra('cpp.EclipseFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
80+
lib('gherkin.GherkinUtilsStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
8081
extra('groovy.GrEclipseFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
8182
lib('java.GoogleJavaFormatStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
8283
lib('java.ImportOrderStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
@@ -125,6 +126,7 @@ lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}}
125126
| [`antlr4.Antlr4FormatterStep`](lib/src/main/java/com/diffplug/spotless/antlr4/Antlr4FormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
126127
| [`cpp.ClangFormatStep`](lib/src/main/java/com/diffplug/spotless/cpp/ClangFormatStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
127128
| [`cpp.EclipseFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/cpp/EclipseFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
129+
| [`gherkin.GherkinUtilsStep`](lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
128130
| [`groovy.GrEclipseFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
129131
| [`java.GoogleJavaFormatStep`](lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
130132
| [`java.ImportOrderStep`](lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java) | :+1: | :+1: | :+1: | :white_large_square: |

lib/build.gradle

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ def NEEDS_GLUE = [
1818
'scalafmt',
1919
'jackson',
2020
'gson',
21-
'cleanthat'
21+
'cleanthat',
22+
'gherkin'
2223
]
2324
for (glue in NEEDS_GLUE) {
2425
sourceSets.register(glue) {
@@ -115,6 +116,9 @@ dependencies {
115116

116117
cleanthatCompileOnly 'io.github.solven-eu.cleanthat:java:2.6'
117118
compatCleanthat2Dot1CompileAndTestOnly 'io.github.solven-eu.cleanthat:java:2.6'
119+
120+
gherkinCompileOnly 'io.cucumber:gherkin-utils:8.0.2'
121+
gherkinCompileOnly 'org.slf4j:slf4j-api:2.0.0'
118122
}
119123

120124
// we'll hold the core lib to a high standard
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2023 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.gherkin;
17+
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
21+
import com.diffplug.spotless.FormatterFunc;
22+
import com.diffplug.spotless.gherkin.GherkinUtilsConfig;
23+
24+
import io.cucumber.gherkin.GherkinParser;
25+
import io.cucumber.gherkin.utils.pretty.Pretty;
26+
import io.cucumber.gherkin.utils.pretty.Syntax;
27+
import io.cucumber.messages.types.Envelope;
28+
import io.cucumber.messages.types.GherkinDocument;
29+
import io.cucumber.messages.types.Source;
30+
import io.cucumber.messages.types.SourceMediaType;
31+
32+
public class GherkinUtilsFormatterFunc implements FormatterFunc {
33+
private static final Logger LOGGER = LoggerFactory.getLogger(GherkinUtilsFormatterFunc.class);
34+
35+
private final GherkinUtilsConfig gherkinSimpleConfig;
36+
37+
public GherkinUtilsFormatterFunc(GherkinUtilsConfig gherkinSimpleConfig) {
38+
this.gherkinSimpleConfig = gherkinSimpleConfig;
39+
}
40+
41+
// Follows https://github.com/cucumber/gherkin-utils/blob/main/java/src/test/java/io/cucumber/gherkin/utils/pretty/PrettyTest.java
42+
private GherkinDocument parse(String gherkin) {
43+
GherkinParser parser = GherkinParser
44+
.builder()
45+
.includeSource(false)
46+
.build();
47+
return parser.parse(Envelope.of(new Source("test.feature", gherkin, SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN)))
48+
.findFirst()
49+
.orElseThrow(() -> new IllegalArgumentException("No envelope"))
50+
.getGherkinDocument()
51+
.orElseThrow(() -> new IllegalArgumentException("No gherkin document"));
52+
}
53+
54+
@Override
55+
public String apply(String inputString) {
56+
GherkinDocument gherkinDocument = parse(inputString);
57+
58+
return Pretty.prettyPrint(gherkinDocument, Syntax.gherkin);
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2023 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.gherkin;
17+
18+
import java.io.Serializable;
19+
20+
public class GherkinUtilsConfig implements Serializable {
21+
private static final long serialVersionUID = 1L;
22+
23+
public static int defaultIndentSpaces() {
24+
// https://cucumber.io/docs/gherkin/reference/
25+
// Recommended indentation is 2 spaces
26+
return 2;
27+
}
28+
29+
final int indentSpaces;
30+
31+
public GherkinUtilsConfig(int indentSpaces) {
32+
this.indentSpaces = indentSpaces;
33+
}
34+
35+
public int getIndentSpaces() {
36+
return indentSpaces;
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2021-2023 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.gherkin;
17+
18+
import java.io.IOException;
19+
import java.io.Serializable;
20+
import java.lang.reflect.Constructor;
21+
import java.lang.reflect.InvocationTargetException;
22+
import java.util.Objects;
23+
24+
import com.diffplug.spotless.FormatterFunc;
25+
import com.diffplug.spotless.FormatterStep;
26+
import com.diffplug.spotless.JarState;
27+
import com.diffplug.spotless.Provisioner;
28+
29+
public class GherkinUtilsStep {
30+
private static final String MAVEN_COORDINATE = "io.cucumber:gherkin-utils:";
31+
private static final String DEFAULT_VERSION = "8.0.2";
32+
33+
public static String defaultVersion() {
34+
return DEFAULT_VERSION;
35+
}
36+
37+
public static FormatterStep create(GherkinUtilsConfig gherkinSimpleConfig,
38+
String formatterVersion, Provisioner provisioner) {
39+
Objects.requireNonNull(provisioner, "provisioner cannot be null");
40+
return FormatterStep.createLazy("gherkin", () -> new GherkinUtilsStep.State(gherkinSimpleConfig, formatterVersion, provisioner), GherkinUtilsStep.State::toFormatter);
41+
}
42+
43+
private static final class State implements Serializable {
44+
private static final long serialVersionUID = 1L;
45+
46+
private final GherkinUtilsConfig gherkinSimpleConfig;
47+
private final JarState jarState;
48+
49+
private State(GherkinUtilsConfig gherkinSimpleConfig, String formatterVersion, Provisioner provisioner) throws IOException {
50+
this.gherkinSimpleConfig = gherkinSimpleConfig;
51+
this.jarState = JarState.from(MAVEN_COORDINATE + formatterVersion, provisioner);
52+
}
53+
54+
FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
55+
InstantiationException, IllegalAccessException {
56+
Class<?> formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.gherkin.GherkinUtilsFormatterFunc");
57+
Constructor<?> constructor = formatterFunc.getConstructor(GherkinUtilsConfig.class);
58+
return (FormatterFunc) constructor.newInstance(gherkinSimpleConfig);
59+
}
60+
}
61+
62+
private GherkinUtilsStep() {
63+
// cannot be directly instantiated
64+
}
65+
}

plugin-gradle/CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1515
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/`.
1616
The same configuration exists for `greclipse` and `eclipseCdt`.
1717
* The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)).
18+
* Added support for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)).
1819
### Changes
1920
* **POTENTIALLY BREAKING** Drop support for `googleJavaFormat` versions &lt; `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630))
2021
* Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630))

plugin-gradle/README.md

+30-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ Spotless supports all of Gradle's built-in performance features (incremental bui
6666
- [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier), [ESLint](#eslint-typescript))
6767
- [Javascript](#javascript) ([prettier](#prettier), [ESLint](#eslint-javascript))
6868
- [JSON](#json)
69+
- [YAML](#yaml)
70+
- [Gherkin](#gherkin)
6971
- Multiple languages
7072
- [Prettier](#prettier) ([plugins](#prettier-plugins), [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection), [caching `npm install` results](#caching-results-of-npm-install))
7173
- javascript, jsx, angular, vue, flow, typescript, css, less, scss, html, json, graphql, markdown, ymaml
@@ -850,7 +852,34 @@ spotless {
850852
}
851853
```
852854
853-
<a name="applying-prettier-to-javascript--flow--typescript--css--scss--less--jsx--graphql--yaml--etc"></a>
855+
## Gherkin
856+
857+
- `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)
858+
859+
```gradle
860+
spotless {
861+
gherkin {
862+
target 'src/**/*.feature' // you have to set the target manually
863+
gherkinUtils() // has its own section below
864+
}
865+
}
866+
```
867+
868+
### gherkinUtils
869+
870+
[homepage](https://github.com/cucumber/gherkin-utils). [changelog](https://github.com/cucumber/gherkin-utils/blob/main/CHANGELOG.md).
871+
872+
Uses a Gherkin pretty-printer that optionally allows configuring the number of spaces that are used to pretty print objects:
873+
874+
```gradle
875+
spotless {
876+
gherkin {
877+
target 'src/**/*.feature' // required to be set explicitly
878+
gherkinUtils()
879+
.version('8.0.2') // optional: custom version of 'io.cucumber:gherkin-utils'
880+
}
881+
}
882+
```
854883
855884
## Prettier
856885
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2016-2023 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.gradle.spotless;
17+
18+
import javax.inject.Inject;
19+
20+
import com.diffplug.spotless.FormatterStep;
21+
import com.diffplug.spotless.gherkin.GherkinUtilsStep;
22+
23+
public class GherkinExtension extends FormatExtension {
24+
static final String NAME = "gherkin";
25+
26+
@Inject
27+
public GherkinExtension(SpotlessExtension spotless) {
28+
super(spotless);
29+
}
30+
31+
@Override
32+
protected void setupTask(SpotlessTask task) {
33+
if (target == null) {
34+
throw noDefaultTargetException();
35+
}
36+
super.setupTask(task);
37+
}
38+
39+
public GherkinUtilsConfig gherkinUtils() {
40+
return new GherkinUtilsConfig();
41+
}
42+
43+
public class GherkinUtilsConfig {
44+
private String version;
45+
private int indent;
46+
47+
public GherkinUtilsConfig() {
48+
this.version = GherkinUtilsStep.defaultVersion();
49+
this.indent = com.diffplug.spotless.gherkin.GherkinUtilsConfig.defaultIndentSpaces();
50+
addStep(createStep());
51+
}
52+
53+
public void version(String version) {
54+
this.version = version;
55+
replaceStep(createStep());
56+
}
57+
58+
private FormatterStep createStep() {
59+
return GherkinUtilsStep.create(new com.diffplug.spotless.gherkin.GherkinUtilsConfig(indent), version, provisioner());
60+
}
61+
}
62+
63+
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ public void yaml(Action<YamlExtension> closure) {
199199
format(YamlExtension.NAME, YamlExtension.class, closure);
200200
}
201201

202+
/** Configures the special Gherkin-specific extension. */
203+
public void gherkin(Action<GherkinExtension> closure) {
204+
requireNonNull(closure);
205+
format(GherkinExtension.NAME, GherkinExtension.class, closure);
206+
}
207+
202208
/** Configures a custom extension. */
203209
public void format(String name, Action<FormatExtension> closure) {
204210
requireNonNull(name, "name");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2021-2023 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.gradle.spotless;
17+
18+
import java.io.IOException;
19+
20+
import org.junit.jupiter.api.Test;
21+
22+
public class GherkinExtensionTest extends GradleIntegrationHarness {
23+
@Test
24+
public void defaultFormatting() throws IOException {
25+
setFile("build.gradle").toLines(
26+
"plugins {",
27+
" id 'java'",
28+
" id 'com.diffplug.spotless'",
29+
"}",
30+
"repositories { mavenCentral() }",
31+
"spotless {",
32+
" gherkin {",
33+
" target 'examples/**/*.feature'",
34+
" gherkinUtils()",
35+
" }",
36+
"}");
37+
setFile("src/main/resources/example.feature").toResource("gherkin/minimalBefore.feature");
38+
setFile("examples/main/resources/example.feature").toResource("gherkin/minimalBefore.feature");
39+
gradleRunner().withArguments("spotlessApply").build();
40+
assertFile("src/main/resources/example.feature").sameAsResource("gherkin/minimalBefore.feature");
41+
assertFile("examples/main/resources/example.feature").sameAsResource("gherkin/minimalAfter.feature");
42+
}
43+
44+
}

0 commit comments

Comments
 (0)