@step-forge/step-forge 0.0.20 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/RUNTIME.md +212 -0
- package/dist/{analyzer-DJyJbU_V.js → analyzer-DYfdoSIS.js} +151 -11
- package/dist/analyzer-DYfdoSIS.js.map +1 -0
- package/dist/analyzer-cli.js +1 -1
- package/dist/analyzer.js +1 -2
- package/dist/cli.cjs +470 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +471 -0
- package/dist/cli.js.map +1 -0
- package/dist/engine-DPRxs6eC.js +181 -0
- package/dist/engine-DPRxs6eC.js.map +1 -0
- package/dist/engine-DWAIlwWp.cjs +204 -0
- package/dist/engine-DWAIlwWp.cjs.map +1 -0
- package/dist/gherkinParser-CHpkEwii.cjs +243 -0
- package/dist/gherkinParser-CHpkEwii.cjs.map +1 -0
- package/dist/{gherkinParser-Dp2d7JNr.js → gherkinParser-CKARHgt4.js} +74 -2
- package/dist/gherkinParser-CKARHgt4.js.map +1 -0
- package/dist/runtime.cjs +5 -160
- package/dist/runtime.d.cts +80 -7
- package/dist/runtime.d.ts +80 -7
- package/dist/runtime.js +2 -158
- package/dist/step-forge.cjs +9 -189
- package/dist/step-forge.cjs.map +1 -1
- package/dist/step-forge.d.cts +6 -6
- package/dist/step-forge.d.ts +6 -6
- package/dist/step-forge.js +3 -158
- package/dist/step-forge.js.map +1 -1
- package/package.json +12 -18
- package/dist/analyzer-DJyJbU_V.js.map +0 -1
- package/dist/gherkinParser-Dp2d7JNr.js.map +0 -1
- package/dist/runtime.cjs.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/vitest.d.ts +0 -74
- package/dist/vitest.js +0 -136
- package/dist/vitest.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Step Forge is a typed wrapper around the Cucumber library. It provides an opinio
|
|
|
4
4
|
|
|
5
5
|
This is just a primer, see the [official documentation site](https://step-forge.com) for more information.
|
|
6
6
|
|
|
7
|
+
To run your `.feature` files against these steps, see the **[Runtime guide](./RUNTIME.md)** — configuration, the `step-forge` CLI, hooks, filtering, and concurrency.
|
|
8
|
+
|
|
7
9
|
## Installation
|
|
8
10
|
|
|
9
11
|
```bash
|
package/RUNTIME.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Step Forge Runtime
|
|
2
|
+
|
|
3
|
+
The Step Forge runtime executes your `.feature` files against your step
|
|
4
|
+
definitions. It is a native TypeScript runner built for speed and Cucumber-like
|
|
5
|
+
semantics — it does **not** run the Cucumber.js runtime, and it does not require
|
|
6
|
+
a bundler or a separate test framework.
|
|
7
|
+
|
|
8
|
+
If you only want to run features, this document is all you need. For how to
|
|
9
|
+
_write_ steps, the world, parsers, and dependencies, see the main
|
|
10
|
+
[README](./README.md).
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
The runner executes under **[Bun](https://bun.sh)** so your TypeScript step
|
|
17
|
+
files run directly, with no transpile/build step and no loader configuration.
|
|
18
|
+
Bun is the one hard requirement:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
curl -fsSL https://bun.sh/install | bash # or: brew install oven-sh/bun/bun
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Everything the runner touches beyond that uses standard `node:` APIs, so your
|
|
25
|
+
step code stays ordinary TypeScript.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install --save-dev @step-forge/step-forge
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
35
|
+
1. **Write step definitions.** A step self-registers the moment you call
|
|
36
|
+
`.step(...)` — no separate registration call. Put them in files matched by
|
|
37
|
+
your `steps` glob (default `**/*.steps.ts`).
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
// features/steps/user.steps.ts
|
|
41
|
+
import { givenBuilder, thenBuilder } from "@step-forge/step-forge";
|
|
42
|
+
|
|
43
|
+
givenBuilder<{ user: string }>()
|
|
44
|
+
.statement((name: string) => `a user named ${name}`)
|
|
45
|
+
.step(({ variables: [name] }) => ({ user: name }));
|
|
46
|
+
|
|
47
|
+
thenBuilder<{ user: string }, {}, {}>()
|
|
48
|
+
.statement((name: string) => `the user is ${name}`)
|
|
49
|
+
.dependencies({ given: { user: "required" } })
|
|
50
|
+
.step(({ variables: [name], given: { user } }) => {
|
|
51
|
+
if (user !== name) throw new Error(`expected ${name}, got ${user}`);
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
2. **Provide a world factory** (optional). One fresh world is created per
|
|
56
|
+
scenario, so state never leaks between scenarios. Omit this to get a default
|
|
57
|
+
`BasicWorld`.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// features/support/world.ts
|
|
61
|
+
import { BasicWorld } from "@step-forge/step-forge";
|
|
62
|
+
export default () => new BasicWorld();
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
3. **Add a config file** (optional but recommended).
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// step-forge.config.ts
|
|
69
|
+
import type { RunnerOptions } from "@step-forge/step-forge/runtime";
|
|
70
|
+
|
|
71
|
+
const config: RunnerOptions = {
|
|
72
|
+
features: "features/**/*.feature",
|
|
73
|
+
steps: "features/**/*.steps.ts",
|
|
74
|
+
world: "features/support/world.ts",
|
|
75
|
+
};
|
|
76
|
+
export default config;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
4. **Run.**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
step-forge
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
A typical `package.json` wires it as the test script:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{ "scripts": { "test": "step-forge" } }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Configuration
|
|
92
|
+
|
|
93
|
+
Config is resolved from three sources, later ones overriding earlier ones:
|
|
94
|
+
**defaults → `step-forge.config.ts` → CLI flags**. Every field is optional.
|
|
95
|
+
|
|
96
|
+
| Field | Type | Default | Meaning |
|
|
97
|
+
| ------------- | ------------------------- | ------------------ | ----------------------------------------------------------------------- |
|
|
98
|
+
| `features` | `string \| string[]` | `**/*.feature` | Feature-file glob(s), relative to the config directory. |
|
|
99
|
+
| `steps` | `string \| string[]` | `**/*.steps.ts` | Step-module glob(s). Importing them is what registers your steps. |
|
|
100
|
+
| `world` | `string` | `BasicWorld` | Module that default-exports a world factory `() => world`. |
|
|
101
|
+
| `concurrency` | `number` | `1` (serial) | Max scenarios in flight at once. See [Concurrency](#concurrency). |
|
|
102
|
+
| `reporter` | `"pretty" \| "progress"` | `pretty` | Output style. See [Reporters](#reporters). |
|
|
103
|
+
| `name` | `string` | — | Only scenarios whose name matches (substring, or `/regex/flags`). |
|
|
104
|
+
| `tags` | `string` | — | Cucumber tag expression, e.g. `@smoke and not @wip`. |
|
|
105
|
+
|
|
106
|
+
The config file is loaded by Bun, so it may be TypeScript and import the
|
|
107
|
+
`RunnerOptions` type for editor help.
|
|
108
|
+
|
|
109
|
+
## CLI
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
step-forge [options] [feature globs...]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Positional arguments are feature globs and **override** the configured
|
|
116
|
+
`features`, so you can run a subset ad hoc.
|
|
117
|
+
|
|
118
|
+
| Flag | Short | Description |
|
|
119
|
+
| ----------------------- | ----- | ------------------------------------------------------- |
|
|
120
|
+
| `--tags <expr>` | `-t` | Tag expression, e.g. `"@smoke and not @wip"`. |
|
|
121
|
+
| `--name <pattern>` | `-n` | Only scenarios whose name matches (substring/`/regex/`).|
|
|
122
|
+
| `--steps <glob>` | `-s` | Step-module glob (repeatable). |
|
|
123
|
+
| `--world <module>` | `-w` | World factory module. |
|
|
124
|
+
| `--concurrency <n>` | `-c` | Max scenarios in flight (default `1`). |
|
|
125
|
+
| `--reporter <name>` | `-r` | `pretty` (default) or `progress`. |
|
|
126
|
+
| `--config <path>` | | Directory to resolve the config file and globs from. |
|
|
127
|
+
| `--help` | `-h` | Show usage. |
|
|
128
|
+
|
|
129
|
+
Examples:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
step-forge features/checkout.feature # one file
|
|
133
|
+
step-forge --tags "@smoke and not @wip" # by tag
|
|
134
|
+
step-forge --name "logs in" # by scenario name
|
|
135
|
+
step-forge --concurrency 8 --reporter progress # parallel, compact output
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The process exits `0` when every scenario passes and `1` when any scenario
|
|
139
|
+
fails, so it drops straight into CI.
|
|
140
|
+
|
|
141
|
+
## Hooks
|
|
142
|
+
|
|
143
|
+
Hooks are side-effect callbacks for setup/teardown (start a server, reset a
|
|
144
|
+
mock). They **never seed scenario state** — that flows exclusively through
|
|
145
|
+
`given`/`when`/`then` so the typed dependency graph stays the single source of
|
|
146
|
+
truth. Import them from the main entry:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import {
|
|
150
|
+
beforeAll,
|
|
151
|
+
afterAll,
|
|
152
|
+
beforeFeature,
|
|
153
|
+
afterFeature,
|
|
154
|
+
beforeScenario,
|
|
155
|
+
afterScenario,
|
|
156
|
+
} from "@step-forge/step-forge";
|
|
157
|
+
|
|
158
|
+
beforeScenario(({ world, scenario }) => {
|
|
159
|
+
/* fresh world available; reset external state here */
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
| Hook | Runs |
|
|
164
|
+
| --------------------- | ------------------------------------------------ |
|
|
165
|
+
| `beforeAll`/`afterAll` | Once around the entire run. |
|
|
166
|
+
| `beforeFeature`/`afterFeature` | Around each feature file. |
|
|
167
|
+
| `beforeScenario`/`afterScenario` | Around each scenario (gets its world).|
|
|
168
|
+
|
|
169
|
+
`after*` hooks run in reverse registration order so teardown unwinds setup, and
|
|
170
|
+
`afterScenario` runs even when a step failed.
|
|
171
|
+
|
|
172
|
+
## Filtering
|
|
173
|
+
|
|
174
|
+
- **Tags** — `--tags` / `tags` accepts a Cucumber tag expression with `and`,
|
|
175
|
+
`or`, `not`, parentheses, and `@tag` atoms:
|
|
176
|
+
`@smoke and (@fast or not @flaky)`.
|
|
177
|
+
- **Name** — `--name` / `name` is a substring by default, or a regex if written
|
|
178
|
+
as `/pattern/flags`.
|
|
179
|
+
- **`@skip`** — a scenario tagged `@skip` is never executed and is reported as
|
|
180
|
+
skipped (not dropped silently).
|
|
181
|
+
- **`@only`** — if any selected scenario is tagged `@only`, the run is focused to
|
|
182
|
+
just those. `@skip` wins over `@only`.
|
|
183
|
+
|
|
184
|
+
## Concurrency
|
|
185
|
+
|
|
186
|
+
Scenarios run **serially by default** (`concurrency: 1`), matching Cucumber.
|
|
187
|
+
Raise `--concurrency` (or the `concurrency` config field) to run scenarios
|
|
188
|
+
concurrently in a single process — ideal for I/O-bound suites.
|
|
189
|
+
|
|
190
|
+
This is safe because of one opinionated invariant Step Forge holds:
|
|
191
|
+
|
|
192
|
+
> **Module state is immutable and scenario-isolated.** All mutable scenario
|
|
193
|
+
> state lives in the per-scenario world (already isolated); anything at module
|
|
194
|
+
> scope is write-once configuration or pure lookups.
|
|
195
|
+
|
|
196
|
+
Because mutable state can't be shared across scenarios, concurrent scenarios
|
|
197
|
+
cannot corrupt one another — no worker threads or process isolation required.
|
|
198
|
+
The corollary is a rule to follow in your own code: **do not have a hook or step
|
|
199
|
+
write to a mutable module-level variable that another step reads.** That is a
|
|
200
|
+
cross-scenario data race and the only way to break parallel runs. Keep
|
|
201
|
+
per-scenario state in the world and you can turn concurrency up freely.
|
|
202
|
+
|
|
203
|
+
## Reporters
|
|
204
|
+
|
|
205
|
+
- **`pretty`** (default) — a Cucumber-style tree grouped by feature, each
|
|
206
|
+
scenario listing its steps with pass/fail/skip marks, then a summary. Failures
|
|
207
|
+
include a stack frame pointing at the failing line in the `.feature` file.
|
|
208
|
+
- **`progress`** — one character per scenario as it finishes (`.` pass, `F`
|
|
209
|
+
fail, `-` skip), then failures in detail and the summary. Best for large
|
|
210
|
+
suites.
|
|
211
|
+
|
|
212
|
+
Colour is emitted only to a TTY and is disabled when `NO_COLOR` is set.
|
|
@@ -1,7 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { glob } from "node:fs/promises";
|
|
1
|
+
import { glob, stat } from "node:fs/promises";
|
|
3
2
|
import * as path from "node:path";
|
|
4
3
|
import ts from "typescript";
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
|
|
6
|
+
import * as messages from "@cucumber/messages";
|
|
7
|
+
//#region src/globFiles.ts
|
|
8
|
+
/** Glob metacharacters. A pattern with none of these is a literal path. */
|
|
9
|
+
const MAGIC = /[*?[\]{}!()]/;
|
|
10
|
+
async function isFile(p) {
|
|
11
|
+
try {
|
|
12
|
+
return (await stat(p)).isFile();
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve glob patterns to a de-duplicated list of absolute file paths, with one
|
|
19
|
+
* important portability guarantee: a **literal absolute path** (no glob magic)
|
|
20
|
+
* is returned directly if it exists, without being handed to `glob()`.
|
|
21
|
+
*
|
|
22
|
+
* This exists because `node:fs`'s `glob` diverges between runtimes — Node
|
|
23
|
+
* matches an absolute-path pattern, Bun returns nothing for one. Rather than
|
|
24
|
+
* depend on that behaviour, we only ever glob relative patterns (against `cwd`)
|
|
25
|
+
* and short-circuit concrete absolute paths ourselves, so callers get identical
|
|
26
|
+
* results under Node and Bun.
|
|
27
|
+
*/
|
|
28
|
+
async function globFiles(patterns, cwd = process.cwd()) {
|
|
29
|
+
const files = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const pattern of patterns) {
|
|
31
|
+
if (path.isAbsolute(pattern) && !MAGIC.test(pattern)) {
|
|
32
|
+
if (await isFile(pattern)) files.add(pattern);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
for await (const match of glob(pattern, { cwd })) files.add(path.resolve(cwd, match));
|
|
36
|
+
}
|
|
37
|
+
return [...files];
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
5
40
|
//#region src/analyzer/stepExtractor.ts
|
|
6
41
|
const BUILDER_NAMES = {
|
|
7
42
|
givenBuilder: "given",
|
|
@@ -219,6 +254,116 @@ function resolveReExportedCall(chain, checker) {
|
|
|
219
254
|
return null;
|
|
220
255
|
}
|
|
221
256
|
//#endregion
|
|
257
|
+
//#region src/analyzer/gherkinParser.ts
|
|
258
|
+
function parseFeatureFiles(filePaths) {
|
|
259
|
+
const scenarios = [];
|
|
260
|
+
for (const filePath of filePaths) {
|
|
261
|
+
const parsed = parseFeatureContent(fs.readFileSync(filePath, "utf-8"), filePath);
|
|
262
|
+
scenarios.push(...parsed);
|
|
263
|
+
}
|
|
264
|
+
return scenarios;
|
|
265
|
+
}
|
|
266
|
+
function parseFeatureContent(content, filePath) {
|
|
267
|
+
const feature = new Parser(new AstBuilder(messages.IdGenerator.uuid()), new GherkinClassicTokenMatcher()).parse(content).feature;
|
|
268
|
+
if (!feature) return [];
|
|
269
|
+
const featureBackground = [];
|
|
270
|
+
const scenarios = [];
|
|
271
|
+
const featureTags = tagNames(feature.tags);
|
|
272
|
+
for (const child of feature.children) {
|
|
273
|
+
if (child.background) featureBackground.push(...child.background.steps);
|
|
274
|
+
if (child.scenario) scenarios.push(...expandScenario(child.scenario, featureBackground, filePath, featureTags));
|
|
275
|
+
if (child.rule) {
|
|
276
|
+
const ruleBackground = [...featureBackground];
|
|
277
|
+
const ruleTags = [...featureTags, ...tagNames(child.rule.tags)];
|
|
278
|
+
for (const ruleChild of child.rule.children) {
|
|
279
|
+
if (ruleChild.background) ruleBackground.push(...ruleChild.background.steps);
|
|
280
|
+
if (ruleChild.scenario) scenarios.push(...expandScenario(ruleChild.scenario, ruleBackground, filePath, ruleTags));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return scenarios;
|
|
285
|
+
}
|
|
286
|
+
/** Extract tag names (each keeping its leading `@`), deduped in order. */
|
|
287
|
+
function tagNames(tags) {
|
|
288
|
+
return [...new Set((tags ?? []).map((t) => t.name))];
|
|
289
|
+
}
|
|
290
|
+
function expandScenario(scenario, backgroundSteps, filePath, inheritedTags) {
|
|
291
|
+
const scenarioTags = [...inheritedTags, ...tagNames(scenario.tags)];
|
|
292
|
+
if (!(scenario.examples.length > 0 && scenario.examples.some((e) => e.tableBody.length > 0))) {
|
|
293
|
+
const bgParsed = convertSteps(backgroundSteps);
|
|
294
|
+
const scenarioParsed = convertSteps(scenario.steps);
|
|
295
|
+
const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioParsed]);
|
|
296
|
+
return [{
|
|
297
|
+
name: scenario.name,
|
|
298
|
+
file: filePath,
|
|
299
|
+
steps: allSteps,
|
|
300
|
+
tags: scenarioTags
|
|
301
|
+
}];
|
|
302
|
+
}
|
|
303
|
+
const results = [];
|
|
304
|
+
for (const example of scenario.examples) {
|
|
305
|
+
if (!example.tableHeader || example.tableBody.length === 0) continue;
|
|
306
|
+
const headers = example.tableHeader.cells.map((c) => c.value);
|
|
307
|
+
const exampleTags = [...scenarioTags, ...tagNames(example.tags)];
|
|
308
|
+
for (const row of example.tableBody) {
|
|
309
|
+
const values = row.cells.map((c) => c.value);
|
|
310
|
+
const substitution = {};
|
|
311
|
+
headers.forEach((h, i) => {
|
|
312
|
+
substitution[h] = values[i];
|
|
313
|
+
});
|
|
314
|
+
const bgParsed = convertSteps(backgroundSteps);
|
|
315
|
+
const scenarioSteps = convertSteps(scenario.steps).map((step) => ({
|
|
316
|
+
...step,
|
|
317
|
+
text: substituteExampleValues(step.text, substitution)
|
|
318
|
+
}));
|
|
319
|
+
const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioSteps]);
|
|
320
|
+
results.push({
|
|
321
|
+
name: headers.map((h, i) => `${h}=${values[i]}`).join(", "),
|
|
322
|
+
file: filePath,
|
|
323
|
+
steps: allSteps,
|
|
324
|
+
tags: exampleTags,
|
|
325
|
+
outline: { name: scenario.name }
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return results;
|
|
330
|
+
}
|
|
331
|
+
function convertSteps(steps) {
|
|
332
|
+
return steps.map((step) => ({
|
|
333
|
+
keyword: normalizeKeyword(step.keyword),
|
|
334
|
+
text: step.text,
|
|
335
|
+
line: step.location.line,
|
|
336
|
+
column: (step.location.column ?? 1) + step.keyword.length
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
function normalizeKeyword(keyword) {
|
|
340
|
+
const trimmed = keyword.trim();
|
|
341
|
+
if (trimmed === "Given") return "Given";
|
|
342
|
+
if (trimmed === "When") return "When";
|
|
343
|
+
if (trimmed === "Then") return "Then";
|
|
344
|
+
if (trimmed === "And") return "And";
|
|
345
|
+
if (trimmed === "But") return "But";
|
|
346
|
+
return "Given";
|
|
347
|
+
}
|
|
348
|
+
function resolveEffectiveKeywords(steps) {
|
|
349
|
+
let lastEffective = "Given";
|
|
350
|
+
return steps.map((step) => {
|
|
351
|
+
let effectiveKeyword;
|
|
352
|
+
if (step.keyword === "And" || step.keyword === "But") effectiveKeyword = lastEffective;
|
|
353
|
+
else effectiveKeyword = step.keyword;
|
|
354
|
+
lastEffective = effectiveKeyword;
|
|
355
|
+
return {
|
|
356
|
+
...step,
|
|
357
|
+
effectiveKeyword
|
|
358
|
+
};
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function substituteExampleValues(text, substitution) {
|
|
362
|
+
let result = text;
|
|
363
|
+
for (const [key, value] of Object.entries(substitution)) result = result.replace(new RegExp(`<${key}>`, "g"), value);
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
//#endregion
|
|
222
367
|
//#region src/analyzer/stepMatcher.ts
|
|
223
368
|
function compileDefinitions(definitions) {
|
|
224
369
|
const compiled = [];
|
|
@@ -353,8 +498,8 @@ function runRules(rules, scenario, matchedSteps) {
|
|
|
353
498
|
//#region src/analyzer/index.ts
|
|
354
499
|
async function analyze(config, options) {
|
|
355
500
|
const rules = options?.rules ?? defaultRules;
|
|
356
|
-
const stepFilePaths = await
|
|
357
|
-
const featureFilePaths = await
|
|
501
|
+
const stepFilePaths = await globFiles(config.stepFiles);
|
|
502
|
+
const featureFilePaths = await globFiles(config.featureFiles);
|
|
358
503
|
if (stepFilePaths.length === 0) return [];
|
|
359
504
|
if (featureFilePaths.length === 0) return [];
|
|
360
505
|
const stepDefinitions = extractStepDefinitions(stepFilePaths, config.tsConfigPath);
|
|
@@ -366,12 +511,7 @@ async function analyze(config, options) {
|
|
|
366
511
|
}
|
|
367
512
|
return diagnostics;
|
|
368
513
|
}
|
|
369
|
-
async function resolveGlobs(patterns) {
|
|
370
|
-
const files = [];
|
|
371
|
-
for (const pattern of patterns) for await (const file of glob(pattern)) files.push(path.resolve(file));
|
|
372
|
-
return [...new Set(files)];
|
|
373
|
-
}
|
|
374
514
|
//#endregion
|
|
375
|
-
export {
|
|
515
|
+
export { parseFeatureContent as a, matchScenarioSteps as i, defaultRules as n, parseFeatureFiles as o, findMatchingDefinitions as r, extractStepDefinitions as s, analyze as t };
|
|
376
516
|
|
|
377
|
-
//# sourceMappingURL=analyzer-
|
|
517
|
+
//# sourceMappingURL=analyzer-DYfdoSIS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer-DYfdoSIS.js","names":[],"sources":["../../src/globFiles.ts","../../src/analyzer/stepExtractor.ts","../../src/analyzer/gherkinParser.ts","../../src/analyzer/stepMatcher.ts","../../src/analyzer/rules/ambiguousStepRule.ts","../../src/analyzer/rules/dependencyRule.ts","../../src/analyzer/rules/undefinedStepRule.ts","../../src/analyzer/rules/index.ts","../../src/analyzer/index.ts"],"sourcesContent":["import { glob } from \"node:fs/promises\";\nimport { stat } from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\n/** Glob metacharacters. A pattern with none of these is a literal path. */\nconst MAGIC = /[*?[\\]{}!()]/;\n\nasync function isFile(p: string): Promise<boolean> {\n try {\n return (await stat(p)).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve glob patterns to a de-duplicated list of absolute file paths, with one\n * important portability guarantee: a **literal absolute path** (no glob magic)\n * is returned directly if it exists, without being handed to `glob()`.\n *\n * This exists because `node:fs`'s `glob` diverges between runtimes — Node\n * matches an absolute-path pattern, Bun returns nothing for one. Rather than\n * depend on that behaviour, we only ever glob relative patterns (against `cwd`)\n * and short-circuit concrete absolute paths ourselves, so callers get identical\n * results under Node and Bun.\n */\nexport async function globFiles(\n patterns: string[],\n cwd: string = process.cwd()\n): Promise<string[]> {\n const files = new Set<string>();\n for (const pattern of patterns) {\n if (path.isAbsolute(pattern) && !MAGIC.test(pattern)) {\n if (await isFile(pattern)) files.add(pattern);\n continue;\n }\n for await (const match of glob(pattern, { cwd })) {\n files.add(path.resolve(cwd, match));\n }\n }\n return [...files];\n}\n","import ts from \"typescript\";\nimport { StepDefinitionMeta } from \"./types.js\";\n\ntype StepType = \"given\" | \"when\" | \"then\";\n\nconst BUILDER_NAMES: Record<string, StepType> = {\n givenBuilder: \"given\",\n whenBuilder: \"when\",\n thenBuilder: \"then\",\n};\n\nexport function extractStepDefinitions(\n filePaths: string[],\n tsConfigPath?: string\n): StepDefinitionMeta[] {\n const configPath =\n tsConfigPath ?? ts.findConfigFile(process.cwd(), ts.sys.fileExists);\n let compilerOptions: ts.CompilerOptions = {\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.ESNext,\n moduleResolution: ts.ModuleResolutionKind.Node10,\n strict: true,\n esModuleInterop: true,\n };\n\n if (configPath) {\n const configFile = ts.readConfigFile(configPath, ts.sys.readFile);\n if (!configFile.error) {\n const parsed = ts.parseJsonConfigFileContent(\n configFile.config,\n ts.sys,\n configPath.replace(/[/\\\\][^/\\\\]+$/, \"\")\n );\n compilerOptions = parsed.options;\n }\n }\n\n // Ensure noEmit so we don't write files\n compilerOptions.noEmit = true;\n\n const program = ts.createProgram(filePaths, compilerOptions);\n const checker = program.getTypeChecker();\n const results: StepDefinitionMeta[] = [];\n\n for (const filePath of filePaths) {\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) continue;\n const fileResults = extractFromSourceFile(sourceFile, checker);\n results.push(...fileResults);\n }\n\n return results;\n}\n\nfunction extractFromSourceFile(\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker\n): StepDefinitionMeta[] {\n const results: StepDefinitionMeta[] = [];\n\n function visit(node: ts.Node) {\n // The chain now terminates at `.step(...)`, which is the registration\n // point (there is no `.register()` anymore).\n if (\n ts.isCallExpression(node) &&\n ts.isPropertyAccessExpression(node.expression) &&\n node.expression.name.text === \"step\"\n ) {\n const meta = extractFromRegisterCall(node, sourceFile, checker);\n if (meta) {\n results.push(meta);\n }\n }\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return results;\n}\n\nfunction extractFromRegisterCall(\n registerCall: ts.CallExpression,\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker\n): StepDefinitionMeta | null {\n // Walk backwards through the method chain to find all parts\n // Pattern: builder().statement(...).dependencies?(...).step(...).register()\n // Or: Variable(\"...\").dependencies?(...).step(...).register()\n\n const chain = collectCallChain(registerCall);\n\n let stepType: StepType | null = null;\n let expression: string | null = null;\n let dependencies: StepDefinitionMeta[\"dependencies\"] = {\n given: {},\n when: {},\n then: {},\n };\n let produces: string[] = [];\n\n for (const link of chain) {\n const name = getCallName(link);\n if (!name) continue;\n\n if (name === \"register\") {\n // Already at register, continue\n continue;\n }\n\n if (name === \"step\") {\n produces = extractProducedKeys(link, checker);\n continue;\n }\n\n if (name === \"dependencies\") {\n dependencies = extractDependencies(link);\n continue;\n }\n\n if (name === \"statement\") {\n expression = extractExpression(link);\n // Try to find the builder type by continuing up the chain\n continue;\n }\n\n // Check if this is a builder call like givenBuilder()\n if (BUILDER_NAMES[name]) {\n stepType = BUILDER_NAMES[name];\n continue;\n }\n }\n\n // If we didn't find the builder or expression in the chain, try the \"re-exported\" pattern:\n // const Given = givenBuilder<T>().statement;\n // Given(\"foo\").step(...).register()\n if (!stepType || !expression) {\n const reExport = resolveReExportedCall(chain, checker);\n if (reExport) {\n if (!stepType) stepType = reExport.stepType;\n if (!expression) expression = reExport.expression;\n }\n }\n\n if (!stepType || !expression) {\n return null;\n }\n\n const line =\n sourceFile.getLineAndCharacterOfPosition(registerCall.getStart()).line + 1;\n\n return {\n stepType,\n expression,\n dependencies,\n produces,\n sourceFile: sourceFile.fileName,\n line,\n };\n}\n\n/**\n * Collect all call expressions in the method chain, from register() back to the origin.\n */\nfunction collectCallChain(call: ts.CallExpression): ts.CallExpression[] {\n const chain: ts.CallExpression[] = [call];\n let current: ts.Expression = call.expression;\n\n while (true) {\n // Walk through PropertyAccessExpression to find the next call\n if (ts.isPropertyAccessExpression(current)) {\n current = current.expression;\n }\n\n if (ts.isCallExpression(current)) {\n chain.push(current);\n current = current.expression;\n } else {\n break;\n }\n }\n\n return chain;\n}\n\nfunction getCallName(call: ts.CallExpression): string | null {\n const expr = call.expression;\n\n // .method() pattern\n if (ts.isPropertyAccessExpression(expr)) {\n return expr.name.text;\n }\n\n // direct call: functionName()\n if (ts.isIdentifier(expr)) {\n return expr.text;\n }\n\n return null;\n}\n\nfunction extractExpression(statementCall: ts.CallExpression): string | null {\n const arg = statementCall.arguments[0];\n if (!arg) return null;\n\n // String literal: .statement(\"a user\")\n if (ts.isStringLiteral(arg)) {\n return arg.text;\n }\n\n // Arrow function with template literal: .statement((name: string) => `a user named ${name}`)\n if (ts.isArrowFunction(arg)) {\n return extractExpressionFromArrowFunction(arg);\n }\n\n return null;\n}\n\nfunction extractExpressionFromArrowFunction(\n fn: ts.ArrowFunction\n): string | null {\n const params = fn.parameters.map((p) => p.name.getText());\n\n // The body should be a template expression or string literal\n let body = fn.body;\n\n // If wrapped in a block with a return, unwrap\n if (ts.isBlock(body)) {\n const returnStmt = body.statements.find(ts.isReturnStatement);\n if (returnStmt?.expression) {\n body = returnStmt.expression;\n } else {\n return null;\n }\n }\n\n if (ts.isTemplateExpression(body)) {\n return reconstructExpressionFromTemplate(body, params);\n }\n\n if (ts.isNoSubstitutionTemplateLiteral(body)) {\n return body.text;\n }\n\n return null;\n}\n\nfunction reconstructExpressionFromTemplate(\n template: ts.TemplateExpression,\n paramNames: string[]\n): string {\n let result = template.head.text;\n\n for (const span of template.templateSpans) {\n if (ts.isIdentifier(span.expression) && paramNames.includes(span.expression.text)) {\n result += \"{string}\";\n } else {\n // Non-parameter expression, use {string} as fallback\n result += \"{string}\";\n }\n result += span.literal.text;\n }\n\n return result;\n}\n\nfunction extractDependencies(\n depsCall: ts.CallExpression\n): StepDefinitionMeta[\"dependencies\"] {\n const deps: StepDefinitionMeta[\"dependencies\"] = {\n given: {},\n when: {},\n then: {},\n };\n\n const arg = depsCall.arguments[0];\n if (!arg || !ts.isObjectLiteralExpression(arg)) return deps;\n\n for (const prop of arg.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))\n continue;\n\n const phase = prop.name.text as \"given\" | \"when\" | \"then\";\n if (!deps[phase]) continue;\n\n if (ts.isObjectLiteralExpression(prop.initializer)) {\n for (const innerProp of prop.initializer.properties) {\n if (\n ts.isPropertyAssignment(innerProp) &&\n ts.isIdentifier(innerProp.name) &&\n ts.isStringLiteral(innerProp.initializer)\n ) {\n const val = innerProp.initializer.text;\n if (val === \"required\" || val === \"optional\") {\n deps[phase][innerProp.name.text] = val;\n }\n }\n }\n }\n }\n\n return deps;\n}\n\nfunction extractProducedKeys(\n stepCall: ts.CallExpression,\n checker: ts.TypeChecker\n): string[] {\n const callback = stepCall.arguments[0];\n if (!callback) return [];\n\n // Try to get the return type of the callback by analyzing its body\n if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {\n return extractProducedKeysFromCallback(callback, checker);\n }\n\n return [];\n}\n\nfunction extractProducedKeysFromCallback(\n callback: ts.ArrowFunction | ts.FunctionExpression,\n checker: ts.TypeChecker\n): string[] {\n const body = callback.body;\n\n // Concise arrow: () => ({ key: value })\n if (!ts.isBlock(body)) {\n return extractKeysFromExpression(body, checker);\n }\n\n // Block body: look at return statements\n const keys = new Set<string>();\n function visitReturn(node: ts.Node) {\n if (ts.isReturnStatement(node) && node.expression) {\n for (const key of extractKeysFromExpression(node.expression, checker)) {\n keys.add(key);\n }\n }\n ts.forEachChild(node, visitReturn);\n }\n visitReturn(body);\n return [...keys];\n}\n\nfunction extractKeysFromExpression(\n expr: ts.Expression,\n checker: ts.TypeChecker\n): string[] {\n // Unwrap parenthesized expressions\n while (ts.isParenthesizedExpression(expr)) {\n expr = expr.expression;\n }\n\n // Object literal: { user: ..., token: ... }\n if (ts.isObjectLiteralExpression(expr)) {\n return expr.properties\n .filter(\n (p): p is ts.PropertyAssignment | ts.ShorthandPropertyAssignment =>\n ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)\n )\n .map((p) => p.name.getText())\n .filter(Boolean);\n }\n\n // Try type checker as fallback\n try {\n const type = checker.getTypeAtLocation(expr);\n return type\n .getProperties()\n .map((p) => p.name)\n .filter((n) => n !== \"merge\");\n } catch {\n return [];\n }\n}\n\ninterface ReExportResult {\n stepType: StepType;\n expression: string | null;\n}\n\nfunction resolveReExportedCall(\n chain: ts.CallExpression[],\n checker: ts.TypeChecker\n): ReExportResult | null {\n // Look for the pattern: Variable(\"...\")... where Variable was assigned from builderType().statement\n // The last call in the chain (furthest from register) should be the variable call\n\n const lastCall = chain[chain.length - 1];\n if (!lastCall) return null;\n\n const expr = lastCall.expression;\n\n // If it's an identifier (like \"Given\", \"When\", \"Then\"), trace its declaration\n let identifier: ts.Identifier | null = null;\n if (ts.isIdentifier(expr)) {\n identifier = expr;\n } else if (\n ts.isPropertyAccessExpression(expr) &&\n ts.isIdentifier(expr.expression)\n ) {\n identifier = expr.expression;\n }\n\n if (!identifier) return null;\n\n const symbol = checker.getSymbolAtLocation(identifier);\n if (!symbol) return null;\n\n const decl = symbol.valueDeclaration;\n if (!decl || !ts.isVariableDeclaration(decl) || !decl.initializer)\n return null;\n\n // Check if initializer is builderType<T>().statement\n const init = decl.initializer;\n\n // Pattern: givenBuilder<T>().statement (PropertyAccessExpression)\n if (ts.isPropertyAccessExpression(init) && init.name.text === \"statement\") {\n const callExpr = init.expression;\n if (ts.isCallExpression(callExpr)) {\n const callee = callExpr.expression;\n if (ts.isIdentifier(callee) && BUILDER_NAMES[callee.text]) {\n // The lastCall IS the statement call — extract expression from it\n const expression = extractExpression(lastCall);\n return {\n stepType: BUILDER_NAMES[callee.text],\n expression,\n };\n }\n }\n }\n\n return null;\n}\n","import * as fs from \"node:fs\";\nimport { GherkinClassicTokenMatcher, Parser, AstBuilder } from \"@cucumber/gherkin\";\nimport * as messages from \"@cucumber/messages\";\nimport { ParsedScenario, ParsedStep } from \"./types.js\";\n\ntype GherkinKeyword = \"Given\" | \"When\" | \"Then\" | \"And\" | \"But\";\n\nexport function parseFeatureFiles(filePaths: string[]): ParsedScenario[] {\n const scenarios: ParsedScenario[] = [];\n\n for (const filePath of filePaths) {\n const content = fs.readFileSync(filePath, \"utf-8\");\n const parsed = parseFeatureContent(content, filePath);\n scenarios.push(...parsed);\n }\n\n return scenarios;\n}\n\nexport function parseFeatureContent(\n content: string,\n filePath: string\n): ParsedScenario[] {\n const newId = messages.IdGenerator.uuid();\n const builder = new AstBuilder(newId);\n const matcher = new GherkinClassicTokenMatcher();\n const parser = new Parser(builder, matcher);\n\n const gherkinDocument: messages.GherkinDocument = parser.parse(content);\n const feature = gherkinDocument.feature;\n if (!feature) return [];\n\n // Collect background steps at the feature level\n const featureBackground: messages.Step[] = [];\n const scenarios: ParsedScenario[] = [];\n const featureTags = tagNames(feature.tags);\n\n for (const child of feature.children) {\n if (child.background) {\n featureBackground.push(...child.background.steps);\n }\n\n if (child.scenario) {\n scenarios.push(\n ...expandScenario(\n child.scenario,\n featureBackground,\n filePath,\n featureTags\n )\n );\n }\n\n if (child.rule) {\n // Rules can have their own backgrounds and tags, both inherited by the\n // rule's scenarios.\n const ruleBackground: messages.Step[] = [...featureBackground];\n const ruleTags = [...featureTags, ...tagNames(child.rule.tags)];\n for (const ruleChild of child.rule.children) {\n if (ruleChild.background) {\n ruleBackground.push(...ruleChild.background.steps);\n }\n if (ruleChild.scenario) {\n scenarios.push(\n ...expandScenario(\n ruleChild.scenario,\n ruleBackground,\n filePath,\n ruleTags\n )\n );\n }\n }\n }\n }\n\n return scenarios;\n}\n\n/** Extract tag names (each keeping its leading `@`), deduped in order. */\nfunction tagNames(tags: readonly messages.Tag[] | undefined): string[] {\n return [...new Set((tags ?? []).map(t => t.name))];\n}\n\nfunction expandScenario(\n scenario: messages.Scenario,\n backgroundSteps: messages.Step[],\n filePath: string,\n inheritedTags: string[]\n): ParsedScenario[] {\n const scenarioTags = [...inheritedTags, ...tagNames(scenario.tags)];\n const hasExamples =\n scenario.examples.length > 0 &&\n scenario.examples.some((e) => e.tableBody.length > 0);\n\n if (!hasExamples) {\n // Regular scenario\n const bgParsed = convertSteps(backgroundSteps);\n const scenarioParsed = convertSteps(scenario.steps);\n const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioParsed]);\n\n return [\n {\n name: scenario.name,\n file: filePath,\n steps: allSteps,\n tags: scenarioTags,\n },\n ];\n }\n\n // Scenario Outline — expand with each example row. Rows carry the outline's\n // base name so the runner can group them, plus the Examples-block tags.\n const results: ParsedScenario[] = [];\n for (const example of scenario.examples) {\n if (!example.tableHeader || example.tableBody.length === 0) continue;\n const headers = example.tableHeader.cells.map((c) => c.value);\n const exampleTags = [...scenarioTags, ...tagNames(example.tags)];\n\n for (const row of example.tableBody) {\n const values = row.cells.map((c) => c.value);\n const substitution: Record<string, string> = {};\n headers.forEach((h, i) => {\n substitution[h] = values[i];\n });\n\n const bgParsed = convertSteps(backgroundSteps);\n const scenarioSteps = convertSteps(scenario.steps).map((step) => ({\n ...step,\n text: substituteExampleValues(step.text, substitution),\n }));\n const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioSteps]);\n\n results.push({\n name: headers.map((h, i) => `${h}=${values[i]}`).join(\", \"),\n file: filePath,\n steps: allSteps,\n tags: exampleTags,\n outline: { name: scenario.name },\n });\n }\n }\n\n return results;\n}\n\nfunction convertSteps(\n steps: readonly messages.Step[]\n): Omit<ParsedStep, \"effectiveKeyword\">[] {\n return steps.map((step) => ({\n keyword: normalizeKeyword(step.keyword),\n text: step.text,\n line: step.location.line,\n // step.location.column points to the keyword start; shift past the\n // keyword (which includes a trailing space) so column points to the\n // start of the step text. This makes `column + text.length` produce\n // the correct end position for diagnostic ranges.\n column: (step.location.column ?? 1) + step.keyword.length,\n }));\n}\n\nfunction normalizeKeyword(keyword: string): GherkinKeyword {\n const trimmed = keyword.trim();\n // Gherkin keywords may include trailing space, e.g. \"Given \"\n if (trimmed === \"Given\") return \"Given\";\n if (trimmed === \"When\") return \"When\";\n if (trimmed === \"Then\") return \"Then\";\n if (trimmed === \"And\") return \"And\";\n if (trimmed === \"But\") return \"But\";\n // Fallback: treat as Given (shouldn't happen with valid Gherkin)\n return \"Given\";\n}\n\nfunction resolveEffectiveKeywords(\n steps: Omit<ParsedStep, \"effectiveKeyword\">[]\n): ParsedStep[] {\n let lastEffective: \"Given\" | \"When\" | \"Then\" = \"Given\";\n\n return steps.map((step) => {\n let effectiveKeyword: \"Given\" | \"When\" | \"Then\";\n if (step.keyword === \"And\" || step.keyword === \"But\") {\n effectiveKeyword = lastEffective;\n } else {\n effectiveKeyword = step.keyword as \"Given\" | \"When\" | \"Then\";\n }\n lastEffective = effectiveKeyword;\n\n return {\n ...step,\n effectiveKeyword,\n };\n });\n}\n\nfunction substituteExampleValues(\n text: string,\n substitution: Record<string, string>\n): string {\n let result = text;\n for (const [key, value] of Object.entries(substitution)) {\n result = result.replace(new RegExp(`<${key}>`, \"g\"), value);\n }\n return result;\n}\n","import { MatchedStep, ParsedScenario, StepDefinitionMeta } from \"./types.js\";\n\ninterface CompiledPattern {\n regex: RegExp;\n definition: StepDefinitionMeta;\n}\n\nfunction compileDefinitions(\n definitions: StepDefinitionMeta[]\n): CompiledPattern[] {\n const compiled: CompiledPattern[] = [];\n for (const def of definitions) {\n try {\n // Replace {paramType} placeholders with (.+) to match any value,\n // regardless of which parser placeholder ({string}, {int}, ...) the\n // step definition declared.\n const placeholder = \"###PLACEHOLDER###\";\n const regexStr = def.expression\n .replace(/\\{[^}]+\\}/g, placeholder)\n .replace(/[.*+?^$()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(new RegExp(placeholder, \"g\"), \"(.+)\");\n compiled.push({\n regex: new RegExp(`^${regexStr}$`, \"i\"),\n definition: def,\n });\n } catch {\n // Skip definitions with invalid expressions\n }\n }\n return compiled;\n}\n\nexport function matchScenarioSteps(\n scenario: ParsedScenario,\n definitions: StepDefinitionMeta[]\n): MatchedStep[] {\n const compiled = compileDefinitions(definitions);\n\n return scenario.steps.map(step => {\n const matches = findMatches(step.text, step.effectiveKeyword, compiled);\n return {\n ...step,\n definitions: matches,\n };\n });\n}\n\nexport function findMatchingDefinitions(\n text: string,\n effectiveKeyword: \"Given\" | \"When\" | \"Then\",\n definitions: StepDefinitionMeta[]\n): StepDefinitionMeta[] {\n const compiled = compileDefinitions(definitions);\n return findMatches(text, effectiveKeyword, compiled);\n}\n\nfunction findMatches(\n text: string,\n effectiveKeyword: \"Given\" | \"When\" | \"Then\",\n compiled: CompiledPattern[]\n): StepDefinitionMeta[] {\n const keywordToStepType: Record<string, string> = {\n Given: \"given\",\n When: \"when\",\n Then: \"then\",\n };\n const expectedStepType = keywordToStepType[effectiveKeyword];\n\n // First try to match with the correct step type\n const typedMatches: StepDefinitionMeta[] = [];\n for (const { regex, definition } of compiled) {\n if (definition.stepType !== expectedStepType) continue;\n if (regex.test(text)) typedMatches.push(definition);\n }\n\n if (typedMatches.length > 0) return typedMatches;\n\n // Fallback: match any step type\n const fallbackMatches: StepDefinitionMeta[] = [];\n for (const { regex, definition } of compiled) {\n if (regex.test(text)) fallbackMatches.push(definition);\n }\n\n return fallbackMatches;\n}\n","import {\n AnalysisRule,\n Diagnostic,\n MatchedStep,\n ParsedScenario,\n} from \"../types.js\";\n\nexport const ambiguousStepRule: AnalysisRule = {\n name: \"ambiguous-step\",\n\n check(\n scenario: ParsedScenario,\n matchedSteps: MatchedStep[]\n ): Diagnostic[] {\n return matchedSteps\n .filter((step) => step.definitions.length > 1)\n .map((step) => {\n const locations = step.definitions\n .map((d) => `${d.sourceFile}:${d.line}`)\n .join(\", \");\n return {\n file: scenario.file,\n range: {\n startLine: step.line,\n startColumn: step.column,\n endLine: step.line,\n endColumn: step.column + step.text.length,\n },\n severity: \"error\" as const,\n message: `Step \"${step.text}\" matches multiple step definitions: ${locations}`,\n rule: \"ambiguous-step\",\n source: \"step-forge\" as const,\n };\n });\n },\n};\n","import {\n AnalysisRule,\n Diagnostic,\n MatchedStep,\n ParsedScenario,\n} from \"../types.js\";\n\nexport const dependencyRule: AnalysisRule = {\n name: \"dependency-check\",\n\n check(\n scenario: ParsedScenario,\n matchedSteps: MatchedStep[]\n ): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const produced = {\n given: new Set<string>(),\n when: new Set<string>(),\n then: new Set<string>(),\n };\n\n for (const step of matchedSteps) {\n if (step.definitions.length !== 1) continue;\n\n const { dependencies, produces, stepType } = step.definitions[0];\n\n // Collect all missing required dependencies for this step\n const missing: Record<string, string[]> = {};\n for (const phase of [\"given\", \"when\", \"then\"] as const) {\n for (const [key, requirement] of Object.entries(dependencies[phase])) {\n if (requirement === \"required\" && !produced[phase].has(key)) {\n if (!missing[phase]) {\n missing[phase] = [];\n }\n missing[phase].push(key);\n }\n }\n }\n\n if (Object.keys(missing).length > 0) {\n const lines = Object.entries(missing).map(\n ([phase, keys]) =>\n `${phase.charAt(0).toUpperCase() + phase.slice(1)}: ${keys.map((k) => `${phase}.${k}`).join(\", \")}`\n );\n const message =\n \"Missing required dependencies:\\n\" + lines.join(\"\\n\");\n\n diagnostics.push({\n file: scenario.file,\n range: {\n startLine: step.line,\n startColumn: step.column,\n endLine: step.line,\n endColumn: step.column + step.text.length,\n },\n severity: \"error\",\n message,\n rule: \"dependency-check\",\n source: \"step-forge\",\n });\n }\n\n // Add produced keys for this step\n for (const key of produces) {\n produced[stepType].add(key);\n }\n }\n\n return diagnostics;\n },\n};\n","import {\n AnalysisRule,\n Diagnostic,\n MatchedStep,\n ParsedScenario,\n} from \"../types.js\";\n\nexport const undefinedStepRule: AnalysisRule = {\n name: \"undefined-step\",\n\n check(\n scenario: ParsedScenario,\n matchedSteps: MatchedStep[]\n ): Diagnostic[] {\n return matchedSteps\n .filter((step) => step.definitions.length === 0)\n .map((step) => ({\n file: scenario.file,\n range: {\n startLine: step.line,\n startColumn: step.column,\n endLine: step.line,\n endColumn: step.column + step.text.length,\n },\n severity: \"error\" as const,\n message: `Step \"${step.text}\" does not match any step definition`,\n rule: \"undefined-step\",\n source: \"step-forge\" as const,\n }));\n },\n};\n","import {\n AnalysisRule,\n Diagnostic,\n MatchedStep,\n ParsedScenario,\n} from \"../types.js\";\nimport { ambiguousStepRule } from \"./ambiguousStepRule.js\";\nimport { dependencyRule } from \"./dependencyRule.js\";\nimport { undefinedStepRule } from \"./undefinedStepRule.js\";\n\nexport const defaultRules: AnalysisRule[] = [\n undefinedStepRule,\n ambiguousStepRule,\n dependencyRule,\n];\n\nexport function runRules(\n rules: AnalysisRule[],\n scenario: ParsedScenario,\n matchedSteps: MatchedStep[]\n): Diagnostic[] {\n return rules.flatMap((rule) => rule.check(scenario, matchedSteps));\n}\n","import { globFiles } from \"../globFiles.js\";\nimport { extractStepDefinitions } from \"./stepExtractor.js\";\nimport { parseFeatureFiles, parseFeatureContent } from \"./gherkinParser.js\";\nimport { matchScenarioSteps, findMatchingDefinitions } from \"./stepMatcher.js\";\nimport { defaultRules, runRules } from \"./rules/index.js\";\nimport type {\n AnalyzerConfig,\n AnalysisRule,\n Diagnostic,\n StepDefinitionMeta,\n ParsedScenario,\n MatchedStep,\n ParsedStep,\n} from \"./types.js\";\n\nexport type {\n AnalyzerConfig,\n AnalysisRule,\n Diagnostic,\n StepDefinitionMeta,\n ParsedScenario,\n MatchedStep,\n ParsedStep,\n};\n\nexport {\n extractStepDefinitions,\n parseFeatureFiles,\n parseFeatureContent,\n matchScenarioSteps,\n findMatchingDefinitions,\n defaultRules,\n};\n\nexport interface AnalyzeOptions {\n rules?: AnalysisRule[];\n}\n\nexport async function analyze(\n config: AnalyzerConfig,\n options?: AnalyzeOptions\n): Promise<Diagnostic[]> {\n const rules = options?.rules ?? defaultRules;\n\n // 1. Resolve file globs to paths\n const stepFilePaths = await globFiles(config.stepFiles);\n const featureFilePaths = await globFiles(config.featureFiles);\n\n if (stepFilePaths.length === 0) {\n return [];\n }\n if (featureFilePaths.length === 0) {\n return [];\n }\n\n // 2. Extract step metadata from step files\n const stepDefinitions = extractStepDefinitions(\n stepFilePaths,\n config.tsConfigPath\n );\n\n // 3. Parse feature files\n const scenarios = parseFeatureFiles(featureFilePaths);\n\n // 4. For each scenario, match steps and run rules\n const diagnostics: Diagnostic[] = [];\n for (const scenario of scenarios) {\n const matchedSteps = matchScenarioSteps(scenario, stepDefinitions);\n const scenarioDiags = runRules(rules, scenario, matchedSteps);\n diagnostics.push(...scenarioDiags);\n }\n\n return diagnostics;\n}\n"],"mappings":"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"}
|
package/dist/analyzer-cli.js
CHANGED
package/dist/analyzer.js
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { n as parseFeatureFiles, t as parseFeatureContent } from "./gherkinParser-Dp2d7JNr.js";
|
|
1
|
+
import { a as parseFeatureContent, i as matchScenarioSteps, n as defaultRules, o as parseFeatureFiles, r as findMatchingDefinitions, s as extractStepDefinitions, t as analyze } from "./analyzer-DYfdoSIS.js";
|
|
3
2
|
export { analyze, defaultRules, extractStepDefinitions, findMatchingDefinitions, matchScenarioSteps, parseFeatureContent, parseFeatureFiles };
|