Skip to content

Commit ab6cb9e

Browse files
authored
#563: Extract tag importer into shared module (#564)
1 parent ad0f9d3 commit ab6cb9e

32 files changed

Lines changed: 698 additions & 289 deletions

‎.github/workflows/broken_links_checker.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ jobs:
1212
contents: read
1313
steps:
1414
- uses: actions/checkout@v7
15+
with:
16+
persist-credentials: false
1517
- name: Configure broken links checker
1618
run: |
1719
mkdir -p ./target

‎.github/workflows/build.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ jobs:
2020
java: 21
2121
- os: ubuntu-latest
2222
java: 25
23+
- os: ubuntu-latest
24+
java: 26
2325

2426
concurrency:
2527
group: ${{ github.workflow }}-${{ github.ref }}-os-${{ matrix.os }}-java-${{ matrix.java }}
@@ -38,6 +40,7 @@ jobs:
3840
- uses: actions/checkout@v7
3941
with:
4042
fetch-depth: 0
43+
persist-credentials: false
4144

4245
- uses: actions/setup-java@v5
4346
name: Set up Java ${{ matrix.java }}

‎.github/workflows/codeql-analysis.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ jobs:
2323
steps:
2424
- name: Checkout repository
2525
uses: actions/checkout@v7
26+
with:
27+
persist-credentials: false
2628

2729
- uses: actions/setup-java@v5
2830
with:

‎.github/workflows/gh-pages.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ jobs:
1717
steps:
1818
- name: Checkout
1919
uses: actions/checkout@v7
20+
with:
21+
persist-credentials: false
2022
- name: Setup Pages
2123
uses: actions/configure-pages@v6
2224
- name: Build with Jekyll

‎.github/workflows/release.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ jobs:
2828
steps:
2929
- name: Checkout
3030
uses: actions/checkout@v7
31+
with:
32+
persist-credentials: false
3133

3234
- name: Fail if not running on main branch
3335
if: ${{ github.ref != 'refs/heads/main' }}

‎.vscode/settings.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"source.generate.finalModifiers": "explicit",
77
"source.fixAll": "explicit"
88
},
9-
"java.saveActions.organizeImports": true,
109
"java.sources.organizeImports.starThreshold": 3,
1110
"java.sources.organizeImports.staticStarThreshold": 3,
1211
"java.configuration.updateBuildConfiguration": "automatic",

