@step-forge/step-forge 0.0.25-alpha.4 → 0.0.25-alpha.5
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/RUNTIME.md +28 -28
- package/dist/{analyzer-5H699Eg4.js → analyzer-E3Kq7Eul.js} +20 -111
- package/dist/analyzer-E3Kq7Eul.js.map +1 -0
- package/dist/analyzer-cli.js +1 -1
- package/dist/analyzer-cli.js.map +1 -1
- package/dist/analyzer.js +1 -1
- package/dist/cli.cjs +1 -36
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +0 -35
- package/dist/cli.js.map +1 -1
- package/dist/{gherkinParser-_ZgvELdy.cjs → gherkinParser-CbBLr_uP.cjs} +1 -16
- package/dist/{gherkinParser-_ZgvELdy.cjs.map → gherkinParser-CbBLr_uP.cjs.map} +1 -1
- package/dist/step-forge.cjs +398 -7
- package/dist/step-forge.cjs.map +1 -1
- package/dist/step-forge.js +392 -2
- package/dist/step-forge.js.map +1 -1
- package/package.json +1 -1
- package/dist/analyzer-5H699Eg4.js.map +0 -1
- package/dist/analyzer-DRudicCk.cjs +0 -531
- package/dist/analyzer-DRudicCk.cjs.map +0 -1
- package/dist/analyzer-NsNpSFox.js +0 -507
- package/dist/analyzer-NsNpSFox.js.map +0 -1
package/RUNTIME.md
CHANGED
|
@@ -93,16 +93,16 @@ npm install --save-dev @step-forge/step-forge
|
|
|
93
93
|
Config is resolved from three sources, later ones overriding earlier ones:
|
|
94
94
|
**defaults → `step-forge.config.ts` → CLI flags**. Every field is optional.
|
|
95
95
|
|
|
96
|
-
| Field | Type
|
|
97
|
-
| ------------- |
|
|
98
|
-
| `features` | `string \| string[]`
|
|
99
|
-
| `steps` | `string \| string[]`
|
|
100
|
-
| `world` | `string`
|
|
101
|
-
| `concurrency` | `number`
|
|
102
|
-
| `reporter` | `"pretty" \| "progress"`
|
|
103
|
-
| `verbose` | `boolean`
|
|
104
|
-
| `name` | `string`
|
|
105
|
-
| `tags` | `string`
|
|
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 & output](#reporters--output). |
|
|
103
|
+
| `verbose` | `boolean` | `false` | Report every scenario, not just failures. |
|
|
104
|
+
| `name` | `string` | — | Only scenarios whose name matches (substring, or `/regex/flags`). |
|
|
105
|
+
| `tags` | `string` | — | Cucumber tag expression, e.g. `@smoke and not @wip`. |
|
|
106
106
|
|
|
107
107
|
The config file is loaded by Bun, so it may be TypeScript and import the
|
|
108
108
|
`RunnerOptions` type for editor help.
|
|
@@ -116,18 +116,18 @@ step-forge [options] [feature globs...]
|
|
|
116
116
|
Positional arguments are feature globs and **override** the configured
|
|
117
117
|
`features`, so you can run a subset ad hoc.
|
|
118
118
|
|
|
119
|
-
| Flag
|
|
120
|
-
|
|
|
121
|
-
| `--tags <expr>`
|
|
122
|
-
| `--name <pattern>`
|
|
123
|
-
| `--steps <glob>`
|
|
124
|
-
| `--world <module>`
|
|
125
|
-
| `--concurrency <n>`
|
|
126
|
-
| `--reporter <name>`
|
|
127
|
-
| `--verbose`
|
|
128
|
-
| `--interactive`
|
|
129
|
-
| `--config <path>`
|
|
130
|
-
| `--help`
|
|
119
|
+
| Flag | Short | Description |
|
|
120
|
+
| ------------------- | ----- | -------------------------------------------------------- |
|
|
121
|
+
| `--tags <expr>` | `-t` | Tag expression, e.g. `"@smoke and not @wip"`. |
|
|
122
|
+
| `--name <pattern>` | `-n` | Only scenarios whose name matches (substring/`/regex/`). |
|
|
123
|
+
| `--steps <glob>` | `-s` | Step-module glob (repeatable). |
|
|
124
|
+
| `--world <module>` | `-w` | World factory module. |
|
|
125
|
+
| `--concurrency <n>` | `-c` | Max scenarios in flight (default `1`). |
|
|
126
|
+
| `--reporter <name>` | `-r` | `pretty` (default) or `progress`. |
|
|
127
|
+
| `--verbose` | `-v` | Report every scenario, not just failures. |
|
|
128
|
+
| `--interactive` | `-i` | Interactive watch mode (see below). Requires a TTY. |
|
|
129
|
+
| `--config <path>` | | Directory to resolve the config file and globs from. |
|
|
130
|
+
| `--help` | `-h` | Show usage. |
|
|
131
131
|
|
|
132
132
|
Examples:
|
|
133
133
|
|
|
@@ -159,7 +159,7 @@ step-forge -i # browse and pick from everything
|
|
|
159
159
|
step-forge -i -t "@smoke" # open with the query pre-filled
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
The prompt is always live, with one committed selection (the
|
|
162
|
+
The prompt is always live, with one committed selection (the _armed_
|
|
163
163
|
population):
|
|
164
164
|
|
|
165
165
|
- **Enter** arms the highlighted choice, runs it immediately, and re-runs it on
|
|
@@ -206,11 +206,11 @@ beforeScenario(({ world, scenario }) => {
|
|
|
206
206
|
});
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
-
| Hook
|
|
210
|
-
|
|
|
211
|
-
| `beforeAll`/`afterAll`
|
|
212
|
-
| `beforeFeature`/`afterFeature`
|
|
213
|
-
| `beforeScenario`/`afterScenario` | Around each scenario (gets its world)
|
|
209
|
+
| Hook | Runs |
|
|
210
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
211
|
+
| `beforeAll`/`afterAll` | Once around the entire run — `beforeAll` before concurrency starts, `afterAll` after every scenario is done. |
|
|
212
|
+
| `beforeFeature`/`afterFeature` | Around each feature file. |
|
|
213
|
+
| `beforeScenario`/`afterScenario` | Around each scenario (gets its world). |
|
|
214
214
|
|
|
215
215
|
Multiple `beforeAll` (and multiple `afterAll`) hooks run **in parallel** with no
|
|
216
216
|
ordering between them — if one setup step must precede another, sequence both
|
|
@@ -4,7 +4,6 @@ import ts from "typescript";
|
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
|
|
6
6
|
import * as messages from "@cucumber/messages";
|
|
7
|
-
import { CucumberExpression, ParameterType, ParameterTypeRegistry } from "@cucumber/cucumber-expressions";
|
|
8
7
|
//#region src/globFiles.ts
|
|
9
8
|
/** Glob metacharacters. A pattern with none of these is a literal path. */
|
|
10
9
|
const MAGIC = /[*?[\]{}!()]/;
|
|
@@ -44,21 +43,6 @@ const BUILDER_NAMES = {
|
|
|
44
43
|
whenBuilder: "when",
|
|
45
44
|
thenBuilder: "then"
|
|
46
45
|
};
|
|
47
|
-
/**
|
|
48
|
-
* Built-in parsers exported by the library, mapped to the cucumber-expression
|
|
49
|
-
* placeholder they register. A step's `.parsers([...])` drives which `{name}`
|
|
50
|
-
* each variable becomes; without this the extractor defaulted every variable to
|
|
51
|
-
* `{string}`, so `the deposit amount is {int}` was recorded as `{string}` and a
|
|
52
|
-
* plain number like `100` looked unmatched. Custom parsers are resolved from
|
|
53
|
-
* their `name` property (see {@link resolveCustomParserName}); anything we can't
|
|
54
|
-
* resolve falls back to `string`.
|
|
55
|
-
*/
|
|
56
|
-
const BUILTIN_PARSER_PLACEHOLDERS = {
|
|
57
|
-
intParser: "int",
|
|
58
|
-
numberParser: "float",
|
|
59
|
-
stringParser: "string",
|
|
60
|
-
booleanParser: "boolean"
|
|
61
|
-
};
|
|
62
46
|
function extractStepDefinitions(filePaths, tsConfigPath) {
|
|
63
47
|
const fast = extractWithSources(filePaths.map(parseSourceFile).filter((s) => s !== null), null);
|
|
64
48
|
if (!fast.needsChecker) return fast.results;
|
|
@@ -117,7 +101,6 @@ function extractFromSourceFile(sourceFile, ctx) {
|
|
|
117
101
|
function extractFromRegisterCall(registerCall, sourceFile, ctx) {
|
|
118
102
|
const chain = collectCallChain(registerCall);
|
|
119
103
|
let stepType = null;
|
|
120
|
-
let statementCall = null;
|
|
121
104
|
let expression = null;
|
|
122
105
|
let dependencies = {
|
|
123
106
|
given: {},
|
|
@@ -125,7 +108,6 @@ function extractFromRegisterCall(registerCall, sourceFile, ctx) {
|
|
|
125
108
|
then: {}
|
|
126
109
|
};
|
|
127
110
|
let produces = [];
|
|
128
|
-
let placeholders = null;
|
|
129
111
|
for (const link of chain) {
|
|
130
112
|
const name = getCallName(link);
|
|
131
113
|
if (!name) continue;
|
|
@@ -138,12 +120,8 @@ function extractFromRegisterCall(registerCall, sourceFile, ctx) {
|
|
|
138
120
|
dependencies = extractDependencies(link);
|
|
139
121
|
continue;
|
|
140
122
|
}
|
|
141
|
-
if (name === "parsers") {
|
|
142
|
-
placeholders = resolveParserPlaceholders(link, sourceFile);
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
123
|
if (name === "statement") {
|
|
146
|
-
|
|
124
|
+
expression = extractExpression(link);
|
|
147
125
|
continue;
|
|
148
126
|
}
|
|
149
127
|
if (BUILDER_NAMES[name]) {
|
|
@@ -151,7 +129,6 @@ function extractFromRegisterCall(registerCall, sourceFile, ctx) {
|
|
|
151
129
|
continue;
|
|
152
130
|
}
|
|
153
131
|
}
|
|
154
|
-
if (statementCall) expression = extractExpression(statementCall, placeholders);
|
|
155
132
|
if (!stepType || !expression) {
|
|
156
133
|
const reExport = resolveReExportedCall(chain, ctx);
|
|
157
134
|
if (reExport) {
|
|
@@ -176,12 +153,11 @@ function extractFromRegisterCall(registerCall, sourceFile, ctx) {
|
|
|
176
153
|
function collectCallChain(call) {
|
|
177
154
|
const chain = [call];
|
|
178
155
|
let current = call.expression;
|
|
179
|
-
|
|
156
|
+
if (ts.isPropertyAccessExpression(current)) current = current.expression;
|
|
157
|
+
while (ts.isCallExpression(current)) {
|
|
158
|
+
chain.push(current);
|
|
159
|
+
current = current.expression;
|
|
180
160
|
if (ts.isPropertyAccessExpression(current)) current = current.expression;
|
|
181
|
-
if (ts.isCallExpression(current)) {
|
|
182
|
-
chain.push(current);
|
|
183
|
-
current = current.expression;
|
|
184
|
-
} else break;
|
|
185
161
|
}
|
|
186
162
|
return chain;
|
|
187
163
|
}
|
|
@@ -191,14 +167,14 @@ function getCallName(call) {
|
|
|
191
167
|
if (ts.isIdentifier(expr)) return expr.text;
|
|
192
168
|
return null;
|
|
193
169
|
}
|
|
194
|
-
function extractExpression(statementCall
|
|
170
|
+
function extractExpression(statementCall) {
|
|
195
171
|
const arg = statementCall.arguments[0];
|
|
196
172
|
if (!arg) return null;
|
|
197
173
|
if (ts.isStringLiteral(arg)) return arg.text;
|
|
198
|
-
if (ts.isArrowFunction(arg)) return extractExpressionFromArrowFunction(arg
|
|
174
|
+
if (ts.isArrowFunction(arg)) return extractExpressionFromArrowFunction(arg);
|
|
199
175
|
return null;
|
|
200
176
|
}
|
|
201
|
-
function extractExpressionFromArrowFunction(fn
|
|
177
|
+
function extractExpressionFromArrowFunction(fn) {
|
|
202
178
|
const params = fn.parameters.map((p) => p.name.getText());
|
|
203
179
|
let body = fn.body;
|
|
204
180
|
if (ts.isBlock(body)) {
|
|
@@ -206,63 +182,19 @@ function extractExpressionFromArrowFunction(fn, placeholders) {
|
|
|
206
182
|
if (returnStmt?.expression) body = returnStmt.expression;
|
|
207
183
|
else return null;
|
|
208
184
|
}
|
|
209
|
-
if (ts.isTemplateExpression(body)) return reconstructExpressionFromTemplate(body, params
|
|
185
|
+
if (ts.isTemplateExpression(body)) return reconstructExpressionFromTemplate(body, params);
|
|
210
186
|
if (ts.isNoSubstitutionTemplateLiteral(body)) return body.text;
|
|
211
187
|
return null;
|
|
212
188
|
}
|
|
213
|
-
function reconstructExpressionFromTemplate(template, paramNames
|
|
189
|
+
function reconstructExpressionFromTemplate(template, paramNames) {
|
|
214
190
|
let result = template.head.text;
|
|
215
191
|
for (const span of template.templateSpans) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const index = paramNames.indexOf(span.expression.text);
|
|
219
|
-
placeholder = placeholders?.[index] ?? "string";
|
|
220
|
-
}
|
|
221
|
-
result += `{${placeholder}}`;
|
|
192
|
+
if (ts.isIdentifier(span.expression) && paramNames.includes(span.expression.text)) result += "{string}";
|
|
193
|
+
else result += "{string}";
|
|
222
194
|
result += span.literal.text;
|
|
223
195
|
}
|
|
224
196
|
return result;
|
|
225
197
|
}
|
|
226
|
-
/**
|
|
227
|
-
* Resolve `.parsers([intParser, colorParser])` to the placeholder name for each
|
|
228
|
-
* variable, positionally. Built-ins map via {@link BUILTIN_PARSER_PLACEHOLDERS};
|
|
229
|
-
* a custom parser is resolved from its `name` property in the same source file;
|
|
230
|
-
* anything unresolved defaults to `string`, matching the runtime default parser.
|
|
231
|
-
*/
|
|
232
|
-
function resolveParserPlaceholders(parsersCall, sourceFile) {
|
|
233
|
-
const arg = parsersCall.arguments[0];
|
|
234
|
-
if (!arg || !ts.isArrayLiteralExpression(arg)) return null;
|
|
235
|
-
return arg.elements.map((element) => {
|
|
236
|
-
if (ts.isIdentifier(element)) {
|
|
237
|
-
const builtin = BUILTIN_PARSER_PLACEHOLDERS[element.text];
|
|
238
|
-
if (builtin) return builtin;
|
|
239
|
-
const custom = resolveCustomParserName(element.text, sourceFile);
|
|
240
|
-
if (custom) return custom;
|
|
241
|
-
}
|
|
242
|
-
return "string";
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Find `const <identifier> = { name: "...", ... }` in `sourceFile` and return
|
|
247
|
-
* its `name` — the cucumber placeholder a custom `Parser` registers. Scoped to
|
|
248
|
-
* the file, so a parser imported from elsewhere won't resolve (it falls back to
|
|
249
|
-
* `string`); the common in-file `const colorParser: Parser<Color> = {...}` does.
|
|
250
|
-
*/
|
|
251
|
-
function resolveCustomParserName(identifier, sourceFile) {
|
|
252
|
-
let found = null;
|
|
253
|
-
const visit = (node) => {
|
|
254
|
-
if (found) return;
|
|
255
|
-
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === identifier && node.initializer && ts.isObjectLiteralExpression(node.initializer)) {
|
|
256
|
-
for (const prop of node.initializer.properties) if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === "name" && ts.isStringLiteralLike(prop.initializer)) {
|
|
257
|
-
found = prop.initializer.text;
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
ts.forEachChild(node, visit);
|
|
262
|
-
};
|
|
263
|
-
visit(sourceFile);
|
|
264
|
-
return found;
|
|
265
|
-
}
|
|
266
198
|
function extractDependencies(depsCall) {
|
|
267
199
|
const deps = {
|
|
268
200
|
given: {},
|
|
@@ -336,7 +268,7 @@ function resolveReExportedCall(chain, ctx) {
|
|
|
336
268
|
if (ts.isCallExpression(callExpr)) {
|
|
337
269
|
const callee = callExpr.expression;
|
|
338
270
|
if (ts.isIdentifier(callee) && BUILDER_NAMES[callee.text]) {
|
|
339
|
-
const expression = extractExpression(lastCall
|
|
271
|
+
const expression = extractExpression(lastCall);
|
|
340
272
|
return {
|
|
341
273
|
stepType: BUILDER_NAMES[callee.text],
|
|
342
274
|
expression
|
|
@@ -468,36 +400,13 @@ function substituteExampleValues(text, substitution) {
|
|
|
468
400
|
}
|
|
469
401
|
//#endregion
|
|
470
402
|
//#region src/analyzer/stepMatcher.ts
|
|
471
|
-
/** `{name}` tokens in a cucumber expression (empty name is the anonymous `{}`). */
|
|
472
|
-
const PARAM_TOKEN = /\{([^}]*)\}/g;
|
|
473
|
-
/**
|
|
474
|
-
* Compile each definition's cucumber expression with the **same** engine the
|
|
475
|
-
* runtime uses (`@cucumber/cucumber-expressions`), so the analyzer and the
|
|
476
|
-
* runtime agree on what a step matches.
|
|
477
|
-
*
|
|
478
|
-
* A hand-rolled `{param}` → `(.+)` regex used to stand in here, but it treated
|
|
479
|
-
* cucumber-expression alternative (`a/b`) and optional (`text(s)`) syntax as
|
|
480
|
-
* literal characters — so any step definition using them (e.g.
|
|
481
|
-
* `there should be {int} error/errors`) never matched, and every such step was
|
|
482
|
-
* wrongly reported as an undefined step regardless of the real state.
|
|
483
|
-
*
|
|
484
|
-
* Built-in placeholders (`{int}`, `{float}`, `{string}`, `{word}`) keep their
|
|
485
|
-
* real, strict regexes so the analyzer disambiguates the way the runtime does
|
|
486
|
-
* (e.g. `no` isn't an `{int}`). The analyzer has only the expression string, not
|
|
487
|
-
* a custom parser's regexp, so each custom `{name}` is registered as a permissive
|
|
488
|
-
* parameter type (any value) — lenient about the value's exact shape, but still
|
|
489
|
-
* anchored by the surrounding literal text.
|
|
490
|
-
*/
|
|
491
403
|
function compileDefinitions(definitions) {
|
|
492
404
|
const compiled = [];
|
|
493
405
|
for (const def of definitions) try {
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
if (!name || registry.lookupByTypeName(name)) continue;
|
|
497
|
-
registry.defineParameterType(new ParameterType(name, /.+/, null, (value) => value));
|
|
498
|
-
}
|
|
406
|
+
const placeholder = "###PLACEHOLDER###";
|
|
407
|
+
const regexStr = def.expression.replace(/\{[^}]+\}/g, placeholder).replace(/[.*+?^$()|[\]\\]/g, "\\$&").replace(new RegExp(placeholder, "g"), "(.+)");
|
|
499
408
|
compiled.push({
|
|
500
|
-
|
|
409
|
+
regex: new RegExp(`^${regexStr}$`, "i"),
|
|
501
410
|
definition: def
|
|
502
411
|
});
|
|
503
412
|
} catch {}
|
|
@@ -523,13 +432,13 @@ function findMatches(text, effectiveKeyword, compiled) {
|
|
|
523
432
|
Then: "then"
|
|
524
433
|
}[effectiveKeyword];
|
|
525
434
|
const typedMatches = [];
|
|
526
|
-
for (const {
|
|
435
|
+
for (const { regex, definition } of compiled) {
|
|
527
436
|
if (definition.stepType !== expectedStepType) continue;
|
|
528
|
-
if (
|
|
437
|
+
if (regex.test(text)) typedMatches.push(definition);
|
|
529
438
|
}
|
|
530
439
|
if (typedMatches.length > 0) return typedMatches;
|
|
531
440
|
const fallbackMatches = [];
|
|
532
|
-
for (const {
|
|
441
|
+
for (const { regex, definition } of compiled) if (regex.test(text)) fallbackMatches.push(definition);
|
|
533
442
|
return fallbackMatches;
|
|
534
443
|
}
|
|
535
444
|
//#endregion
|
|
@@ -640,4 +549,4 @@ async function analyze(config, options) {
|
|
|
640
549
|
//#endregion
|
|
641
550
|
export { parseFeatureContent as a, matchScenarioSteps as i, defaultRules as n, parseFeatureFiles as o, findMatchingDefinitions as r, extractStepDefinitions as s, analyze as t };
|
|
642
551
|
|
|
643
|
-
//# sourceMappingURL=analyzer-
|
|
552
|
+
//# sourceMappingURL=analyzer-E3Kq7Eul.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer-E3Kq7Eul.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\n/**\n * Threaded through the AST walk. `checker` is `null` on the parse-only fast\n * path; a step that genuinely needs type information sets `needsChecker`, which\n * triggers a one-time retry with a real type-checked `Program`.\n */\ninterface ExtractCtx {\n checker: ts.TypeChecker | null;\n needsChecker: boolean;\n}\n\nexport function extractStepDefinitions(\n filePaths: string[],\n tsConfigPath?: string\n): StepDefinitionMeta[] {\n // Fast path: parse each file with `createSourceFile` (tokenize + parse only,\n // no type resolution — ~1ms/file) and walk the AST. Building a full\n // type-checked `Program` loads `lib.*.d.ts` and resolves every import (~150ms)\n // and is only needed for two uncommon shapes: a `.step()` whose return isn't a\n // plain object literal, or the re-exported-builder pattern. Those set\n // `needsChecker`, and we retry once with a real Program below.\n const sources = filePaths\n .map(parseSourceFile)\n .filter((s): s is ts.SourceFile => s !== null);\n const fast = extractWithSources(sources, null);\n if (!fast.needsChecker) return fast.results;\n\n // Slow path: some step needs type information. Build the program once and\n // re-extract from *its* source files — the checker only understands nodes it\n // bound itself, so the parse-only ASTs above can't be reused here.\n const program = ts.createProgram(\n filePaths,\n resolveCompilerOptions(tsConfigPath)\n );\n const checker = program.getTypeChecker();\n const checkedSources = filePaths\n .map(fp => program.getSourceFile(fp))\n .filter((s): s is ts.SourceFile => s !== undefined);\n return extractWithSources(checkedSources, checker).results;\n}\n\n/** Parse a single file into an AST with no type resolution. */\nfunction parseSourceFile(filePath: string): ts.SourceFile | null {\n const text = ts.sys.readFile(filePath);\n if (text === undefined) return null;\n const scriptKind = filePath.endsWith(\".tsx\")\n ? ts.ScriptKind.TSX\n : ts.ScriptKind.TS;\n // setParentNodes: true — the extractor relies on `.getText()`/`.getStart()`,\n // which walk parent pointers up to the SourceFile.\n return ts.createSourceFile(\n filePath,\n text,\n ts.ScriptTarget.ESNext,\n true,\n scriptKind\n );\n}\n\n/** Resolve compiler options from tsconfig (fallback to sane defaults). */\nfunction resolveCompilerOptions(tsConfigPath?: string): ts.CompilerOptions {\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 return compilerOptions;\n}\n\nfunction extractWithSources(\n sources: ts.SourceFile[],\n checker: ts.TypeChecker | null\n): { results: StepDefinitionMeta[]; needsChecker: boolean } {\n const ctx: ExtractCtx = { checker, needsChecker: false };\n const results: StepDefinitionMeta[] = [];\n for (const sourceFile of sources) {\n results.push(...extractFromSourceFile(sourceFile, ctx));\n }\n return { results, needsChecker: ctx.needsChecker };\n}\n\nfunction extractFromSourceFile(\n sourceFile: ts.SourceFile,\n ctx: ExtractCtx\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, ctx);\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 ctx: ExtractCtx\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, ctx);\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, ctx);\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 // Walk through PropertyAccessExpression to find the next call\n if (ts.isPropertyAccessExpression(current)) {\n current = current.expression;\n }\n\n while (ts.isCallExpression(current)) {\n chain.push(current);\n current = current.expression;\n if (ts.isPropertyAccessExpression(current)) {\n current = current.expression;\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 (\n ts.isIdentifier(span.expression) &&\n paramNames.includes(span.expression.text)\n ) {\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)) 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 ctx: ExtractCtx\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, ctx);\n }\n\n return [];\n}\n\nfunction extractProducedKeysFromCallback(\n callback: ts.ArrowFunction | ts.FunctionExpression,\n ctx: ExtractCtx\n): string[] {\n const body = callback.body;\n\n // Concise arrow: () => ({ key: value })\n if (!ts.isBlock(body)) {\n return extractKeysFromExpression(body, ctx);\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, ctx)) {\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 ctx: ExtractCtx\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 // Non-literal return (a variable, a spread-only object, a function call): the\n // keys can only come from the type checker. On the parse-only pass we have\n // none — flag it so the caller retries with a real Program.\n if (!ctx.checker) {\n ctx.needsChecker = true;\n return [];\n }\n try {\n const type = ctx.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 ctx: ExtractCtx\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 // Tracing the re-exported builder's declaration needs symbol resolution,\n // which only a real Program provides. Flag for the checked retry.\n if (!ctx.checker) {\n ctx.needsChecker = true;\n return null;\n }\n\n const symbol = ctx.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 {\n GherkinClassicTokenMatcher,\n Parser,\n AstBuilder,\n} 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\n/**\n * One parsed feature file: its `Feature:` title (empty if unnamed) plus every\n * scenario (outline rows expanded). {@link parseFeatureContent} exposes just the\n * scenarios; {@link parseFeatureCatalog} keeps the title too, for UIs that label\n * scenarios by their feature.\n */\nexport interface ParsedFeature {\n file: string;\n name: string;\n scenarios: ParsedScenario[];\n}\n\n/** Build a fresh gherkin parser and parse `content` into a document. */\nfunction parseDocument(content: string): messages.GherkinDocument {\n const builder = new AstBuilder(messages.IdGenerator.uuid());\n const matcher = new GherkinClassicTokenMatcher();\n return new Parser(builder, matcher).parse(content);\n}\n\n/**\n * Parse feature files into a catalog of `{ file, name, scenarios }`, one entry\n * per file (one gherkin parse each). Used by interactive mode to offer\n * feature/scenario names as typeahead choices labelled by their `Feature:` title.\n */\nexport function parseFeatureCatalog(filePaths: string[]): ParsedFeature[] {\n return filePaths.map(file => {\n const document = parseDocument(fs.readFileSync(file, \"utf-8\"));\n return {\n file,\n name: document.feature?.name ?? \"\",\n scenarios: documentToScenarios(document, file),\n };\n });\n}\n\nexport function parseFeatureContent(\n content: string,\n filePath: string\n): ParsedScenario[] {\n return documentToScenarios(parseDocument(content), filePath);\n}\n\n/** Expand a parsed gherkin document into scenarios (outline rows included). */\nfunction documentToScenarios(\n gherkinDocument: messages.GherkinDocument,\n filePath: string\n): ParsedScenario[] {\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 line: scenario.location.line,\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([\n ...bgParsed,\n ...scenarioSteps,\n ]);\n\n results.push({\n name: headers.map((h, i) => `${h}=${values[i]}`).join(\", \"),\n file: filePath,\n line: row.location.line,\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(scenario: ParsedScenario, matchedSteps: MatchedStep[]): 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(scenario: ParsedScenario, matchedSteps: MatchedStep[]): 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 = \"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(scenario: ParsedScenario, matchedSteps: MatchedStep[]): 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-cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer-cli.js","names":[],"sources":["../../src/analyzer/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { analyze } from \"./index.js\";\nimport type { AnalyzerConfig, Diagnostic } from \"./types.js\";\n\nfunction parseArgs(args: string[]): AnalyzerConfig {\n const config: AnalyzerConfig = {\n stepFiles: [],\n featureFiles: [],\n };\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n const next = args[i + 1];\n\n if ((arg === \"--steps\" || arg === \"-s\") && next) {\n config.stepFiles.push(next);\n i++;\n } else if ((arg === \"--features\" || arg === \"-f\") && next) {\n config.featureFiles.push(next);\n i++;\n } else if (arg === \"--tsconfig\" && next) {\n config.tsConfigPath = next;\n i++;\n } else if (arg === \"--help\" || arg === \"-h\") {\n printUsage();\n process.exit(0);\n }\n }\n\n return config;\n}\n\nfunction printUsage() {\n console.log(`Usage: step-forge-analyze [options]\n\nOptions:\n -s, --steps <glob> Glob pattern for step definition files (repeatable)\n -f, --features <glob> Glob pattern for feature files (repeatable)\n --tsconfig <path> Path to tsconfig.json (default: auto-detect)\n -h, --help Show this help message\n\nExample:\n step-forge-analyze --steps \"features/steps/**/*.ts\" --features \"features/**/*.feature\"\n`);\n}\n\nfunction formatDiagnostic(diag: Diagnostic): string {\n const location = `${diag.file}:${diag.range.startLine}:${diag.range.startColumn}`;\n return `${location} - ${diag.severity}: ${diag.message}`;\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n const config = parseArgs(args);\n\n if (config.stepFiles.length === 0 || config.featureFiles.length === 0) {\n console.error(\
|
|
1
|
+
{"version":3,"file":"analyzer-cli.js","names":[],"sources":["../../src/analyzer/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { analyze } from \"./index.js\";\nimport type { AnalyzerConfig, Diagnostic } from \"./types.js\";\n\nfunction parseArgs(args: string[]): AnalyzerConfig {\n const config: AnalyzerConfig = {\n stepFiles: [],\n featureFiles: [],\n };\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n const next = args[i + 1];\n\n if ((arg === \"--steps\" || arg === \"-s\") && next) {\n config.stepFiles.push(next);\n i++;\n } else if ((arg === \"--features\" || arg === \"-f\") && next) {\n config.featureFiles.push(next);\n i++;\n } else if (arg === \"--tsconfig\" && next) {\n config.tsConfigPath = next;\n i++;\n } else if (arg === \"--help\" || arg === \"-h\") {\n printUsage();\n process.exit(0);\n }\n }\n\n return config;\n}\n\nfunction printUsage() {\n console.log(`Usage: step-forge-analyze [options]\n\nOptions:\n -s, --steps <glob> Glob pattern for step definition files (repeatable)\n -f, --features <glob> Glob pattern for feature files (repeatable)\n --tsconfig <path> Path to tsconfig.json (default: auto-detect)\n -h, --help Show this help message\n\nExample:\n step-forge-analyze --steps \"features/steps/**/*.ts\" --features \"features/**/*.feature\"\n`);\n}\n\nfunction formatDiagnostic(diag: Diagnostic): string {\n const location = `${diag.file}:${diag.range.startLine}:${diag.range.startColumn}`;\n return `${location} - ${diag.severity}: ${diag.message}`;\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n const config = parseArgs(args);\n\n if (config.stepFiles.length === 0 || config.featureFiles.length === 0) {\n console.error(\"Error: Both --steps and --features are required.\\n\");\n printUsage();\n process.exit(1);\n }\n\n const diagnostics = await analyze(config);\n\n if (diagnostics.length === 0) {\n console.log(\"No issues found.\");\n process.exit(0);\n }\n\n const errors = diagnostics.filter(d => d.severity === \"error\");\n const warnings = diagnostics.filter(d => d.severity === \"warning\");\n\n for (const diag of diagnostics) {\n console.log(formatDiagnostic(diag));\n }\n\n console.log(\n `\\nFound ${errors.length} error(s) and ${warnings.length} warning(s).`\n );\n process.exit(errors.length > 0 ? 1 : 0);\n}\n\n// Only run when invoked directly (not when imported by Cucumber or other tools)\nconst isDirectRun =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith(\"analyzer-cli.js\") ||\n process.argv[1]?.endsWith(\"analyzer-cli.ts\");\n\nif (isDirectRun) {\n main().catch(err => {\n console.error(\"Analyzer failed:\", err);\n process.exit(1);\n });\n}\n"],"mappings":";;;AAKA,SAAS,UAAU,MAAgC;CACjD,MAAM,SAAyB;EAC7B,WAAW,CAAC;EACZ,cAAc,CAAC;CACjB;CAEA,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EACjB,MAAM,OAAO,KAAK,IAAI;EAEtB,KAAK,QAAQ,aAAa,QAAQ,SAAS,MAAM;GAC/C,OAAO,UAAU,KAAK,IAAI;GAC1B;EACF,OAAO,KAAK,QAAQ,gBAAgB,QAAQ,SAAS,MAAM;GACzD,OAAO,aAAa,KAAK,IAAI;GAC7B;EACF,OAAO,IAAI,QAAQ,gBAAgB,MAAM;GACvC,OAAO,eAAe;GACtB;EACF,OAAO,IAAI,QAAQ,YAAY,QAAQ,MAAM;GAC3C,WAAW;GACX,QAAQ,KAAK,CAAC;EAChB;CACF;CAEA,OAAO;AACT;AAEA,SAAS,aAAa;CACpB,QAAQ,IAAI;;;;;;;;;;CAUb;AACD;AAEA,SAAS,iBAAiB,MAA0B;CAElD,OAAO,GAAG,GADU,KAAK,KAAK,GAAG,KAAK,MAAM,UAAU,GAAG,KAAK,MAAM,cACjD,KAAK,KAAK,SAAS,IAAI,KAAK;AACjD;AAEA,eAAe,OAAO;CAEpB,MAAM,SAAS,UADF,QAAQ,KAAK,MAAM,CACJ,CAAC;CAE7B,IAAI,OAAO,UAAU,WAAW,KAAK,OAAO,aAAa,WAAW,GAAG;EACrE,QAAQ,MAAM,oDAAoD;EAClE,WAAW;EACX,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,cAAc,MAAM,QAAQ,MAAM;CAExC,IAAI,YAAY,WAAW,GAAG;EAC5B,QAAQ,IAAI,kBAAkB;EAC9B,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,SAAS,YAAY,QAAO,MAAK,EAAE,aAAa,OAAO;CAC7D,MAAM,WAAW,YAAY,QAAO,MAAK,EAAE,aAAa,SAAS;CAEjE,KAAK,MAAM,QAAQ,aACjB,QAAQ,IAAI,iBAAiB,IAAI,CAAC;CAGpC,QAAQ,IACN,WAAW,OAAO,OAAO,gBAAgB,SAAS,OAAO,aAC3D;CACA,QAAQ,KAAK,OAAO,SAAS,IAAI,IAAI,CAAC;AACxC;AAQA,IAJE,OAAO,KAAK,QAAQ,UAAU,QAAQ,KAAK,QAC3C,QAAQ,KAAK,EAAE,EAAE,SAAS,iBAAiB,KAC3C,QAAQ,KAAK,EAAE,EAAE,SAAS,iBAAiB,GAG3C,KAAK,CAAC,CAAC,OAAM,QAAO;CAClB,QAAQ,MAAM,oBAAoB,GAAG;CACrC,QAAQ,KAAK,CAAC;AAChB,CAAC"}
|
package/dist/analyzer.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
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-
|
|
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-E3Kq7Eul.js";
|
|
2
2
|
export { analyze, defaultRules, extractStepDefinitions, findMatchingDefinitions, matchScenarioSteps, parseFeatureContent, parseFeatureFiles };
|
package/dist/cli.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
const require_gherkinParser = require("./gherkinParser-
|
|
2
|
+
const require_gherkinParser = require("./gherkinParser-CbBLr_uP.cjs");
|
|
3
3
|
const require_hooks = require("./hooks-Be0cjULN.cjs");
|
|
4
4
|
const require_engine = require("./engine-vqA-eL_T.cjs");
|
|
5
5
|
let node_path = require("node:path");
|
|
@@ -1022,8 +1022,6 @@ function isInside(child, parent) {
|
|
|
1022
1022
|
//#endregion
|
|
1023
1023
|
//#region src/runtime/interactive.ts
|
|
1024
1024
|
const useColor = !process.env.NO_COLOR && (process.stdout.isTTY ?? false) === true;
|
|
1025
|
-
const bold = (s) => useColor ? `\x1b[1m${s}\x1b[22m` : s;
|
|
1026
|
-
const dim = (s) => useColor ? `\x1b[2m${s}\x1b[22m` : s;
|
|
1027
1025
|
const red = (s) => useColor ? `\x1b[31m${s}\x1b[39m` : s;
|
|
1028
1026
|
const yellow = (s) => useColor ? `\x1b[33m${s}\x1b[39m` : s;
|
|
1029
1027
|
/**
|
|
@@ -1154,7 +1152,6 @@ var InteractiveSession = class {
|
|
|
1154
1152
|
this.prompt.complete(0);
|
|
1155
1153
|
return;
|
|
1156
1154
|
}
|
|
1157
|
-
if (selected.length === 1) for (const line of await this.analyzeScenario(selected[0])) this.prompt.note(line);
|
|
1158
1155
|
await this.spawnRun(runArgs(choice, this.config));
|
|
1159
1156
|
}
|
|
1160
1157
|
/** Every scenario across the current catalog, flattened. */
|
|
@@ -1229,28 +1226,6 @@ var InteractiveSession = class {
|
|
|
1229
1226
|
});
|
|
1230
1227
|
});
|
|
1231
1228
|
}
|
|
1232
|
-
/**
|
|
1233
|
-
* Run the static analyzer over a single scenario and return any dependency /
|
|
1234
|
-
* undefined / ambiguous diagnostics as note lines for the results region. The
|
|
1235
|
-
* analyzer needs the optional `typescript` peer for AST extraction; if it's
|
|
1236
|
-
* absent we note that and skip, never failing the run.
|
|
1237
|
-
*/
|
|
1238
|
-
async analyzeScenario(scenario) {
|
|
1239
|
-
let analyzer;
|
|
1240
|
-
try {
|
|
1241
|
-
analyzer = await Promise.resolve().then(() => require("./analyzer-DRudicCk.cjs")).then((n) => n.analyzer_exports);
|
|
1242
|
-
} catch {
|
|
1243
|
-
return [dim(" analysis skipped: install `typescript` to enable it")];
|
|
1244
|
-
}
|
|
1245
|
-
try {
|
|
1246
|
-
const stepFiles = await require_gherkinParser.globFiles(this.config.steps, this.config.cwd);
|
|
1247
|
-
const defs = analyzer.extractStepDefinitions(stepFiles);
|
|
1248
|
-
const matched = analyzer.matchScenarioSteps(scenario, defs);
|
|
1249
|
-
return formatDiagnostics(analyzer.defaultRules.flatMap((rule) => rule.check(scenario, matched)), this.config.cwd);
|
|
1250
|
-
} catch (err) {
|
|
1251
|
-
return [dim(` analysis unavailable: ${err instanceof Error ? err.message : err}`)];
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
1229
|
status() {
|
|
1255
1230
|
if (!this.armed) return " type to filter · ↑↓ move · enter run · ctrl-c quit";
|
|
1256
1231
|
if (this.dirty) return " ⏸ suspended (query edited) · enter runs the selection · esc cancel";
|
|
@@ -1361,16 +1336,6 @@ function clock() {
|
|
|
1361
1336
|
d.getSeconds()
|
|
1362
1337
|
].map((n) => String(n).padStart(2, "0")).join(":");
|
|
1363
1338
|
}
|
|
1364
|
-
function formatDiagnostics(diagnostics, cwd) {
|
|
1365
|
-
if (diagnostics.length === 0) return [];
|
|
1366
|
-
const lines = [bold(" analyzer:")];
|
|
1367
|
-
for (const d of diagnostics) {
|
|
1368
|
-
const mark = d.severity === "error" ? red("✗") : d.severity === "warning" ? yellow("!") : dim("i");
|
|
1369
|
-
const loc = `${node_path.relative(cwd, d.file)}:${d.range.startLine}`;
|
|
1370
|
-
lines.push(` ${mark} ${d.message} ${dim(`(${loc})`)}`);
|
|
1371
|
-
}
|
|
1372
|
-
return lines;
|
|
1373
|
-
}
|
|
1374
1339
|
//#endregion
|
|
1375
1340
|
//#region src/runtime/cli.ts
|
|
1376
1341
|
const HELP = `step-forge — native TypeScript runner for Gherkin step definitions
|