diff --git a/.agents/skills/openfasttrace/SKILL.md b/.agents/skills/openfasttrace/SKILL.md
index 68debdbce..2e25ae04b 100644
--- a/.agents/skills/openfasttrace/SKILL.md
+++ b/.agents/skills/openfasttrace/SKILL.md
@@ -71,6 +71,28 @@ Multiple coverage:
## Tracing
+## Syntax: Gherkin
+
+Gherkin `.feature` files can define OFT scenario items. Put exactly one OFT ID
+in the contiguous tag region immediately before a `Scenario` or `Scenario
+Outline`. Optional `# Covers:` and `# Needs:` comments belong between the tags
+and the scenario header. Multiple `Covers` comments accumulate IDs; `Needs`
+may appear once.
+
+```gherkin
+@id:scn~user-login~1
+# Covers: req~authentication~1
+# Needs: dsn, itest
+Scenario: User logs in
+ Given a registered user
+ When valid credentials are entered
+ Then access is granted
+```
+
+Legacy coverage tags are recognized only in Gherkin comments, for example
+`# [impl~login~1 -> dsn~authentication~1]`. Executable Gherkin lines are not
+evaluated for coverage tags.
+
Tracing can be performed via CLI, Maven, or Gradle.
### CLI Usage
diff --git a/api/src/main/java/org/itsallcode/openfasttrace/api/importer/ImportEventListener.java b/api/src/main/java/org/itsallcode/openfasttrace/api/importer/ImportEventListener.java
index aedb7ef5c..19d5ca7e7 100644
--- a/api/src/main/java/org/itsallcode/openfasttrace/api/importer/ImportEventListener.java
+++ b/api/src/main/java/org/itsallcode/openfasttrace/api/importer/ImportEventListener.java
@@ -35,7 +35,7 @@ public interface ImportEventListener
/**
* The importer found the status of the specification item
- *
+ *
* @param status
* the status
*/
@@ -94,7 +94,7 @@ public interface ImportEventListener
/**
* Add a tag
- *
+ *
* @param tag
* the tag
*/
@@ -102,7 +102,7 @@ public interface ImportEventListener
/**
* Set the location of the specification item in the imported file
- *
+ *
* @param path
* the path of the imported file
* @param line
@@ -117,19 +117,40 @@ public interface ImportEventListener
/**
* Set the location of the specification item in the imported file
- *
+ *
* @param location
* the location
*/
void setLocation(Location location);
/**
- * Set to {@code true} if the specification item forwards needed
- * coverage
- *
+ * Set to {@code true} if the specification item forwards needed coverage
+ *
* @param forwards
* {@code true} if the specification item forwards needed
* coverage
*/
void setForwards(boolean forwards);
+
+ /**
+ * Add a complete specification item to the list of items being built. Use
+ * this method if the specification item is already complete and does not
+ * need to be built from events, e.g. when the item is specified in a single
+ * line and does not span multiple lines in the input file.
+ *
+ * This is equivalent to the following sequence of method calls:
+ *
+ * - {@link #beginSpecificationItem()}
+ * - {@link #setId(SpecificationItemId)}
+ * - {@link #addCoveredId(SpecificationItemId)}
+ * - {@link #addNeededArtifactType(String)}
+ * - {@link #setLocation(String, int)}
+ * - ...
+ * - {@link #endSpecificationItem()}
+ *
+ *
+ * @param item
+ * the complete specification item
+ */
+ void addSpecificationItem(final SpecificationItem item);
}
diff --git a/api/src/main/java/org/itsallcode/openfasttrace/api/importer/SpecificationListBuilder.java b/api/src/main/java/org/itsallcode/openfasttrace/api/importer/SpecificationListBuilder.java
index a99ceb6c1..f3bc5d5db 100644
--- a/api/src/main/java/org/itsallcode/openfasttrace/api/importer/SpecificationListBuilder.java
+++ b/api/src/main/java/org/itsallcode/openfasttrace/api/importer/SpecificationListBuilder.java
@@ -28,7 +28,7 @@ private SpecificationListBuilder(final FilterSettings filterSettings)
/**
* Creates a new {@link SpecificationListBuilder}.
- *
+ *
* @return a new {@link SpecificationListBuilder}.
*/
public static SpecificationListBuilder create()
@@ -39,7 +39,7 @@ public static SpecificationListBuilder create()
/**
* Creates a new {@link SpecificationListBuilder} with the given
* {@link FilterSettings}.
- *
+ *
* @param filterSettings
* the filter settings for the new builder.
* @return a new {@link SpecificationListBuilder}.
@@ -139,7 +139,7 @@ public void addTag(final String tag)
public List build()
{
this.endSpecificationItem();
- return Collections.unmodifiableList(this.items) ;
+ return Collections.unmodifiableList(this.items);
}
/**
@@ -177,14 +177,20 @@ public void endSpecificationItem()
{
final SpecificationItem item = createNewSpecificationItem();
// [impl->dsn~filtering-by-artifact-types-during-import~1]
- if (isAccepted(item))
- {
- addNewItemToList(item);
- }
+ addSpecificationItem(item);
}
resetState();
}
+ @Override
+ public void addSpecificationItem(final SpecificationItem item)
+ {
+ if (isAccepted(item))
+ {
+ addNewItemToList(item);
+ }
+ }
+
// [impl->dsn~cleaning-imported-multi-line-text-elements~1]
private SpecificationItem createNewSpecificationItem()
{
diff --git a/api/src/test/java/org/itsallcode/openfasttrace/api/importer/TestSpecificationListBuilder.java b/api/src/test/java/org/itsallcode/openfasttrace/api/importer/TestSpecificationListBuilder.java
index 890d113e1..8acc9209d 100644
--- a/api/src/test/java/org/itsallcode/openfasttrace/api/importer/TestSpecificationListBuilder.java
+++ b/api/src/test/java/org/itsallcode/openfasttrace/api/importer/TestSpecificationListBuilder.java
@@ -1,8 +1,7 @@
package org.itsallcode.openfasttrace.api.importer;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.assertAll;
import java.util.*;
@@ -57,6 +56,17 @@ void testBuildWithTags()
assertThat(items.get(0).getTags(), containsInAnyOrder("foo", "bar"));
}
+ @Test
+ void testAddSpecificationItem()
+ {
+ final SpecificationItem item = SpecificationItem.builder().id(ID).build();
+ final SpecificationListBuilder builder = SpecificationListBuilder.create();
+
+ builder.addSpecificationItem(item);
+
+ assertThat(builder.build(), contains(item));
+ }
+
// [utest->dsn~filtering-by-artifact-types-during-import~1]
@Test
void testFilterArtifactOfType()
@@ -153,10 +163,8 @@ void testDuplicateIdNotIgnored()
@Test
void testFilterSpecificationItemsByStatus()
{
- final Set wantedStatuses = new HashSet<>();
- wantedStatuses.add(ItemStatus.DRAFT);
- final FilterSettings filterSettings = FilterSettings.builder() //
- .wantedStatuses(wantedStatuses) //
+ final FilterSettings filterSettings = FilterSettings.builder()
+ .wantedStatuses(Set.of(ItemStatus.DRAFT))
.build();
final SpecificationListBuilder builder = SpecificationListBuilder
.createWithFilter(filterSettings);
@@ -164,7 +172,8 @@ void testFilterSpecificationItemsByStatus()
addItemWithStatus(builder, "out-B", ItemStatus.APPROVED);
addItemWithStatus(builder, "out-C", ItemStatus.PROPOSED);
addItemWithStatus(builder, "out-D", ItemStatus.REJECTED);
- addItemWithStatus(builder, "out-E", null); // becomes APPROVED by default
+ // out-E becomes APPROVED by default
+ addItemWithStatus(builder, "out-E", null);
final List items = builder.build();
assertThat(items.stream().map(SpecificationItem::getName).toList(),
containsInAnyOrder("in-A"));
@@ -255,7 +264,6 @@ void testMultilineTextFieldsGetTrimmed()
assertAll(
() -> assertThat(item.getComment(), equalTo("a comment")),
() -> assertThat(item.getDescription(), equalTo("a description")),
- () -> assertThat(item.getRationale(), equalTo("a rationale"))
- );
+ () -> assertThat(item.getRationale(), equalTo("a rationale")));
}
}
diff --git a/doc/changes/changes.md b/doc/changes/changes.md
index e12760c6d..a3891fc6e 100644
--- a/doc/changes/changes.md
+++ b/doc/changes/changes.md
@@ -1,5 +1,6 @@
# Changes
+* [4.7.0](changes_4.7.0.md)
* [4.6.0](changes_4.6.0.md)
* [4.5.0](changes_4.5.0.md)
* [4.4.0](changes_4.4.0.md)
diff --git a/doc/changes/changes_4.7.0.md b/doc/changes/changes_4.7.0.md
new file mode 100644
index 000000000..22c995bca
--- /dev/null
+++ b/doc/changes/changes_4.7.0.md
@@ -0,0 +1,14 @@
+# OpenFastTrace 4.7.0, released 2026-07-??
+
+Code name: Gherkin Importer
+
+## Summary
+
+OpenFastTrace can now import traced specifications directly from Gherkin
+`.feature` files. Annotate scenarios or scenario outlines with an OFT ID and
+optional `Covers` and `Needs` metadata; existing coverage tags remain supported
+when written in Gherkin comments.
+
+## New Features
+
+* #563: Added Gherkin `.feature` specification import for annotated scenarios and scenario outlines.
diff --git a/doc/changesets/563-support-gherkin-feature-specification-documents.md b/doc/changesets/563-support-gherkin-feature-specification-documents.md
deleted file mode 100644
index 1a5661722..000000000
--- a/doc/changesets/563-support-gherkin-feature-specification-documents.md
+++ /dev/null
@@ -1,156 +0,0 @@
-# GH-563 Support Gherkin `.feature` Files As OpenFastTrace Specification Documents
-
-## Goal
-
-Allow OpenFastTrace to import specification items from Gherkin `Scenario` and
-`Scenario Outline` blocks in `.feature` files. Preserve legacy coverage tags
-when they are written in Gherkin comments, while avoiding coverage-tag regular
-expression evaluation for executable Gherkin lines.
-
-## Scope
-
-In scope:
-
-* Extract shared line scanning and long/short coverage-tag parsing into a
- prerequisite module.
-* Add a dedicated Gherkin importer that composes the shared tag parser.
-* Import OFT scenario items identified by `@id:`.
-* Parse scoped `# Covers:` and `# Needs:` metadata strictly.
-* Preserve comment-based legacy coverage tags in `.feature` files and all
- existing tag importer behavior for non-`.feature` inputs.
-* Update traced requirements, design, tests, and user documentation.
-
-Out of scope:
-
-* Importing `Feature`, `Rule`, `Background`, or `Examples` as specification
- items.
-* Supporting coverage tags in executable Gherkin lines.
-* Adding an external Gherkin parser dependency.
-* Moving Gherkin grammar or validation into the shared parser module.
-
-## Design References
-
-* [System Requirements](../spec/system_requirements.md)
-* [Design](../spec/design.md)
-* [Quality Requirements](../spec/design/quality_requirements.md)
-* [User Guide](../user_guide.md)
-
-## Strategy
-
-1. Merge a behavior-preserving refactoring PR that introduces a shared module
- for line scanning and long/short coverage-tag parsing. It retains `.feature`
- support in the tag importer.
-2. Atomically move `.feature` ownership from the tag importer to a new Gherkin
- importer with higher precedence when the Gherkin importer is registered.
-3. Implement Gherkin parsing as a single-pass state machine. It receives every
- line but forwards only comment lines to the shared coverage-tag parser.
-4. Keep all Gherkin syntax, state, validation, and `ImportEventListener`
- mapping in the Gherkin importer.
-
-## Gherkin Syntax And Behavior
-
-* A Gherkin scenario is an OFT item only when its immediately preceding,
- contiguous Gherkin tag region contains exactly one
- `@id:` tag. Other Gherkin tags are ignored.
-* Directives are recognized only in `#` comment lines after that ID tag region
- and before the associated `Scenario:` or `Scenario Outline:` header.
- Comments elsewhere are ignored by Gherkin metadata parsing.
-* `# Covers:` and `# Needs:` are case-sensitive. Each is optional but may occur
- at most once. When present, it must contain a non-empty comma-separated list;
- duplicate values and malformed IDs or artifact types are errors.
-* A repeated or invalid `@id` tag, or a scoped directive without exactly one
- valid ID, fails the import with an `ImporterException` containing the file,
- line, and reason. Scenarios without OFT metadata remain ignored.
-* The scenario header line is the item location. Text after `Scenario:` or
- `Scenario Outline:` is the title. The importer streams the scenario-step
- block into the description, excluding comments and `Examples`; it ends the
- item at the next `Scenario`, `Scenario Outline`, `Feature`, `Rule`,
- `Background`, `Examples`, or end of file.
-* The importer retains only active metadata and previously imported Gherkin IDs
- for duplicate detection. It does not buffer a complete file or description.
-
-## Task List
-
-- [ ] Create and checkout branch
- `feature/563_support_gherkin_feature_specification_documents`.
-
-### PR 1: Shared Coverage-Tag Parser Refactoring
-
-- [ ] Add `importer/tag-importer-common` with artifact ID
- `openfasttrace-importer-tag-importer-common` to the Maven reactor.
-- [ ] Add JPMS module `org.itsallcode.openfasttrace.importer.tag.common` and
- export only `LineReader` (including its line-consumer contract) and
- `CoverageTagParser` from
- `org.itsallcode.openfasttrace.importer.tag.common`.
-- [ ] Move line scanning, line-handler composition, regex matching, long/short
- coverage-tag parsing, and CRC32 ID generation from `importer/tag` into
- the shared module without changing parsing semantics, generated IDs,
- listener events, logging, or exception wrapping.
-- [ ] Define `CoverageTagParser.create(PathConfig, InputFile,
- ImportEventListener)` to compose the long-tag parser and, when a path
- configuration is present, the short-tag parser into one line consumer.
- Keep parser implementation classes encapsulated.
-- [ ] Refactor `openfasttrace-importer-tag` into a thin adapter that creates
- the shared parser and scans its input once with the shared `LineReader`.
- Keep all supported extensions, including `.feature`, unchanged in this
- refactoring PR.
-- [ ] Move scanner tests to the shared module and add focused shared-parser
- tests for representative long and configured short tags, asserting
- listener events, locations, generated IDs, coverage links, and needed
- artifact types.
-- [ ] Keep the existing tag-importer parsing and factory/configuration tests as
- regression tests, including `.feature` support, to prove that the
- refactoring is behavior-preserving.
-
-### Requirements And Design
-
-- [ ] Add requirements for importing Gherkin scenarios and outlines, strict
- scoped metadata validation, Gherkin importer selection, and comment-only
- legacy coverage-tag compatibility.
-- [ ] Stop and ask user for review of the updated system requirements.
-- [ ] Add design items for factory precedence, the streaming Gherkin state
- machine, metadata scope, event mapping, and shared-parser delegation.
-- [ ] Stop and ask user for review of the updated design.
-
-### PR 2: Gherkin Importer
-
-- [ ] Add `importer/gherkin` with artifact ID
- `openfasttrace-importer-gherkin`; register it in the Maven reactor and
- product dependencies.
-- [ ] Provide a Gherkin importer factory for `.feature` files with priority
- `9000`, ahead of the tag importer's priority `10000`.
-- [ ] Implement the defined single-pass Gherkin state machine and map imported
- scenario fragments to `ImportEventListener` events.
-- [ ] Inject the shared coverage-tag parser and forward only lines whose
- trimmed form starts with `#` to it.
-- [ ] Implement the specified `@id:`, `Covers`, `Needs`, title, description,
- boundary, duplicate-ID, and error behavior.
-- [ ] Preserve the shared parser's existing `ImporterException` behavior for
- legacy coverage tags; do not introduce a new shared validation exception.
-
-### Verification
-
-- [ ] Add Gherkin importer unit tests for valid scenarios and outlines,
- location/title/description extraction, coverage metadata, non-OFT tags,
- and ignored ordinary scenarios.
-- [ ] Add validation tests for invalid or multiple IDs, orphan directives,
- repeated or empty directives, malformed list entries, duplicate metadata
- values, and duplicate Gherkin IDs. Assert exception type and relevant
- message content.
-- [ ] Add regression tests proving comment coverage tags import in `.feature`
- files, non-comment coverage-tag text is ignored in `.feature` files, and
- tag importer behavior for non-`.feature` inputs is unchanged.
-- [ ] Add pipeline tests proving each Gherkin file is scanned once and only
- comment lines reach the shared coverage-tag parser.
-- [ ] Add product-level tests for Gherkin importer precedence and mixed
- scenario specifications with comment-based legacy coverage tags.
-- [ ] Run `./oft-self-trace.sh` and ensure the trace stays clean.
-- [ ] Run `mvn -T 1C verify` and ensure all quality gates pass.
-
-### Documentation And Changelog
-
-- [ ] Extend [doc/user_guide.md](../user_guide.md) with the `.feature` syntax,
- placement rules, validation behavior, and examples.
-- [ ] Update [.agents/skills/openfasttrace/SKILL.md](../../.agents/skills/openfasttrace/SKILL.md)
- with the Gherkin syntax and comment-only compatibility rule.
-- [ ] Add the GH-563 entry to [doc/changes/changes_4.6.0.md](../changes/changes_4.6.0.md).
diff --git a/doc/spec/design.md b/doc/spec/design.md
index 48353ade3..7156b075f 100644
--- a/doc/spec/design.md
+++ b/doc/spec/design.md
@@ -235,8 +235,49 @@ The default priorities for standard importers are:
1. Markdown Importer: 1000
2. reStructuredText Importer: 2000
3. Specobject (ReqM2) Importer: 3000
-4. Tag Importer: 10000
-5. Zip Importer: 20000
+4. Gherkin Importer: 9000
+5. Tag Importer: 10000
+6. Zip Importer: 20000
+
+### Gherkin Import
+
+#### Importer Selection
+`dsn~gherkin.importer-selection~1`
+
+The Gherkin importer selects `.feature` files with priority 9000, ahead of the Tag Importer. This gives Gherkin syntax ownership to the dedicated importer while retaining the Tag Importer as the fallback for other source files.
+
+Covers:
+
+* `req~gherkin-scenario-import~1`
+
+Needs: impl, utest, itest
+
+#### Streaming Import
+`dsn~gherkin.streaming-import~1`
+
+The Gherkin importer scans each input file once. It imports only scenarios and scenario outlines with exactly one immediately preceding `@id:` tag, maps header location and title to import events, and streams non-comment scenario steps into the description until a Gherkin block boundary.
+
+Scoped `# Covers:` and `# Needs:` comments are validated between the tag region and header. Multiple `Covers` directives accumulate coverage IDs; `Needs` occurs at most once. The importer retains only active metadata and previously imported IDs.
+
+Covers:
+
+* `req~gherkin-scenario-import~1`
+* `req~gherkin-metadata-validation~1`
+
+Needs: impl, utest
+
+#### Support Legacy Coverage Tags
+`dsn~gherkin.comment-coverage-tags~1`
+
+The Gherkin importer delegates only comment lines to the shared coverage-tag parser. This preserves legacy comment coverage tags without applying their regular expressions to executable Gherkin text.
+
+When a comment tag occurs inside an imported scenario, its listener events are buffered until the scenario ends. The shared parser emits complete specification-item event sequences, so emitting them immediately would interleave them with the open scenario item and corrupt the listener state.
+
+Covers:
+
+* `req~gherkin-comment-coverage-tags~1`
+
+Needs: impl, utest
### ReqM2 File Detection
`dsn~import.reqm2-file-detection~1`
@@ -558,7 +599,7 @@ Needs: impl, itest
#### HTML Reports Allows Configuring Details Display Status
`dsn~reporting.html.details-display~1`
-OFT allows configuring the specification item detail section display status (expanded or collapsed). Default is collapsed.
+OFT allows configuring the specification item detail section display status (expanded or collapsed). Default is collapsed.
Covers:
@@ -699,13 +740,13 @@ Needs: impl, utest
A requirement ID has the following format
requirement-id = type "~" id "~" revision
-
+
type = 1*ALPHA
-
+
id = id-fragment *("." id-fragment)
-
+
id-fragment = UNICODE_ALPHA *(UNICODE_ALPHA / DIGIT / "_" / "-")
-
+
revision = 1*DIGIT
Rationale:
@@ -750,9 +791,9 @@ Needs: impl, utest
In Markdown specification item references have the following format:
reference = (plain-reference / url-style-link)
-
+
plain-reference = requirement-id
-
+
url-style-link = "[" link-text "]" "(" "#" requirement-id ")"
Covers:
@@ -767,9 +808,9 @@ Needs: impl, utest
The Markdown Importer supports the following format for links that cover a different specification item.
covers-list = covers-header 1*(LINEBREAK covers-line)
-
+
covers-header = "Covers:" *WSP
-
+
covers-line = *WSP "*" *WSP reference
Only one traced reference per line is supported. Any optional text after the reference is ignored if it is separated by at least one whitespace character
@@ -790,9 +831,9 @@ Needs: impl, utest
The Markdown Importer supports the following format for links to a different specification item which the current depends on.
depends-list = depends-header 1*(LINEBREAK depends-line)
-
+
depends-header = "Depends:" *WSP
-
+
depends-line = *WSP "*" *WSP reference
Only one traced reference per line is supported. Any optional text after the reference is ignored if it is separated by at least one whitespace character
@@ -852,11 +893,11 @@ The Markdown Importer supports forwarding required coverage from one artifact ty
artifact-need-redirection = skipped-artifact-type *WSP "-->" *WSP target-artifact-list
*WSP ":" *WSP original-requirement-id
-
+
skipped-artifact-type = artifact-type
-
+
target-artifact-list = artifact-type *("," *WSP artifact-type)
-
+
original-requirement-id = requirement-id
The following example shows an architectural specification item that forwards the needed coverage directly to the detailed design and an integration test:
@@ -1234,7 +1275,7 @@ Needs: impl, utest
### Why is This Architecture Relevant?
-Authors of importers need to be able to rely on these cleanups being done centrally, so that they don't have to implement them themselves.
+Authors of importers need to be able to rely on these cleanups being done centrally, so that they don't have to implement them themselves.
### Alternatives Considered
diff --git a/doc/spec/design/quality_requirements.md b/doc/spec/design/quality_requirements.md
index 04101bacf..3d94811a3 100644
--- a/doc/spec/design/quality_requirements.md
+++ b/doc/spec/design/quality_requirements.md
@@ -115,11 +115,12 @@ The following rules are written for both human contributors and coding agents. T
### Java Test Rules
-1. Use Hamcrest matchers for assertions.
-2. Extract repeated complex assertions into dedicated matcher classes when that improves readability.
-3. Declare only the specific checked exceptions that are directly thrown by the code under test. Do not use generic exceptions (e.g., Exception, Throwable) in test method signatures.
-4. When asserting exceptions, the assertion should invoke exactly one method call—the method under test. Avoid nesting additional method calls inside the assertion. Prepare all inputs outside the assertion so the failure is attributable to a single call.
-5. Prefer @ParameterizedTest for testing multiple input variations instead of generating or mutating test data within a single test method (e.g., loops or inline variations).
+1. Test method names must start with `test` and describe the behavior under test.
+2. Use Hamcrest matchers for assertions.
+3. Extract repeated complex assertions into dedicated matcher classes when that improves readability.
+4. Declare only the specific checked exceptions that are directly thrown by the code under test. Do not use generic exceptions (e.g., Exception, Throwable) in test method signatures.
+5. When asserting exceptions, the assertion should invoke exactly one method call—the method under test. Avoid nesting additional method calls inside the assertion. Prepare all inputs outside the assertion so the failure is attributable to a single call.
+6. Prefer @ParameterizedTest for testing multiple input variations instead of generating or mutating test data within a single test method (e.g., loops or inline variations).
## Dependency Policy
diff --git a/doc/spec/system_requirements.md b/doc/spec/system_requirements.md
index c4efb2b41..9ec3f6ab7 100644
--- a/doc/spec/system_requirements.md
+++ b/doc/spec/system_requirements.md
@@ -125,6 +125,46 @@ Coverage tags indicate parts of the source code that implements a certain requir
Needs: req
+### Gherkin Import
+`feat~gherkin-import~1`
+
+OFT imports specification items from annotated Gherkin scenarios and scenario outlines in `.feature` files.
+
+Needs: req
+
+#### Import Gherkin Scenarios
+`req~gherkin-scenario-import~1`
+
+OFT imports a Gherkin `Scenario` or `Scenario Outline` as a specification item when its immediately preceding contiguous tag region contains exactly one `@id:` tag. The scenario header supplies the title and location; scenario steps form the description.
+
+Covers:
+
+* [feat~gherkin-import~1](#gherkin-import)
+
+Needs: dsn
+
+#### Validate Gherkin Metadata
+`req~gherkin-metadata-validation~1`
+
+OFT accepts optional, scoped `# Covers:` and `# Needs:` comments between an ID tag region and its scenario header. `Covers` may occur multiple times; `Needs` may occur once. Each directive must contain a non-empty, valid, duplicate-free list. Invalid IDs, artifact types, repeated IDs, duplicate scenario IDs, and orphan directives cause an import error that identifies the file and line.
+
+Covers:
+
+* [feat~gherkin-import~1](#gherkin-import)
+
+Needs: dsn
+
+#### Preserve Gherkin Comment Coverage Tags
+`req~gherkin-comment-coverage-tags~1`
+
+OFT imports legacy coverage tags from comments in `.feature` files, but does not evaluate coverage tags in executable Gherkin lines.
+
+Covers:
+
+* [feat~gherkin-import~1](#gherkin-import)
+
+Needs: dsn
+
### ReqM2 Export
`feat~reqm2-export~1`
@@ -526,7 +566,7 @@ Covers:
Needs: dsn
-#### Include Items That Don't Have Tags Or Where at Least One Tag Matches
+#### Include Items That Don't Have Tags Or Where at Least One Tag Matches
`req~include-items-that-do-not-have-tags-or-where-at-least-one-tag-matches~1`
OFT gives users the option to include only specification items that either do not have tags or have at least one tag from a configurable set of tags during processing.
@@ -548,7 +588,7 @@ Reports are the main way to find out if a projects requirements are covered prop
Users can choose to display the requirement origin (e.g. file and line number) in reports:
* In the body of a specification item
-* For each link to a specification item
+* For each link to a specification item
Rationale:
@@ -806,29 +846,29 @@ Covers:
Needs: dsn
#### Common
-
+
##### CLI Help
`req~cli.help~1`
-
+
`help`, `-h` and `--help` show a short help text with command line usage.
-
+
Covers:
-
+
* [feat~command-line-interface~1](#command-line-interface)
-
+
Needs: dsn
-
+
##### CLI Version
`req~cli.version~1`
-
+
`help`, `-h` and `--help` show the version of OFT.
-
+
Covers:
-
+
* [feat~command-line-interface~1](#command-line-interface)
-
+
Needs: dsn
-
+
##### Input Selection
`req~cli.input-selection~1`
diff --git a/doc/user_guide.md b/doc/user_guide.md
index 15daae3d9..70b29d02c 100644
--- a/doc/user_guide.md
+++ b/doc/user_guide.md
@@ -822,6 +822,25 @@ Note that XML is at the moment not yet supported by the Tag Importer, because it
**Test Specification languages**
* [Gherkin](https://cucumber.io/docs/gherkin/) (`.feature`)
+
+#### Gherkin
+
+OFT imports Gherkin `Scenario` and `Scenario Outline` blocks in `.feature` files when the immediately preceding tag region contains one OFT ID tag. Place optional `Covers` and `Needs` comments after the tags and before the scenario header:
+
+```gherkin
+@smoke
+@id:scn~user-can-log-in~1
+# Covers: req~authentication~1
+# Needs: dsn, itest
+Scenario: A registered user logs in
+ Given a registered user
+ When they enter valid credentials
+ Then access is granted
+```
+
+The scenario header becomes the item title and location; its executable steps become the description. `Covers` and `Needs` are case-sensitive and optional. Multiple `Covers` comments accumulate coverage IDs, while `Needs` may appear once; all lists must be non-empty and comma-separated. Invalid or duplicate IDs, types, or directives cause an import error.
+
+Existing full coverage tags remain supported in Gherkin comments, for example `# [impl~login~1 -> dsn~authentication~1]`. OFT deliberately ignores coverage-tag-shaped text in executable Gherkin lines.
#### Markdown
diff --git a/importer/gherkin/.settings/org.eclipse.jdt.core.prefs b/importer/gherkin/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..a40522564
--- /dev/null
+++ b/importer/gherkin/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,413 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
+org.eclipse.jdt.core.compiler.processAnnotations=disabled
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=17
+org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false
+org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false
+org.eclipse.jdt.core.formatter.align_with_spaces=false
+org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=0
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type=49
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assertion_message=0
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
+org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_module_statements=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_record_components=16
+org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0
+org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_type_annotations=0
+org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
+org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=next_line_on_wrap
+org.eclipse.jdt.core.formatter.brace_position_for_block=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_record_constructor=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_record_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=next_line
+org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=false
+org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.indent_tag_description=false
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert
+org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=false
+org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_if_empty
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=true
+org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_if_empty
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_if_empty
+org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_if_empty
+org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.text_block_indentation=0
+org.eclipse.jdt.core.formatter.use_on_off_tags=true
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
+org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
+org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
diff --git a/importer/gherkin/.settings/org.eclipse.jdt.ui.prefs b/importer/gherkin/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 000000000..0a69ece30
--- /dev/null
+++ b/importer/gherkin/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,146 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_itsallcode style
+formatter_settings_version=21
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=java;javax;org;com;
+org.eclipse.jdt.ui.ondemandthreshold=4
+org.eclipse.jdt.ui.staticondemandthreshold=4
+sp_cleanup.add_all=false
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.array_with_curly=false
+sp_cleanup.arrays_fill=false
+sp_cleanup.bitwise_conditional_expression=false
+sp_cleanup.boolean_literal=false
+sp_cleanup.boolean_value_rather_than_comparison=false
+sp_cleanup.break_loop=false
+sp_cleanup.collection_cloning=false
+sp_cleanup.comparing_on_criteria=false
+sp_cleanup.comparison_statement=false
+sp_cleanup.controlflow_merge=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=false
+sp_cleanup.convert_to_switch_expressions=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.do_while_rather_than_while=false
+sp_cleanup.double_negation=false
+sp_cleanup.else_if=false
+sp_cleanup.embedded_if=false
+sp_cleanup.evaluate_nullable=false
+sp_cleanup.extract_increment=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.hash=false
+sp_cleanup.if_condition=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.instanceof=false
+sp_cleanup.instanceof_keyword=false
+sp_cleanup.invert_equals=false
+sp_cleanup.join=false
+sp_cleanup.lazy_logical_operator=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.map_cloning=false
+sp_cleanup.merge_conditional_blocks=false
+sp_cleanup.multi_catch=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.no_string_creation=false
+sp_cleanup.no_super=false
+sp_cleanup.number_suffix=false
+sp_cleanup.objects_equals=false
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false
+sp_cleanup.operand_factorization=false
+sp_cleanup.organize_imports=true
+sp_cleanup.overridden_assignment=false
+sp_cleanup.plain_replacement=false
+sp_cleanup.precompile_regex=false
+sp_cleanup.primitive_comparison=false
+sp_cleanup.primitive_parsing=false
+sp_cleanup.primitive_rather_than_wrapper=false
+sp_cleanup.primitive_serialization=false
+sp_cleanup.pull_out_if_from_if_else=false
+sp_cleanup.pull_up_assignment=false
+sp_cleanup.push_down_negation=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.reduce_indentation=false
+sp_cleanup.redundant_comparator=false
+sp_cleanup.redundant_falling_through_block_end=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_modifiers=false
+sp_cleanup.remove_redundant_semicolons=false
+sp_cleanup.remove_redundant_type_arguments=false
+sp_cleanup.remove_trailing_whitespaces=false
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_array_creation=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.return_expression=false
+sp_cleanup.simplify_lambda_expression_and_method_ref=false
+sp_cleanup.single_used_field=false
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.standard_comparison=false
+sp_cleanup.static_inner_class=false
+sp_cleanup.strictly_equal_or_different=false
+sp_cleanup.stringbuffer_to_stringbuilder=false
+sp_cleanup.stringbuilder=false
+sp_cleanup.stringbuilder_for_local_vars=false
+sp_cleanup.stringconcat_to_textblock=false
+sp_cleanup.substring=false
+sp_cleanup.switch=false
+sp_cleanup.system_property=false
+sp_cleanup.system_property_boolean=false
+sp_cleanup.system_property_file_encoding=false
+sp_cleanup.system_property_file_separator=false
+sp_cleanup.system_property_line_separator=false
+sp_cleanup.system_property_path_separator=false
+sp_cleanup.ternary_operator=false
+sp_cleanup.try_with_resource=false
+sp_cleanup.unlooped_while=false
+sp_cleanup.unreachable_block=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_autoboxing=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_directly_map_method=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_string_is_blank=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+sp_cleanup.use_unboxing=false
+sp_cleanup.use_var=false
+sp_cleanup.useless_continue=false
+sp_cleanup.useless_return=false
+sp_cleanup.valueof_rather_than_instantiation=false
diff --git a/importer/gherkin/pom.xml b/importer/gherkin/pom.xml
new file mode 100644
index 000000000..4038d4640
--- /dev/null
+++ b/importer/gherkin/pom.xml
@@ -0,0 +1,31 @@
+
+ 4.0.0
+ openfasttrace-importer-gherkin
+ OpenFastTrace Gherkin Importer
+
+ ../../openfasttrace-mc-deployable-parent/pom.xml
+ org.itsallcode.openfasttrace
+ openfasttrace-mc-deployable-parent
+ ${revision}
+
+
+ ${reproducible.build.timestamp}
+
+
+
+ org.itsallcode.openfasttrace
+ openfasttrace-api
+
+
+ org.itsallcode.openfasttrace
+ openfasttrace-importer-tag-importer-common
+
+
+ org.itsallcode.openfasttrace
+ openfasttrace-testutil
+ test
+
+
+
diff --git a/importer/gherkin/src/main/java/module-info.java b/importer/gherkin/src/main/java/module-info.java
new file mode 100644
index 000000000..397577fd2
--- /dev/null
+++ b/importer/gherkin/src/main/java/module-info.java
@@ -0,0 +1,15 @@
+import org.itsallcode.openfasttrace.importer.gherkin.GherkinImporterFactory;
+
+/**
+ * Provides an importer for Gherkin {@code .feature} files.
+ *
+ * @provides org.itsallcode.openfasttrace.api.importer.ImporterFactory
+ */
+module org.itsallcode.openfasttrace.importer.gherkin
+{
+ requires transitive org.itsallcode.openfasttrace.api;
+ requires org.itsallcode.openfasttrace.importer.tag.common;
+
+ provides org.itsallcode.openfasttrace.api.importer.ImporterFactory
+ with GherkinImporterFactory;
+}
diff --git a/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporter.java b/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporter.java
new file mode 100644
index 000000000..f3b80ad05
--- /dev/null
+++ b/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporter.java
@@ -0,0 +1,26 @@
+package org.itsallcode.openfasttrace.importer.gherkin;
+
+import org.itsallcode.openfasttrace.api.importer.ImportEventListener;
+import org.itsallcode.openfasttrace.api.importer.Importer;
+import org.itsallcode.openfasttrace.api.importer.input.InputFile;
+import org.itsallcode.openfasttrace.importer.tag.common.LineReader;
+
+/** Imports annotated Gherkin scenarios while streaming the input once. */
+// [impl->dsn~gherkin.streaming-import~1]
+final class GherkinImporter implements Importer
+{
+ private final InputFile file;
+ private final GherkinLineConsumer lineConsumer;
+
+ GherkinImporter(final InputFile file, final ImportEventListener listener)
+ {
+ this.file = file;
+ this.lineConsumer = new GherkinLineConsumer(file, listener);
+ }
+
+ @Override
+ public void runImport()
+ {
+ LineReader.create(this.file).readLines(this.lineConsumer);
+ }
+}
diff --git a/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporterFactory.java b/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporterFactory.java
new file mode 100644
index 000000000..b083ea5a0
--- /dev/null
+++ b/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporterFactory.java
@@ -0,0 +1,27 @@
+package org.itsallcode.openfasttrace.importer.gherkin;
+
+import org.itsallcode.openfasttrace.api.importer.*;
+import org.itsallcode.openfasttrace.api.importer.input.InputFile;
+
+/** Factory for importing OpenFastTrace specifications from Gherkin files. */
+// [impl->dsn~gherkin.importer-selection~1]
+public class GherkinImporterFactory extends AbstractRegexMatchingImporterFactory
+{
+ /** Create a factory that accepts Gherkin {@code .feature} files. */
+ public GherkinImporterFactory()
+ {
+ super("(?i).*\\.feature");
+ }
+
+ @Override
+ public int getPriority()
+ {
+ return 9000;
+ }
+
+ @Override
+ public Importer createImporter(final InputFile file, final ImportEventListener listener)
+ {
+ return new GherkinImporter(file, listener);
+ }
+}
diff --git a/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinLineConsumer.java b/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinLineConsumer.java
new file mode 100644
index 000000000..d49f0b0e8
--- /dev/null
+++ b/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinLineConsumer.java
@@ -0,0 +1,244 @@
+package org.itsallcode.openfasttrace.importer.gherkin;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.itsallcode.openfasttrace.api.core.SpecificationItemId;
+import org.itsallcode.openfasttrace.api.importer.ImportEventListener;
+import org.itsallcode.openfasttrace.api.importer.input.InputFile;
+import org.itsallcode.openfasttrace.importer.tag.common.CoverageTagParser;
+import org.itsallcode.openfasttrace.importer.tag.common.LineReader.LineConsumer;
+
+/** Stateful parser for the Gherkin lines of one input file. */
+// [impl->dsn~gherkin.streaming-import~1]
+// [impl->dsn~gherkin.comment-coverage-tags~1]
+final class GherkinLineConsumer implements LineConsumer
+{
+ private static final int UNICODE = Pattern.UNICODE_CHARACTER_CLASS;
+ private static final Pattern ID_TAG = Pattern.compile("@id:([^\\s]+)", UNICODE);
+ private static final Pattern SCENARIO = Pattern.compile("^\\s*Scenario(?: Outline)?:(.*)$", UNICODE);
+ private static final Pattern BOUNDARY = Pattern
+ .compile("^\\s*(?:Scenario(?: Outline)?|Feature|Rule|Background|Examples):", UNICODE);
+ private static final Pattern DIRECTIVE = Pattern.compile("^\\s*#\\s*(Covers|Needs):(.*)$", UNICODE);
+ private static final Pattern ARTIFACT_TYPE = Pattern.compile("\\p{IsAlphabetic}+");
+
+ private final InputFile file;
+ private final ImportEventListener listener;
+ private final LineConsumer coverageTagParser;
+ private final Set importedIds = new LinkedHashSet<>();
+ private SpecificationItemId pendingId;
+ private Set coveredIds = new LinkedHashSet<>();
+ private Set neededArtifactTypes = new LinkedHashSet<>();
+ private boolean hasNeedsDirective;
+ private boolean metadataRegion;
+ private boolean tagRegion;
+ private boolean importingScenario;
+
+ GherkinLineConsumer(final InputFile file, final ImportEventListener listener)
+ {
+ this.file = file;
+ this.listener = listener;
+ this.coverageTagParser = CoverageTagParser.create(null, file, listener);
+ }
+
+ @Override
+ public void readLine(final int lineNumber, final String line)
+ {
+ if (line.trim().startsWith("#"))
+ {
+ this.coverageTagParser.readLine(lineNumber, line);
+ }
+ final Matcher scenario = SCENARIO.matcher(line);
+ if (scenario.matches())
+ {
+ endScenario();
+ beginScenario(lineNumber, scenario.group(1).trim());
+ return;
+ }
+ if (BOUNDARY.matcher(line).find())
+ {
+ endScenario();
+ clearMetadata();
+ return;
+ }
+ if (this.importingScenario)
+ {
+ if (!line.trim().startsWith("#"))
+ {
+ this.listener.appendDescription(line + System.lineSeparator());
+ }
+ return;
+ }
+ readMetadata(lineNumber, line);
+ }
+
+ @Override
+ public void finish()
+ {
+ endScenario();
+ }
+
+ private void readMetadata(final int lineNumber, final String line)
+ {
+ if (line.trim().startsWith("@"))
+ {
+ readTagRegion(lineNumber, line.trim());
+ return;
+ }
+ final Matcher directive = DIRECTIVE.matcher(line);
+ if (this.metadataRegion && directive.matches())
+ {
+ readDirective(lineNumber, directive.group(1), directive.group(2));
+ return;
+ }
+ if (!line.trim().startsWith("#"))
+ {
+ clearMetadata();
+ }
+ }
+
+ private void readTagRegion(final int lineNumber, final String tags)
+ {
+ if (!this.tagRegion)
+ {
+ clearMetadata();
+ }
+ this.metadataRegion = true;
+ this.tagRegion = true;
+ final Matcher matcher = ID_TAG.matcher(tags);
+ while (matcher.find())
+ {
+ if (this.pendingId != null)
+ {
+ fail(lineNumber, "multiple @id tags before a scenario");
+ }
+ this.pendingId = parseId(lineNumber, matcher.group(1));
+ }
+ }
+
+ private void readDirective(final int lineNumber, final String name, final String values)
+ {
+ this.tagRegion = false;
+ if (this.pendingId == null)
+ {
+ fail(lineNumber, name + " directive requires exactly one preceding @id tag");
+ }
+ final boolean covers = "Covers".equals(name);
+ if (!covers && this.hasNeedsDirective)
+ {
+ fail(lineNumber, "repeated " + name + " directive");
+ }
+ final String[] entries = splitValues(lineNumber, name, values);
+ if (covers)
+ {
+ readCoveredIds(lineNumber, entries);
+ return;
+ }
+ readNeededArtifactTypes(lineNumber, entries);
+ this.hasNeedsDirective = true;
+ }
+
+ private String[] splitValues(final int lineNumber, final String name, final String values)
+ {
+ if (values.trim().isEmpty())
+ {
+ fail(lineNumber, name + " directive requires a non-empty list");
+ }
+ return values.trim().split(",", -1);
+ }
+
+ private void readCoveredIds(final int lineNumber, final String[] entries)
+ {
+ for (final String entry : entries)
+ {
+ final String value = requireValue(lineNumber, "Covers", entry);
+ final SpecificationItemId id = parseId(lineNumber, value);
+ if (!this.coveredIds.add(id))
+ {
+ fail(lineNumber, "Covers directive contains duplicate value '" + id + "'");
+ }
+ }
+ }
+
+ private void readNeededArtifactTypes(final int lineNumber, final String[] entries)
+ {
+ for (final String entry : entries)
+ {
+ final String value = requireValue(lineNumber, "Needs", entry);
+ if (!ARTIFACT_TYPE.matcher(value).matches())
+ {
+ fail(lineNumber, "Needs directive contains invalid artifact type '" + value + "'");
+ }
+ if (!this.neededArtifactTypes.add(value))
+ {
+ fail(lineNumber, "Needs directive contains duplicate value '" + value + "'");
+ }
+ }
+ }
+
+ private String requireValue(final int lineNumber, final String name, final String entry)
+ {
+ final String value = entry.trim();
+ if (value.isEmpty())
+ {
+ fail(lineNumber, name + " directive contains an empty value");
+ }
+ return value;
+ }
+
+ private SpecificationItemId parseId(final int lineNumber, final String value)
+ {
+ if (!SpecificationItemId.ID_PATTERN.matcher(value).matches())
+ {
+ fail(lineNumber, "invalid specification item ID '" + value + "'");
+ }
+ return SpecificationItemId.parseId(value);
+ }
+
+ private void beginScenario(final int lineNumber, final String title)
+ {
+ if (this.pendingId == null)
+ {
+ clearMetadata();
+ return;
+ }
+ if (!this.importedIds.add(this.pendingId))
+ {
+ fail(lineNumber, "duplicate Gherkin ID '" + this.pendingId + "'");
+ }
+ this.listener.beginSpecificationItem();
+ this.listener.setLocation(this.file.getPath(), lineNumber);
+ this.listener.setId(this.pendingId);
+ this.listener.setTitle(title);
+ this.coveredIds.forEach(this.listener::addCoveredId);
+ this.neededArtifactTypes.forEach(this.listener::addNeededArtifactType);
+ this.importingScenario = true;
+ clearMetadata();
+ }
+
+ private void endScenario()
+ {
+ if (this.importingScenario)
+ {
+ this.listener.endSpecificationItem();
+ this.importingScenario = false;
+ }
+ }
+
+ private void clearMetadata()
+ {
+ this.pendingId = null;
+ this.coveredIds = new LinkedHashSet<>();
+ this.neededArtifactTypes = new LinkedHashSet<>();
+ this.hasNeedsDirective = false;
+ this.metadataRegion = false;
+ this.tagRegion = false;
+ }
+
+ private void fail(final int lineNumber, final String reason)
+ {
+ throw new IllegalArgumentException(this.file.getPath() + ":" + lineNumber + ": " + reason);
+ }
+}
diff --git a/importer/gherkin/src/main/resources/META-INF/services/org.itsallcode.openfasttrace.api.importer.ImporterFactory b/importer/gherkin/src/main/resources/META-INF/services/org.itsallcode.openfasttrace.api.importer.ImporterFactory
new file mode 100644
index 000000000..77df55442
--- /dev/null
+++ b/importer/gherkin/src/main/resources/META-INF/services/org.itsallcode.openfasttrace.api.importer.ImporterFactory
@@ -0,0 +1 @@
+org.itsallcode.openfasttrace.importer.gherkin.GherkinImporterFactory
diff --git a/importer/gherkin/src/test/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporterTest.java b/importer/gherkin/src/test/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporterTest.java
new file mode 100644
index 000000000..a149a62f5
--- /dev/null
+++ b/importer/gherkin/src/test/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporterTest.java
@@ -0,0 +1,255 @@
+package org.itsallcode.openfasttrace.importer.gherkin;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.itsallcode.openfasttrace.api.core.SpecificationItem;
+import org.itsallcode.openfasttrace.api.core.SpecificationItemId;
+import org.itsallcode.openfasttrace.api.importer.ImportEventListener;
+import org.itsallcode.openfasttrace.api.importer.ImporterException;
+import org.itsallcode.openfasttrace.api.importer.input.InputFile;
+import org.itsallcode.openfasttrace.testutil.importer.ImportAssertions;
+import org.itsallcode.openfasttrace.testutil.importer.input.StreamInput;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.InOrder;
+
+class GherkinImporterTest
+{
+ private static final GherkinImporterFactory FACTORY = new GherkinImporterFactory();
+
+ // [utest->dsn~gherkin.streaming-import~1]
+ @Test
+ void testImportsScenarioOutlineWithScopedMetadataAndSteps()
+ {
+ final String source = """
+ @smoke
+ @unrelated @id:scn~account-login~1 @anotherTag
+ # Covers: req~login~1
+ # Needs: dsn, itest
+ Scenario Outline: Login works
+ Given a registered user
+ When they log in
+ Then access is granted
+ Examples:
+ | user |
+ | Ada |
+ """;
+
+ final List items = importText(source);
+
+ final SpecificationItem item = items.get(0);
+ assertAll(
+ () -> assertThat(items, contains(
+ hasProperty("id", hasToString("scn~account-login~1")))),
+ () -> assertThat(item.getTitle(), is("Login works")),
+ () -> assertThat(item.getLocation().getLine(), is(5)),
+ () -> assertThat(item.getDescription(), is(String.join(System.lineSeparator(),
+ "Given a registered user", " When they log in", " Then access is granted"))),
+ () -> assertThat(item.getCoveredIds(), contains(hasToString("req~login~1"))),
+ () -> assertThat(item.getNeedsArtifactTypes(), containsInAnyOrder("dsn", "itest")));
+ }
+
+ // [utest->dsn~gherkin.importer-selection~1]
+ @Test
+ void testFactorySupportsFeatureFilesWithHigherPrecedenceThanTagImporter()
+ {
+ final InputFile file = StreamInput.forReader(Path.of("specification.feature"),
+ new java.io.BufferedReader(new java.io.StringReader("")));
+
+ assertAll(
+ () -> assertThat(FACTORY.supportsFile(file), is(true)),
+ () -> assertThat(FACTORY.getPriority(), is(9000)));
+ }
+
+ // [utest->dsn~gherkin.streaming-import~1]
+ @Test
+ void testIgnoresScenarioWithoutOftMetadata()
+ {
+ final List items = importText("""
+ Feature: login
+ Scenario: ordinary scenario
+ Given nothing
+ """);
+
+ assertThat(items, is(empty()));
+ }
+
+ // [utest->dsn~gherkin.streaming-import~1]
+ @Test
+ void testImportsMultipleCoversDirectives()
+ {
+ final String source = """
+ @id:scn~account-login~1
+ # Covers: req~login~1
+ # Covers: req~security~1
+ Scenario: Login
+ """;
+
+ final List items = importText(source);
+
+ assertThat(items.get(0).getCoveredIds(), contains(
+ hasToString("req~login~1"), hasToString("req~security~1")));
+ }
+
+ // [utest->dsn~gherkin.streaming-import~1]
+ @Test
+ void testRejectsDirectiveWithoutId()
+ {
+ final String source = """
+ @ordinary
+ # Needs: dsn
+ Scenario: Login
+ """;
+
+ final ImporterException exception = assertThrows(ImporterException.class, () -> importText(source));
+
+ assertThat(exception.getMessage(), equalTo(
+ "Error processing line specification.feature:2 '# Needs: dsn': specification.feature:2: Needs directive requires exactly one preceding @id tag"));
+ }
+
+ // [utest->dsn~gherkin.streaming-import~1]
+ @Test
+ void testIgnoresDirectivesOutsideAnIdMetadataRegion()
+ {
+ final List items = importText("""
+ # Covers: req~login~1
+ Scenario: Login
+ """);
+
+ assertThat(items, is(empty()));
+ }
+
+ // [utest->dsn~gherkin.streaming-import~1]
+ @Test
+ void testKeepsMetadataWhenAnUnrelatedCommentPrecedesTheScenario()
+ {
+ final List items = importText("""
+ @id:scn~login~1
+ # A human-readable comment
+ Scenario: Login
+ """);
+
+ assertThat(items, contains(hasProperty("id", hasToString("scn~login~1"))));
+ }
+
+ // [utest->dsn~gherkin.streaming-import~1]
+ @ParameterizedTest
+ @MethodSource("invalidMetadata")
+ void testRejectsInvalidMetadata(final String source, final String reason)
+ {
+ final ImporterException exception = assertThrows(ImporterException.class, () -> importText(source));
+
+ assertThat(exception.getMessage(), containsString(reason));
+ }
+
+ private static Stream invalidMetadata()
+ {
+ return Stream.of(
+ Arguments.of("""
+ @id:invalid
+ Scenario: Login
+ """, "invalid specification item ID"),
+ Arguments.of("""
+ @id:scn~login~1
+ @id:scn~another-login~1
+ Scenario: Login
+ """, "multiple @id tags"),
+ Arguments.of("""
+ @id:scn~login~1
+ # Needs: dsn
+ # Needs: itest
+ Scenario: Login
+ """, "repeated Needs directive"),
+ Arguments.of("""
+ @id:scn~login~1
+ # Covers:
+ Scenario: Login
+ """, "requires a non-empty list"),
+ Arguments.of("""
+ @id:scn~login~1
+ # Covers: req~login~1,
+ Scenario: Login
+ """, "contains an empty value"),
+ Arguments.of("""
+ @id:scn~login~1
+ # Covers: req~login~1, req~login~1
+ Scenario: Login
+ """, "contains duplicate value"),
+ Arguments.of("""
+ @id:scn~login~1
+ # Needs: invalid-type
+ Scenario: Login
+ """, "invalid artifact type"),
+ Arguments.of("""
+ @id:scn~login~1
+ # Needs: dsn, dsn
+ Scenario: Login
+ """, "contains duplicate value"),
+ Arguments.of("""
+ @id:scn~login~1
+ Scenario: Login
+ Feature: Another feature
+ @id:scn~login~1
+ Scenario: Login again
+ """, "duplicate Gherkin ID"));
+ }
+
+ // [utest->dsn~gherkin.comment-coverage-tags~1]
+ @Test
+ void testImportsCommentCoverageTagsButIgnoresExecutableCoverageTags()
+ {
+ final String source = """
+ @id:scn~ordinary~1
+ Scenario: ordinary
+ # [%s]
+ Given [%s]
+ """.formatted("impl~gherkin-comment~1 -> dsn~gherkin~1",
+ "impl~gherkin-executable~1 -> dsn~gherkin~1");
+ final List items = importText(source);
+
+ assertThat(items, containsInAnyOrder(
+ hasProperty("id", hasToString("scn~ordinary~1")),
+ hasProperty("id", hasToString("impl~gherkin-comment~1"))));
+ }
+
+ // [utest->dsn~gherkin.comment-coverage-tags~1]
+ @Test
+ void testImportsCommentCoverageTagsWhileScenarioIsOpen()
+ {
+ final ImportEventListener listener = mock(ImportEventListener.class);
+ final InputFile file = StreamInput.forReader(Path.of("specification.feature"),
+ new BufferedReader(new StringReader("""
+ @id:scn~ordinary~1
+ Scenario: ordinary
+ # [%s]
+ Given a step
+ """.formatted("impl~gherkin-comment~1 -> dsn~gherkin~1"))));
+
+ new GherkinImporter(file, listener).runImport();
+
+ final InOrder events = inOrder(listener);
+ events.verify(listener).beginSpecificationItem();
+ events.verify(listener).setId(SpecificationItemId.parseId("scn~ordinary~1"));
+ events.verify(listener).addSpecificationItem(any(SpecificationItem.class));
+ events.verify(listener).endSpecificationItem();
+ }
+
+ private static List importText(final String source)
+ {
+ return ImportAssertions.runImporterOnText(Path.of("specification.feature"), source, FACTORY);
+ }
+}
diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/AbstractLightWeightMarkupImporter.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/AbstractLightWeightMarkupImporter.java
index 400ae7647..5a05de32a 100644
--- a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/AbstractLightWeightMarkupImporter.java
+++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/AbstractLightWeightMarkupImporter.java
@@ -1,6 +1,7 @@
package org.itsallcode.openfasttrace.importer.lightweightmarkup;
import org.itsallcode.openfasttrace.api.core.ItemStatus;
+import org.itsallcode.openfasttrace.api.core.SpecificationItem;
import org.itsallcode.openfasttrace.api.core.SpecificationItemId;
import org.itsallcode.openfasttrace.api.importer.ImportEventListener;
import org.itsallcode.openfasttrace.api.importer.Importer;
@@ -268,15 +269,15 @@ protected void forward()
{
final ForwardingSpecificationItem forward = new ForwardingSpecificationItem(
this.stateMachine.getLastToken());
- this.listener.beginSpecificationItem();
- this.listener.setId(forward.getSkippedId());
- this.listener.addCoveredId(forward.getOriginalId());
+ final SpecificationItem.Builder item = SpecificationItem.builder()
+ .id(forward.getSkippedId())
+ .addCoveredId(forward.getOriginalId())
+ .forwards(true)
+ .location(this.file.getPath(), this.currentContext.lineNumber());
for (final String targetArtifactType : forward.getTargetArtifactTypes())
{
- this.listener.addNeededArtifactType(targetArtifactType.trim());
+ item.addNeedsArtifactType(targetArtifactType.trim());
}
- this.listener.setForwards(true);
- this.listener.setLocation(this.file.getPath(), this.currentContext.lineNumber());
- this.listener.endSpecificationItem();
+ this.listener.addSpecificationItem(item.build());
}
}
diff --git a/importer/tag-importer-common/.settings/org.eclipse.jdt.core.prefs b/importer/tag-importer-common/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..a40522564
--- /dev/null
+++ b/importer/tag-importer-common/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,413 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
+org.eclipse.jdt.core.compiler.processAnnotations=disabled
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=17
+org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false
+org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false
+org.eclipse.jdt.core.formatter.align_with_spaces=false
+org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=0
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type=49
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assertion_message=0
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
+org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_module_statements=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_record_components=16
+org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0
+org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_type_annotations=0
+org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
+org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=next_line_on_wrap
+org.eclipse.jdt.core.formatter.brace_position_for_block=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_record_constructor=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_record_declaration=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=next_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=next_line
+org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=false
+org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.indent_tag_description=false
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert
+org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=false
+org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_if_empty
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=true
+org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_if_empty
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_if_empty
+org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_if_empty
+org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.text_block_indentation=0
+org.eclipse.jdt.core.formatter.use_on_off_tags=true
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
+org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
+org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
diff --git a/importer/tag-importer-common/.settings/org.eclipse.jdt.ui.prefs b/importer/tag-importer-common/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 000000000..0a69ece30
--- /dev/null
+++ b/importer/tag-importer-common/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,146 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_itsallcode style
+formatter_settings_version=21
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=java;javax;org;com;
+org.eclipse.jdt.ui.ondemandthreshold=4
+org.eclipse.jdt.ui.staticondemandthreshold=4
+sp_cleanup.add_all=false
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.array_with_curly=false
+sp_cleanup.arrays_fill=false
+sp_cleanup.bitwise_conditional_expression=false
+sp_cleanup.boolean_literal=false
+sp_cleanup.boolean_value_rather_than_comparison=false
+sp_cleanup.break_loop=false
+sp_cleanup.collection_cloning=false
+sp_cleanup.comparing_on_criteria=false
+sp_cleanup.comparison_statement=false
+sp_cleanup.controlflow_merge=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=false
+sp_cleanup.convert_to_switch_expressions=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.do_while_rather_than_while=false
+sp_cleanup.double_negation=false
+sp_cleanup.else_if=false
+sp_cleanup.embedded_if=false
+sp_cleanup.evaluate_nullable=false
+sp_cleanup.extract_increment=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.hash=false
+sp_cleanup.if_condition=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.instanceof=false
+sp_cleanup.instanceof_keyword=false
+sp_cleanup.invert_equals=false
+sp_cleanup.join=false
+sp_cleanup.lazy_logical_operator=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.map_cloning=false
+sp_cleanup.merge_conditional_blocks=false
+sp_cleanup.multi_catch=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.no_string_creation=false
+sp_cleanup.no_super=false
+sp_cleanup.number_suffix=false
+sp_cleanup.objects_equals=false
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false
+sp_cleanup.operand_factorization=false
+sp_cleanup.organize_imports=true
+sp_cleanup.overridden_assignment=false
+sp_cleanup.plain_replacement=false
+sp_cleanup.precompile_regex=false
+sp_cleanup.primitive_comparison=false
+sp_cleanup.primitive_parsing=false
+sp_cleanup.primitive_rather_than_wrapper=false
+sp_cleanup.primitive_serialization=false
+sp_cleanup.pull_out_if_from_if_else=false
+sp_cleanup.pull_up_assignment=false
+sp_cleanup.push_down_negation=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.reduce_indentation=false
+sp_cleanup.redundant_comparator=false
+sp_cleanup.redundant_falling_through_block_end=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_modifiers=false
+sp_cleanup.remove_redundant_semicolons=false
+sp_cleanup.remove_redundant_type_arguments=false
+sp_cleanup.remove_trailing_whitespaces=false
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_array_creation=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.return_expression=false
+sp_cleanup.simplify_lambda_expression_and_method_ref=false
+sp_cleanup.single_used_field=false
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.standard_comparison=false
+sp_cleanup.static_inner_class=false
+sp_cleanup.strictly_equal_or_different=false
+sp_cleanup.stringbuffer_to_stringbuilder=false
+sp_cleanup.stringbuilder=false
+sp_cleanup.stringbuilder_for_local_vars=false
+sp_cleanup.stringconcat_to_textblock=false
+sp_cleanup.substring=false
+sp_cleanup.switch=false
+sp_cleanup.system_property=false
+sp_cleanup.system_property_boolean=false
+sp_cleanup.system_property_file_encoding=false
+sp_cleanup.system_property_file_separator=false
+sp_cleanup.system_property_line_separator=false
+sp_cleanup.system_property_path_separator=false
+sp_cleanup.ternary_operator=false
+sp_cleanup.try_with_resource=false
+sp_cleanup.unlooped_while=false
+sp_cleanup.unreachable_block=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_autoboxing=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_directly_map_method=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_string_is_blank=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+sp_cleanup.use_unboxing=false
+sp_cleanup.use_var=false
+sp_cleanup.useless_continue=false
+sp_cleanup.useless_return=false
+sp_cleanup.valueof_rather_than_instantiation=false
diff --git a/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/DelegatingLineConsumer.java b/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/DelegatingLineConsumer.java
index 0def4d7ce..6e2602154 100644
--- a/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/DelegatingLineConsumer.java
+++ b/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/DelegatingLineConsumer.java
@@ -19,4 +19,10 @@ public void readLine(final int lineNumber, final String line)
{
this.delegates.forEach(delegate -> delegate.readLine(lineNumber, line));
}
+
+ @Override
+ public void finish()
+ {
+ this.delegates.forEach(LineConsumer::finish);
+ }
}
diff --git a/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/LineReader.java b/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/LineReader.java
index 32bafc053..0fa22d21c 100644
--- a/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/LineReader.java
+++ b/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/LineReader.java
@@ -44,6 +44,7 @@ public void readLines(final LineConsumer consumer) {
currentLineNumber = reader.getLineNumber();
processLine(consumer, currentLineNumber, line);
}
+ finish(consumer);
} catch (final IOException exception) {
throw new ImporterException("Error reading \"" + this.file + "\" at line " + currentLineNumber, exception);
}
@@ -55,7 +56,16 @@ private void processLine(final LineConsumer consumer, final int currentLineNumbe
consumer.readLine(currentLineNumber, line);
} catch (final RuntimeException exception) {
throw new ImporterException("Error processing line " + this.file.getPath() + ":" + currentLineNumber + " '"
- + line + "': " + exception, exception);
+ + line + "': " + exception.getMessage(), exception);
+ }
+ }
+
+ private void finish(final LineConsumer consumer) {
+ try {
+ consumer.finish();
+ } catch (final RuntimeException exception) {
+ throw new ImporterException("Error finishing " + this.file.getPath() + ": " + exception.getMessage(),
+ exception);
}
}
@@ -73,5 +83,12 @@ public interface LineConsumer {
* line content without its line separator
*/
void readLine(int lineNumber, String line);
+
+ /**
+ * Finish consuming the input after its last line has been read.
+ */
+ default void finish() {
+ // Default implementation intentionally does nothing.
+ }
}
}
diff --git a/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/LongTagImportingLineConsumer.java b/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/LongTagImportingLineConsumer.java
index b4aaebd2a..6290593a9 100644
--- a/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/LongTagImportingLineConsumer.java
+++ b/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/LongTagImportingLineConsumer.java
@@ -7,6 +7,7 @@
import java.util.logging.Logger;
import java.util.regex.Matcher;
+import org.itsallcode.openfasttrace.api.core.SpecificationItem;
import org.itsallcode.openfasttrace.api.core.SpecificationItemId;
import org.itsallcode.openfasttrace.api.importer.ImportEventListener;
import org.itsallcode.openfasttrace.api.importer.input.InputFile;
@@ -14,7 +15,8 @@
// [impl->dsn~import.full-coverage-tag~1]
// [impl->dsn~import.full-coverage-tag-with-needed-coverage~1]
// [impl->dsn~import.full-coverage-tag-multiple-needed-coverage~1]
-class LongTagImportingLineConsumer extends AbstractRegexLineConsumer {
+class LongTagImportingLineConsumer extends AbstractRegexLineConsumer
+{
private static final Logger LOG = Logger
.getLogger(LongTagImportingLineConsumer.class.getName());
@@ -44,42 +46,51 @@ class LongTagImportingLineConsumer extends AbstractRegexLineConsumer {
private final InputFile file;
private final ImportEventListener listener;
- LongTagImportingLineConsumer(final InputFile file, final ImportEventListener listener) {
+ LongTagImportingLineConsumer(final InputFile file, final ImportEventListener listener)
+ {
super(TAG_REGEX);
this.file = file;
this.listener = listener;
}
@Override
- public void processMatch(final Matcher matcher, final int lineNumber, final int lineMatchCount) {
+ public void processMatch(final Matcher matcher, final int lineNumber, final int lineMatchCount)
+ {
final List coveredIds = parseCoveredIds(matcher.group("coveredIds"));
final List neededArtifactTypes = parseNeededArtifactTypes(matcher.group("neededArtifactTypes"));
final List generatedIds = createItemIds(matcher, lineNumber, lineMatchCount, coveredIds,
neededArtifactTypes);
- if (generatedIds.size() > 1) {
+ if (generatedIds.size() > 1)
+ {
assert generatedIds.size() == coveredIds.size();
- for (int i = 0; i < generatedIds.size(); i++) {
+ for (int i = 0; i < generatedIds.size(); i++)
+ {
addSpecificationItem(lineNumber, generatedIds.get(i), List.of(coveredIds.get(i)), neededArtifactTypes);
}
- } else {
+ }
+ else
+ {
addSpecificationItem(lineNumber, generatedIds.get(0), coveredIds, neededArtifactTypes);
}
}
private void addSpecificationItem(final int lineNumber, final SpecificationItemId generatedId,
- final List coveredIds, final List neededArtifactTypes) {
- this.listener.beginSpecificationItem();
- this.listener.setLocation(this.file.getPath(), lineNumber);
- this.listener.setId(generatedId);
- coveredIds.forEach(this.listener::addCoveredId);
- neededArtifactTypes.forEach(this.listener::addNeededArtifactType);
- this.listener.endSpecificationItem();
+ final List coveredIds, final List neededArtifactTypes)
+ {
+ final SpecificationItem.Builder item = SpecificationItem.builder()
+ .id(generatedId)
+ .location(this.file.getPath(), lineNumber);
+ coveredIds.forEach(item::addCoveredId);
+ neededArtifactTypes.forEach(item::addNeedsArtifactType);
+ this.listener.addSpecificationItem(item.build());
logItem(lineNumber, coveredIds, neededArtifactTypes, generatedId);
}
- private static List parseCoveredIds(final String input) {
- if (input == null) {
+ private static List parseCoveredIds(final String input)
+ {
+ if (input == null)
+ {
return emptyList();
}
return Arrays.stream(input.split(","))
@@ -89,8 +100,10 @@ private static List parseCoveredIds(final String input) {
.toList();
}
- private static List parseNeededArtifactTypes(final String input) {
- if (input == null) {
+ private static List parseNeededArtifactTypes(final String input)
+ {
+ if (input == null)
+ {
return emptyList();
}
return Arrays.stream(input.split(","))
@@ -101,16 +114,19 @@ private static List parseNeededArtifactTypes(final String input) {
private List createItemIds(final Matcher matcher, final int lineNumber,
final int lineMatchCount, final List coveredIds,
- final List neededArtifactTypes) {
+ final List neededArtifactTypes)
+ {
final String artifactType = matcher.group("artifactType");
final String customName = matcher.group("customName");
final String revision = matcher.group("revision");
- if (customName != null) {
+ if (customName != null)
+ {
return List.of(SpecificationItemId.createId(artifactType, customName, parseRevision(revision)));
}
final List result = new java.util.ArrayList<>(coveredIds.size());
- for (final SpecificationItemId coveredId : coveredIds) {
+ for (final SpecificationItemId coveredId : coveredIds)
+ {
final String name = getItemName(lineNumber, lineMatchCount, coveredId, neededArtifactTypes);
result.add(SpecificationItemId.createId(artifactType, name, parseRevision(revision)));
}
@@ -118,30 +134,38 @@ private List createItemIds(final Matcher matcher, final int
}
private void logItem(final int lineNumber, final List coveredIds,
- final List neededArtifactTypes, final SpecificationItemId generatedId) {
- if (neededArtifactTypes.isEmpty()) {
+ final List neededArtifactTypes, final SpecificationItemId generatedId)
+ {
+ if (neededArtifactTypes.isEmpty())
+ {
LOG.finest(() -> "File " + this.file + ":" + lineNumber + ": found '" + generatedId + "' covering IDs "
+ coveredIds);
- } else {
+ }
+ else
+ {
LOG.finest(() -> "File " + this.file + ":" + lineNumber + ": found '" + generatedId + "' covering IDs "
+ coveredIds + ", needs artifact types " + neededArtifactTypes);
}
}
- private static int parseRevision(final String revision) {
+ private static int parseRevision(final String revision)
+ {
return Optional.ofNullable(revision).map(Integer::parseInt).orElse(0);
}
// [impl->dsn~import.full-coverage-tag-with-needed-coverage-readable-names~1]
private String getItemName(final int lineNumber, final int lineMatchCount, final SpecificationItemId coveredId,
- final List neededArtifactTypes) {
- if (neededArtifactTypes.isEmpty()) {
+ final List neededArtifactTypes)
+ {
+ if (neededArtifactTypes.isEmpty())
+ {
return generateUniqueName(coveredId, lineNumber, lineMatchCount);
}
return coveredId.getName();
}
- private String generateUniqueName(final SpecificationItemId coveredId, final int lineNumber, final int counter) {
+ private String generateUniqueName(final SpecificationItemId coveredId, final int lineNumber, final int counter)
+ {
final String uniqueName = this.file.getPath() + lineNumber + counter + coveredId;
final String checksum = Long.toString(ChecksumCalculator.calculateCrc32(uniqueName));
return coveredId.getName() + "-" + checksum;
diff --git a/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/ShortTagImportingLineConsumer.java b/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/ShortTagImportingLineConsumer.java
index 52c7877a9..d246dbe09 100644
--- a/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/ShortTagImportingLineConsumer.java
+++ b/importer/tag-importer-common/src/main/java/org/itsallcode/openfasttrace/importer/tag/common/ShortTagImportingLineConsumer.java
@@ -3,6 +3,7 @@
import java.util.logging.Logger;
import java.util.regex.Matcher;
+import org.itsallcode.openfasttrace.api.core.SpecificationItem;
import org.itsallcode.openfasttrace.api.core.SpecificationItemId;
import org.itsallcode.openfasttrace.api.importer.ImportEventListener;
import org.itsallcode.openfasttrace.api.importer.ImporterException;
@@ -44,16 +45,11 @@ void processMatch(final Matcher matcher, final int lineNumber, final int lineMat
LOG.finest(() -> "File " + this.file + ":" + lineNumber + ": found '" + tagItemId + "' covering id '"
+ coveredId + "'");
- addItem(lineNumber, coveredId, tagItemId);
- }
-
- private void addItem(final int lineNumber, final SpecificationItemId coveredId,
- final SpecificationItemId tagItemId) {
- this.listener.beginSpecificationItem();
- this.listener.setLocation(this.file.toString(), lineNumber);
- this.listener.setId(tagItemId);
- this.listener.addCoveredId(coveredId);
- this.listener.endSpecificationItem();
+ this.listener.addSpecificationItem(SpecificationItem.builder()
+ .id(tagItemId)
+ .location(this.file.toString(), lineNumber)
+ .addCoveredId(coveredId)
+ .build());
}
private SpecificationItemId createCoveredItem(final String name, final String revision) {
diff --git a/importer/tag-importer-common/src/test/java/org/itsallcode/openfasttrace/importer/tag/common/TestDelegatingLineConsumer.java b/importer/tag-importer-common/src/test/java/org/itsallcode/openfasttrace/importer/tag/common/TestDelegatingLineConsumer.java
new file mode 100644
index 000000000..2ced642f1
--- /dev/null
+++ b/importer/tag-importer-common/src/test/java/org/itsallcode/openfasttrace/importer/tag/common/TestDelegatingLineConsumer.java
@@ -0,0 +1,35 @@
+package org.itsallcode.openfasttrace.importer.tag.common;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.List;
+
+import org.itsallcode.openfasttrace.importer.tag.common.LineReader.LineConsumer;
+import org.junit.jupiter.api.Test;
+
+class TestDelegatingLineConsumer {
+ @Test
+ void testReadLineCallsAllDelegates() {
+ final LineConsumer firstDelegate = mock(LineConsumer.class);
+ final LineConsumer secondDelegate = mock(LineConsumer.class);
+ final DelegatingLineConsumer consumer = new DelegatingLineConsumer(List.of(firstDelegate, secondDelegate));
+
+ consumer.readLine(2, "line");
+
+ verify(firstDelegate).readLine(2, "line");
+ verify(secondDelegate).readLine(2, "line");
+ }
+
+ @Test
+ void testFinishesAllDelegates() {
+ final LineConsumer firstDelegate = mock(LineConsumer.class);
+ final LineConsumer secondDelegate = mock(LineConsumer.class);
+ final DelegatingLineConsumer consumer = new DelegatingLineConsumer(List.of(firstDelegate, secondDelegate));
+
+ consumer.finish();
+
+ verify(firstDelegate).finish();
+ verify(secondDelegate).finish();
+ }
+}
diff --git a/importer/tag-importer-common/src/test/java/org/itsallcode/openfasttrace/importer/tag/common/TestLineReader.java b/importer/tag-importer-common/src/test/java/org/itsallcode/openfasttrace/importer/tag/common/TestLineReader.java
index 1da851f22..69ffb2ad0 100644
--- a/importer/tag-importer-common/src/test/java/org/itsallcode/openfasttrace/importer/tag/common/TestLineReader.java
+++ b/importer/tag-importer-common/src/test/java/org/itsallcode/openfasttrace/importer/tag/common/TestLineReader.java
@@ -96,7 +96,17 @@ void testWrapsConsumerFailureWithLineInformation() {
doThrow(cause).when(this.consumerMock).readLine(1, "line1");
final ImporterException exception = assertThrows(ImporterException.class, () -> readContent("line1"));
- assertThat(exception.getMessage(), equalTo("Error processing line dummy:1 'line1': " + cause));
+ assertThat(exception.getMessage(), equalTo("Error processing line dummy:1 'line1': invalid line"));
+ }
+
+ @Test
+ void testWrapsConsumerFinishFailure() {
+ final RuntimeException cause = new IllegalArgumentException("cannot finish");
+ doThrow(cause).when(this.consumerMock).finish();
+
+ final ImporterException exception = assertThrows(ImporterException.class, () -> readContent(""));
+
+ assertThat(exception.getMessage(), equalTo("Error finishing dummy: cannot finish"));
}
@Test
@@ -126,6 +136,7 @@ private void assertLinesRead(final String... expectedLines) {
inOrder.verify(this.consumerMock).readLine(lineNumber, line);
lineNumber++;
}
+ inOrder.verify(this.consumerMock).finish();
inOrder.verifyNoMoreInteractions();
}
}
diff --git a/importer/tag/src/main/java/org/itsallcode/openfasttrace/importer/tag/TagImporterFactory.java b/importer/tag/src/main/java/org/itsallcode/openfasttrace/importer/tag/TagImporterFactory.java
index 775694084..7d2585665 100644
--- a/importer/tag/src/main/java/org/itsallcode/openfasttrace/importer/tag/TagImporterFactory.java
+++ b/importer/tag/src/main/java/org/itsallcode/openfasttrace/importer/tag/TagImporterFactory.java
@@ -21,7 +21,6 @@ public class TagImporterFactory extends AbstractImporterFactory
"dox", // Doxygen
"c#", "cs", // C#
"cfg", "conf", "ini", // configuration files
- "feature", // Gherkin feature files
"go", // Go
"groovy", // Groovy
"json", "htm", "html", "xhtml", "xml", "yaml", "yml", // markup languages
diff --git a/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporterFactory.java b/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporterFactory.java
index b25305b31..5bef2c5fd 100644
--- a/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporterFactory.java
+++ b/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporterFactory.java
@@ -26,7 +26,7 @@ protected List getSupportedFilenames()
{
return asList("file.java", "FILE.java", "file.md.java", "file.ads", "file.adb", "foo.bash", "foo.bar.bash",
"foo.bat", "foo.java", "foo.c", "foo.C", "foo.c++", "foo.c#", "foo.cc", "foo.cfg",
- "foo.conf", "foo.cpp", "foo.dox", "foo.cs", "foo.feature", "foo.fxml", "foo.go",
+ "foo.conf", "foo.cpp", "foo.dox", "foo.cs", "foo.fxml", "foo.go",
"foo.groovy", "foo.h", "foo.H", "foo.hh", "foo.h++",
"foo.htm", "foo.html", "foo.ini", "foo.js", "foo.kt", "foo.kts", "foo.mjs", "foo.cjs", "foo.ejs",
"foo.ts", "foo.json",
diff --git a/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporterWithConfig.java b/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporterWithConfig.java
index 17bfb1d1d..103a9a394 100644
--- a/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporterWithConfig.java
+++ b/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporterWithConfig.java
@@ -8,6 +8,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
+import org.itsallcode.openfasttrace.api.core.SpecificationItem;
import org.itsallcode.openfasttrace.api.core.SpecificationItemId;
import org.itsallcode.openfasttrace.api.importer.ImportEventListener;
import org.itsallcode.openfasttrace.api.importer.ImporterException;
@@ -70,8 +71,11 @@ void testFileWithNewTagFormatAlsoSupported()
// avoid error in
// self-trace
runImport("[type->" + itemName + "]");
- verify(this.listenerMock)
- .setId(SpecificationItemId.createId("type", "coveredname" + "-3264583751", 0));
+ verify(this.listenerMock).addSpecificationItem(SpecificationItem.builder()
+ .id(SpecificationItemId.createId("type", "coveredname" + "-3264583751", 0))
+ .location(FILE.toString(), 1)
+ .addCoveredId(SpecificationItemId.parseId(itemName))
+ .build());
}
@Test
@@ -139,11 +143,11 @@ void testFileWithLegacyTagFormatWithPrefix()
private void verifyTag(final int lineNumber, final SpecificationItemId coveredId,
final SpecificationItemId tagItemId)
{
- this.inOrderListener.verify(this.listenerMock).beginSpecificationItem();
- this.inOrderListener.verify(this.listenerMock).setLocation(FILE.toString(), lineNumber);
- this.inOrderListener.verify(this.listenerMock).setId(tagItemId);
- this.inOrderListener.verify(this.listenerMock).addCoveredId(coveredId);
- this.inOrderListener.verify(this.listenerMock).endSpecificationItem();
+ this.inOrderListener.verify(this.listenerMock).addSpecificationItem(SpecificationItem.builder()
+ .id(tagItemId)
+ .location(FILE.toString(), lineNumber)
+ .addCoveredId(coveredId)
+ .build());
}
private void runImport(final String content)
diff --git a/oft-self-trace.sh b/oft-self-trace.sh
index d6b5b5471..a35633efb 100755
--- a/oft-self-trace.sh
+++ b/oft-self-trace.sh
@@ -22,6 +22,7 @@ if $oft_script trace \
"$base_dir/importer/specobject/src" \
"$base_dir/importer/zip/src" \
"$base_dir/importer/tag-importer-common/src" \
+ "$base_dir/importer/gherkin/src" \
"$base_dir/importer/tag/src" \
"$base_dir/core/src/main" \
"$base_dir/core/src/test/java" \
diff --git a/parent/pom.xml b/parent/pom.xml
index 2648a3eff..a4863bfe5 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -10,7 +10,7 @@
Free requirement tracking suite
https://github.com/itsallcode/openfasttrace
- 4.6.0
+ 4.7.0
17
6.1.0-M1
6.1.1
@@ -147,6 +147,12 @@
${revision}
compile
+
+ org.itsallcode.openfasttrace
+ openfasttrace-importer-gherkin
+ ${revision}
+ compile
+
org.itsallcode.openfasttrace
openfasttrace-importer-tag-importer-common
@@ -615,7 +621,7 @@
-
org.eclipse.m2e
diff --git a/pom.xml b/pom.xml
index 3b7d3fca3..14e96d6b3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,6 +30,7 @@
importer/restructuredtext
importer/specobject
importer/tag-importer-common
+ importer/gherkin
importer/tag
importer/zip
reporter/plaintext
diff --git a/product/pom.xml b/product/pom.xml
index 58214bd1f..23d7f68fc 100644
--- a/product/pom.xml
+++ b/product/pom.xml
@@ -37,6 +37,10 @@
org.itsallcode.openfasttrace
openfasttrace-importer-specobject
+
+ org.itsallcode.openfasttrace
+ openfasttrace-importer-gherkin
+
org.itsallcode.openfasttrace
openfasttrace-importer-tag
diff --git a/product/src/test/java/org/itsallcode/openfasttrace/TestAllServicesAvailable.java b/product/src/test/java/org/itsallcode/openfasttrace/TestAllServicesAvailable.java
index 33368f051..381bf5d63 100644
--- a/product/src/test/java/org/itsallcode/openfasttrace/TestAllServicesAvailable.java
+++ b/product/src/test/java/org/itsallcode/openfasttrace/TestAllServicesAvailable.java
@@ -69,7 +69,7 @@ private static ReporterFactoryLoader createReporterLoader()
@ParameterizedTest
@CsvSource(
- { "md", "oreqm", "java", "zip" })
+ { "md", "oreqm", "java", "feature", "zip" })
void importerAvailable(final String suffix)
{
final InputFile file = RealFileInput.forPath(Paths.get("file." + suffix));
diff --git a/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java b/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java
index 3144600ae..98ad3c447 100644
--- a/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java
+++ b/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java
@@ -14,6 +14,7 @@
import org.itsallcode.openfasttrace.api.report.ReporterFactory;
import org.itsallcode.openfasttrace.exporter.specobject.SpecobjectExporterFactory;
import org.itsallcode.openfasttrace.importer.markdown.MarkdownImporterFactory;
+import org.itsallcode.openfasttrace.importer.gherkin.GherkinImporterFactory;
import org.itsallcode.openfasttrace.importer.restructuredtext.RestructuredTextImporterFactory;
import org.itsallcode.openfasttrace.importer.specobject.SpecobjectImporterFactory;
import org.itsallcode.openfasttrace.importer.tag.TagImporterFactory;
@@ -48,11 +49,12 @@ void testImporterFactoriesRegistered()
final ImporterContext context = new ImporterContext(null);
final List services = getRegisteredServices(ImporterFactory.class,
context);
- assertThat(services, hasSize(5));
+ assertThat(services, hasSize(6));
assertThat(services, containsInAnyOrder(
instanceOf(MarkdownImporterFactory.class),
instanceOf(RestructuredTextImporterFactory.class),
instanceOf(SpecobjectImporterFactory.class),
+ instanceOf(GherkinImporterFactory.class),
instanceOf(TagImporterFactory.class),
instanceOf(ZipFileImporterFactory.class)));
for (final ImporterFactory importerFactory : services)
diff --git a/product/src/test/java/org/itsallcode/openfasttrace/importer/ImporterFactoryLoaderIT.java b/product/src/test/java/org/itsallcode/openfasttrace/importer/ImporterFactoryLoaderIT.java
index ae55fca3a..aa50f1a68 100644
--- a/product/src/test/java/org/itsallcode/openfasttrace/importer/ImporterFactoryLoaderIT.java
+++ b/product/src/test/java/org/itsallcode/openfasttrace/importer/ImporterFactoryLoaderIT.java
@@ -42,4 +42,27 @@ void testFallBackToTagImporterWhenXmlIsNotSpecObjectFile(@TempDir Path tempDir)
hasProperty("id", hasToString(startsWith("impl~foobar")))
));
}
+
+ // [itest->dsn~gherkin.importer-selection~1]
+ @Test
+ void testSelectsGherkinImporterBeforeTagImporter(@TempDir final Path tempDir) throws IOException {
+ final Oft oft = Oft.create();
+ Files.writeString(tempDir.resolve("login.feature"), """
+ @id:scn~login~1
+ Scenario: Login
+ # [%s]
+ Given [%s]
+ """.formatted("impl~login~1 -> dsn~login~1",
+ "impl~must-not-be-imported~1 -> dsn~login~1"));
+ final ImportSettings settings = ImportSettings.builder()
+ .addInputs(tempDir)
+ .filter(FilterSettings.builder().build())
+ .build();
+
+ final List items = oft.importItems(settings);
+
+ assertThat(items, containsInAnyOrder(
+ hasProperty("id", hasToString("scn~login~1")),
+ hasProperty("id", hasToString("impl~login~1"))));
+ }
}