‎AGENTS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ You are an expert Java developer specializing in requirement tracing and softwar
3131
- Review all changes with `./oft-self-trace.sh` to ensure tracing completeness.
3232
- Follow the branching strategy: `<type>/<number>_<short-description-lower-snake-case>` (e.g., `feature/533_update_agents_md`).
3333
- Place coverage markers at the narrowest possible scope (method or class).
34+
- Follow the quality requirements in `doc/spec/design/quality_requirements.md`.
3435
- **Ask First**:
3536
- Before adding new external dependencies to `pom.xml`.
3637
- Before changing existing architectural patterns in `openfasttrace-core`.
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# GH-563 Support Gherkin `.feature` Files As OpenFastTrace Specification Documents
2+
3+
## Goal
4+
5+
Allow OpenFastTrace to import specification items from Gherkin `Scenario` and
6+
`Scenario Outline` blocks in `.feature` files. Preserve legacy coverage tags
7+
when they are written in Gherkin comments, while avoiding coverage-tag regular
8+
expression evaluation for executable Gherkin lines.
9+
10+
## Scope
11+
12+
In scope:
13+
14+
* Extract shared line scanning and long/short coverage-tag parsing into a
15+
prerequisite module.
16+
* Add a dedicated Gherkin importer that composes the shared tag parser.
17+
* Import OFT scenario items identified by `@id:<oft-id>`.
18+
* Parse scoped `# Covers:` and `# Needs:` metadata strictly.
19+
* Preserve comment-based legacy coverage tags in `.feature` files and all
20+
existing tag importer behavior for non-`.feature` inputs.
21+
* Update traced requirements, design, tests, and user documentation.
22+
23+
Out of scope:
24+
25+
* Importing `Feature`, `Rule`, `Background`, or `Examples` as specification
26+
items.
27+
* Supporting coverage tags in executable Gherkin lines.
28+
* Adding an external Gherkin parser dependency.
29+
* Moving Gherkin grammar or validation into the shared parser module.
30+
31+
## Design References
32+
33+
* [System Requirements](../spec/system_requirements.md)
34+
* [Design](../spec/design.md)
35+
* [Quality Requirements](../spec/design/quality_requirements.md)
36+
* [User Guide](../user_guide.md)
37+
38+
## Strategy
39+
40+
1. Merge a behavior-preserving refactoring PR that introduces a shared module
41+
for line scanning and long/short coverage-tag parsing. It retains `.feature`
42+
support in the tag importer.
43+
2. Atomically move `.feature` ownership from the tag importer to a new Gherkin
44+
importer with higher precedence when the Gherkin importer is registered.
45+
3. Implement Gherkin parsing as a single-pass state machine. It receives every
46+
line but forwards only comment lines to the shared coverage-tag parser.
47+
4. Keep all Gherkin syntax, state, validation, and `ImportEventListener`
48+
mapping in the Gherkin importer.
49+
50+
## Gherkin Syntax And Behavior
51+
52+
* A Gherkin scenario is an OFT item only when its immediately preceding,
53+
contiguous Gherkin tag region contains exactly one
54+
`@id:<SpecificationItemId>` tag. Other Gherkin tags are ignored.
55+
* Directives are recognized only in `#` comment lines after that ID tag region
56+
and before the associated `Scenario:` or `Scenario Outline:` header.
57+
Comments elsewhere are ignored by Gherkin metadata parsing.
58+
* `# Covers:` and `# Needs:` are case-sensitive. Each is optional but may occur
59+
at most once. When present, it must contain a non-empty comma-separated list;
60+
duplicate values and malformed IDs or artifact types are errors.
61+
* A repeated or invalid `@id` tag, or a scoped directive without exactly one
62+
valid ID, fails the import with an `ImporterException` containing the file,
63+
line, and reason. Scenarios without OFT metadata remain ignored.
64+
* The scenario header line is the item location. Text after `Scenario:` or
65+
`Scenario Outline:` is the title. The importer streams the scenario-step
66+
block into the description, excluding comments and `Examples`; it ends the
67+
item at the next `Scenario`, `Scenario Outline`, `Feature`, `Rule`,
68+
`Background`, `Examples`, or end of file.
69+
* The importer retains only active metadata and previously imported Gherkin IDs
70+
for duplicate detection. It does not buffer a complete file or description.
71+
72+
## Task List
73+
74+
- [ ] Create and checkout branch
75+
`feature/563_support_gherkin_feature_specification_documents`.
76+
77+
### PR 1: Shared Coverage-Tag Parser Refactoring
78+
79+
- [ ] Add `importer/tag-importer-common` with artifact ID
80+
`openfasttrace-importer-tag-importer-common` to the Maven reactor.
81+
- [ ] Add JPMS module `org.itsallcode.openfasttrace.importer.tag.common` and
82+
export only `LineReader` (including its line-consumer contract) and
83+
`CoverageTagParser` from
84+
`org.itsallcode.openfasttrace.importer.tag.common`.
85+
- [ ] Move line scanning, line-handler composition, regex matching, long/short
86+
coverage-tag parsing, and CRC32 ID generation from `importer/tag` into
87+
the shared module without changing parsing semantics, generated IDs,
88+
listener events, logging, or exception wrapping.
89+
- [ ] Define `CoverageTagParser.create(PathConfig, InputFile,
90+
ImportEventListener)` to compose the long-tag parser and, when a path
91+
configuration is present, the short-tag parser into one line consumer.
92+
Keep parser implementation classes encapsulated.
93+
- [ ] Refactor `openfasttrace-importer-tag` into a thin adapter that creates
94+
the shared parser and scans its input once with the shared `LineReader`.
95+
Keep all supported extensions, including `.feature`, unchanged in this
96+
refactoring PR.
97+
- [ ] Move scanner tests to the shared module and add focused shared-parser
98+
tests for representative long and configured short tags, asserting
99+
listener events, locations, generated IDs, coverage links, and needed
100+
artifact types.
101+
- [ ] Keep the existing tag-importer parsing and factory/configuration tests as
102+
regression tests, including `.feature` support, to prove that the
103+
refactoring is behavior-preserving.
104+
105+
### Requirements And Design
106+
107+
- [ ] Add requirements for importing Gherkin scenarios and outlines, strict
108+
scoped metadata validation, Gherkin importer selection, and comment-only
109+
legacy coverage-tag compatibility.
110+
- [ ] Stop and ask user for review of the updated system requirements.
111+
- [ ] Add design items for factory precedence, the streaming Gherkin state
112+
machine, metadata scope, event mapping, and shared-parser delegation.
113+
- [ ] Stop and ask user for review of the updated design.
114+
115+
### PR 2: Gherkin Importer
116+
117+
- [ ] Add `importer/gherkin` with artifact ID
118+
`openfasttrace-importer-gherkin`; register it in the Maven reactor and
119+
product dependencies.
120+
- [ ] Provide a Gherkin importer factory for `.feature` files with priority
121+
`9000`, ahead of the tag importer's priority `10000`.
122+
- [ ] Implement the defined single-pass Gherkin state machine and map imported
123+
scenario fragments to `ImportEventListener` events.
124+
- [ ] Inject the shared coverage-tag parser and forward only lines whose
125+
trimmed form starts with `#` to it.
126+
- [ ] Implement the specified `@id:`, `Covers`, `Needs`, title, description,
127+
boundary, duplicate-ID, and error behavior.
128+
- [ ] Preserve the shared parser's existing `ImporterException` behavior for
129+
legacy coverage tags; do not introduce a new shared validation exception.
130+
131+
### Verification
132+
133+
- [ ] Add Gherkin importer unit tests for valid scenarios and outlines,
134+
location/title/description extraction, coverage metadata, non-OFT tags,
135+
and ignored ordinary scenarios.
136+
- [ ] Add validation tests for invalid or multiple IDs, orphan directives,
137+
repeated or empty directives, malformed list entries, duplicate metadata
138+
values, and duplicate Gherkin IDs. Assert exception type and relevant
139+
message content.
140+
- [ ] Add regression tests proving comment coverage tags import in `.feature`
141+
files, non-comment coverage-tag text is ignored in `.feature` files, and
142+
tag importer behavior for non-`.feature` inputs is unchanged.
143+
- [ ] Add pipeline tests proving each Gherkin file is scanned once and only
144+
comment lines reach the shared coverage-tag parser.
145+
- [ ] Add product-level tests for Gherkin importer precedence and mixed
146+
scenario specifications with comment-based legacy coverage tags.
147+
- [ ] Run `./oft-self-trace.sh` and ensure the trace stays clean.
148+
- [ ] Run `mvn -T 1C verify` and ensure all quality gates pass.
149+
150+
### Documentation And Changelog
151+
152+
- [ ] Extend [doc/user_guide.md](../user_guide.md) with the `.feature` syntax,
153+
placement rules, validation behavior, and examples.
154+
- [ ] Update [.agents/skills/openfasttrace/SKILL.md](../../.agents/skills/openfasttrace/SKILL.md)
155+
with the Gherkin syntax and comment-only compatibility rule.
156+
- [ ] Add the GH-563 entry to [doc/changes/changes_4.6.0.md](../changes/changes_4.6.0.md).

