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); + } +}