‎doc/spec/design.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ The plugin loader discovers and loads available plugins.
9898
## Importers
9999
For each specification artifact type OFT uses an importer. The importer uses the specification artifact as data source and reads specification items from it.
100100

101+
### Shared Coverage Tag Parser
102+
103+
The `importer/tag-importer-common` module provides the reusable line scanning and coverage-tag parsing used by importers. Its public API consists of `LineReader`, which forwards input lines to a consumer, and `CoverageTagParser`, which recognizes full coverage tags and optionally configured short coverage tags.
104+
105+
The tag importer remains responsible for selecting its input files and creating the shared parser. Parsing implementation classes remain encapsulated in the shared module so that future importers can reuse the same coverage-tag semantics without depending on tag-importer internals.
106+
101107
## Import Event Listener
102108
Importers emit events if they find parts of a [specification item](#specification-item) in the artifact they are importing.
103109

‎doc/spec/design/quality_requirements.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ The following rules are written for both human contributors and coding agents. T
110110
3. Declare method parameters as `final`.
111111
4. Output parameters are only allowed when required by external libraries.
112112
5. Prefer explicit types over `var`.
113+
6. Limit code lines to 120 characters. Do not wrap code earlier solely to fit a shorter line length.
114+
7. Limit comment lines, including JavaDoc, to 80 characters.
113115

114116
### Java Test Rules
115117

0 commit comments

Comments
 (0)