@step-forge/step-forge 0.0.21 → 0.0.22
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 +42 -12
- package/dist/{analyzer-DYfdoSIS.js → analyzer-byS8yRrY.js} +52 -24
- package/dist/analyzer-byS8yRrY.js.map +1 -0
- package/dist/analyzer-cli.js +1 -1
- package/dist/analyzer.d.ts +2 -0
- package/dist/analyzer.js +1 -1
- package/dist/cli.cjs +129 -74
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +129 -74
- package/dist/cli.js.map +1 -1
- package/dist/{hooks-Dar49TtT.d.ts → config-C7PCYgYy.d.cts} +65 -16
- package/dist/{hooks-Dar49TtT.d.cts → config-C7PCYgYy.d.ts} +65 -16
- package/dist/{engine-DPRxs6eC.js → engine-DPVLEHBi.js} +8 -26
- package/dist/engine-DPVLEHBi.js.map +1 -0
- package/dist/{engine-DWAIlwWp.cjs → engine-vqA-eL_T.cjs} +8 -26
- package/dist/engine-vqA-eL_T.cjs.map +1 -0
- package/dist/{gherkinParser-CHpkEwii.cjs → gherkinParser-BT40q_i3.cjs} +97 -2
- package/dist/gherkinParser-BT40q_i3.cjs.map +1 -0
- package/dist/{gherkinParser-CKARHgt4.js → gherkinParser-NcttZgN4.js} +74 -3
- package/dist/gherkinParser-NcttZgN4.js.map +1 -0
- package/dist/hooks-BDCMKeNq.js +71 -0
- package/dist/{hooks-CywugMQQ.js.map → hooks-BDCMKeNq.js.map} +1 -1
- package/dist/{hooks-CGYzwDOv.cjs → hooks-Be0cjULN.cjs} +20 -31
- package/dist/{hooks-CGYzwDOv.cjs.map → hooks-Be0cjULN.cjs.map} +1 -1
- package/dist/runtime.cjs +3 -3
- package/dist/runtime.d.cts +11 -48
- package/dist/runtime.d.ts +11 -48
- package/dist/runtime.js +3 -3
- package/dist/step-forge.cjs +65 -28
- package/dist/step-forge.cjs.map +1 -1
- package/dist/step-forge.d.cts +13 -4
- package/dist/step-forge.d.ts +13 -4
- package/dist/step-forge.js +65 -28
- package/dist/step-forge.js.map +1 -1
- package/package.json +2 -2
- package/dist/analyzer-DYfdoSIS.js.map +0 -1
- package/dist/engine-DPRxs6eC.js.map +0 -1
- package/dist/engine-DWAIlwWp.cjs.map +0 -1
- package/dist/gherkinParser-CHpkEwii.cjs.map +0 -1
- package/dist/gherkinParser-CKARHgt4.js.map +0 -1
- package/dist/hooks-CywugMQQ.js +0 -82
package/dist/step-forge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step-forge.js","names":[],"sources":["../../src/builderTypeUtils.ts","../../src/parsers.ts","../../src/utils.ts","../../src/common.ts","../../src/given.ts","../../src/when.ts","../../src/then.ts","../../src/analyzer/stepExtractor.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","../../src/init.ts","../../src/hooks.ts","../../src/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nexport type StepType = \"given\" | \"when\" | \"then\";\n\n/** A dependency declaration: each key of a phase's state marked required/optional. */\nexport type RequiredOrOptional<T> = {\n [K in keyof T]?: \"required\" | \"optional\";\n};\n\n/** The dependency map for a single phase, keyed by state property name. */\nexport type DepMap = Record<string, \"required\" | \"optional\">;\n\n/** The fully-resolved dependency declaration passed to `addStep` at runtime. */\nexport type FullDependencies = {\n given: DepMap;\n when: DepMap;\n then: DepMap;\n};\n\n/**\n * Narrows a phase's full state to only the keys named in a dependency map.\n * Optional dependencies widen to `| undefined`; keys not present in the state\n * are dropped. This is the shape a step function sees for `given`/`when`/`then`.\n */\nexport type Restrict<State, Deps extends RequiredOrOptional<State>> = {\n [K in keyof State as K extends keyof Deps\n ? K\n : never]: Deps[K] extends \"optional\" ? State[K] | undefined : State[K];\n};\n\nexport type GetFunctionArgs<T> = T extends (...args: infer A) => any\n ? A\n : never;\n\nexport const isString = (\n statement: string | ((...args: [...any]) => string)\n): statement is string => typeof statement === \"string\";\n","/**\n * A Parser is a typed cucumber-expression *parameter type*: it declares how a\n * value is recognised in a Gherkin step (`regexp`) and how the matched text is\n * turned into a TypeScript value (`parse`). The step expression uses `{name}`\n * as the placeholder.\n *\n * The engine folds each parser straight into the cucumber-expression matcher —\n * `parse` is the parameter type's transform, so coercion happens exactly once,\n * during matching (no second pass). This is also what lets a parser introduce a\n * brand-new placeholder like `{color}`: it is registered as a real parameter\n * type, so text that doesn't match `regexp` simply doesn't match the step.\n *\n * The four built-ins below reuse cucumber-expressions' own built-in parameter\n * types (`{int}`, `{float}`, `{string}`) — those names are already registered,\n * so the library performs the match + coercion and each parser's `regexp`/\n * `parse` serve to document intent and to drive TypeScript inference of the\n * variable type. `booleanParser` has no built-in equivalent, so it is a genuine\n * custom parameter type whose `parse` runs at match time.\n */\nexport type Parser<T> = {\n /** Parameter-type name; the step expression uses `{name}` as the placeholder. */\n name: string;\n /** How cucumber-expressions recognises the value in the step text. */\n regexp: RegExp | RegExp[];\n /** Transform the matched text into the typed value. */\n parse: (value: string) => T;\n};\n\n/** Matches a quoted string (`{string}`) and strips the surrounding quotes. */\nexport const stringParser: Parser<string> = {\n name: \"string\",\n regexp: [/\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\"/, /'([^'\\\\]*(\\\\.[^'\\\\]*)*)'/],\n parse: value => {\n const match = /^\"([\\s\\S]*)\"$/.exec(value) ?? /^'([\\s\\S]*)'$/.exec(value);\n return match ? match[1].replace(/\\\\([\"'])/g, \"$1\") : value;\n },\n};\n\n/** Matches an unquoted integer (`{int}`). */\nexport const intParser: Parser<number> = {\n name: \"int\",\n regexp: /-?\\d+/,\n parse: value => parseInt(value, 10),\n};\n\n/** Matches an unquoted floating point number (`{float}`). */\nexport const numberParser: Parser<number> = {\n name: \"float\",\n regexp: /-?\\d*\\.?\\d+/,\n parse: value => parseFloat(value),\n};\n\n/**\n * Matches an unquoted `true`/`false` word (`{boolean}`) and parses it to a\n * boolean. Unlike the others this is a genuine custom parameter type — cucumber\n * has no built-in `boolean` — so its `regexp`/`parse` are what the matcher uses.\n */\nexport const booleanParser: Parser<boolean> = {\n name: \"boolean\",\n regexp: /true|false/,\n parse: value => value === \"true\",\n};\n","import { MergeableWorld } from \"./world\";\n\nexport const requireFromGiven = <G>(\n keys: (keyof G)[],\n world: MergeableWorld<G, unknown, unknown>\n) => {\n keys.forEach(key => {\n if (!world.given[key]) {\n throw new Error(`Key ${String(key)} is required in given state`);\n }\n });\n return keys.reduce(\n (acc, key) => {\n return {\n ...acc,\n [key]: world.given[key],\n };\n },\n {} as { [key in keyof G]: G[key] }\n );\n};\nexport const requireFromWhen = <W>(\n keys: (keyof W)[],\n world: MergeableWorld<unknown, W, unknown>\n) => {\n keys.forEach(key => {\n if (!world.when[key]) {\n throw new Error(`Key ${String(key)} is required in when state`);\n }\n });\n return keys.reduce(\n (acc, key) => {\n return {\n ...acc,\n [key]: world.when[key],\n };\n },\n {} as { [key in keyof W]: W[key] }\n );\n};\nexport const requireFromThen = <T>(\n keys: (keyof T)[],\n world: MergeableWorld<unknown, unknown, T>\n) => {\n keys.forEach(key => {\n if (!world.then[key]) {\n throw new Error(`Key ${String(key)} is required in then state`);\n }\n });\n return keys.reduce(\n (acc, key) => {\n return {\n ...acc,\n [key]: world.then[key],\n };\n },\n {} as { [key in keyof T]: T[key] }\n );\n};\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport _ from \"lodash\";\n\nimport { DepMap, FullDependencies, StepType } from \"./builderTypeUtils\";\nimport { Parser, stringParser } from \"./parsers\";\nimport { globalRegistry } from \"./runtime/registry\";\nimport { requireFromGiven, requireFromThen, requireFromWhen } from \"./utils\";\nimport { MergeableWorld } from \"./world\";\n\n/**\n * The runtime core of the builder chain. `addStep` is deliberately phase-agnostic:\n * the calling builder (given/when/then) has already computed the exact type of the\n * step function via its two type parameters:\n *\n * - `StepFnInput` — the `{ variables, given, when, then }` object the step receives,\n * with each phase already narrowed to its declared dependencies.\n * - `StepFnOutput` — the phase-appropriate return type (`Partial<State>`, or `void`).\n *\n * Everything else is plain runtime data (a statement function, the step type, the\n * dependency map, the parsers), so `addStep` carries no generics for them.\n */\nexport const addStep =\n <StepFnInput, StepFnOutput>(\n statement: (...args: any[]) => string,\n stepType: StepType,\n dependencies: FullDependencies = {\n given: {},\n when: {},\n then: {},\n },\n declaredParsers?: Parser<any>[]\n ) =>\n (stepFunction: (input: StepFnInput) => StepFnOutput) => {\n const {\n given: givenDependencies,\n when: whenDependencies,\n then: thenDependencies,\n } = dependencies;\n // Resolve the parsers up front, defaulting every variable to `stringParser`\n // (the `{string}` placeholder, value passed through unchanged) when none are\n // provided. Numeric/boolean values are opt-in via explicit parsers.\n const argCount = statement.length;\n const parsers =\n declaredParsers ?? Array.from({ length: argCount }, () => stringParser);\n const expression = statement(...parsers.map(parser => `{${parser.name}}`));\n // The fully-wired step body, decoupled from any test runner: takes an\n // explicit world plus the values captured from a Gherkin step, validates +\n // narrows dependencies, runs the user's step, and merges the result. The\n // captured values arrive already coerced — each parser is registered as the\n // cucumber-expression parameter type, so `parse` runs during matching, not\n // here.\n const execute = async (\n world: MergeableWorld<any, any, any>,\n capturedArgs: unknown[]\n ) => {\n const requiredKeys = (deps: DepMap) =>\n Object.entries(deps)\n .filter(([, value]) => value === \"required\")\n .map(([key]) => key);\n const narrowedGiven = {\n ..._.pick(world.given, Object.keys(givenDependencies)),\n ...requireFromGiven(requiredKeys(givenDependencies), world),\n };\n const narrowedWhen = {\n ..._.pick(world.when, Object.keys(whenDependencies)),\n ...requireFromWhen(requiredKeys(whenDependencies), world),\n };\n const narrowedThen = {\n ..._.pick(world.then, Object.keys(thenDependencies)),\n ...requireFromThen(requiredKeys(thenDependencies), world),\n };\n const result = await stepFunction({\n variables: capturedArgs,\n given: narrowedGiven,\n when: narrowedWhen,\n then: narrowedThen,\n } as StepFnInput);\n world[stepType].merge({\n ...(result as any),\n });\n };\n\n // Registration is the terminal action of the builder chain: calling\n // `.step(fn)` makes the step matchable and executable by the runtime.\n globalRegistry.add({ stepType, expression, parsers, execute });\n\n return {\n statement,\n expression,\n dependencies,\n stepType,\n stepFunction,\n };\n };\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport {\n FullDependencies,\n GetFunctionArgs,\n isString,\n RequiredOrOptional,\n Restrict,\n StepType,\n} from \"./builderTypeUtils\";\nimport { addStep } from \"./common\";\nimport { Parser } from \"./parsers\";\n\nconst GIVEN: StepType = \"given\";\n\n// The `{ variables, given, when, then }` object a given step receives. Only\n// `given` is reachable — when/then are `never`, enforcing phase restriction.\n// `Given` is the state already narrowed to declared dependencies (`never` when\n// no dependencies were declared).\ntype GivenInput<Variables, Given> = {\n variables: Variables;\n given: Given;\n when: never;\n then: never;\n};\ntype GivenOutput<GivenState> =\n | Partial<GivenState>\n | Promise<Partial<GivenState>>;\n\nconst givenDependencies =\n <Variables, GivenState>(\n statement: (...args: any[]) => string,\n parsers?: Parser<any>[]\n ) =>\n <GivenDeps extends RequiredOrOptional<GivenState>>(dependencies: {\n given: GivenDeps;\n }) => ({\n step: addStep<\n GivenInput<Variables, Restrict<GivenState, GivenDeps>>,\n GivenOutput<GivenState>\n >(\n statement,\n GIVEN,\n { ...dependencies, when: {}, then: {} } as FullDependencies,\n parsers\n ),\n });\n\nconst givenParsers =\n <Variables extends any[], GivenState>(\n statement: (...args: any[]) => string\n ) =>\n <Parsers extends { [K in keyof Variables]: Parser<Variables[K]> }>(\n parsers: Parsers\n ) => ({\n dependencies: givenDependencies<Variables, GivenState>(\n statement,\n parsers as unknown as Parser<any>[]\n ),\n step: addStep<GivenInput<Variables, never>, GivenOutput<GivenState>>(\n statement,\n GIVEN,\n undefined,\n parsers as unknown as Parser<any>[]\n ),\n });\n\nconst givenStatement =\n <GivenState>() =>\n <Statement extends ((...args: [...any]) => string) | string>(\n statement: Statement\n ) => {\n const normalizedStatement: (...args: any[]) => string = isString(statement)\n ? () => statement\n : (statement as (...args: any[]) => string);\n\n type Variables = Statement extends string ? [] : GetFunctionArgs<Statement>;\n return {\n dependencies: givenDependencies<Variables, GivenState>(\n normalizedStatement\n ),\n parsers: givenParsers<Variables, GivenState>(normalizedStatement),\n step: addStep<GivenInput<Variables, never>, GivenOutput<GivenState>>(\n normalizedStatement,\n GIVEN\n ),\n };\n };\n\nexport const givenBuilder = <GivenState>() => ({\n statement: givenStatement<GivenState>(),\n});\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport {\n FullDependencies,\n GetFunctionArgs,\n isString,\n RequiredOrOptional,\n Restrict,\n StepType,\n} from \"./builderTypeUtils\";\nimport { addStep } from \"./common\";\nimport { Parser } from \"./parsers\";\n\nconst WHEN: StepType = \"when\";\n\n// The object a when step receives: `given` and `when` are reachable (narrowed to\n// declared dependencies, or `never` when none), `then` is always out of reach.\ntype WhenInput<Variables, Given, When> = {\n variables: Variables;\n given: Given;\n when: When;\n then: never;\n};\ntype WhenOutput<WhenState> = Partial<WhenState> | Promise<Partial<WhenState>>;\n\nconst whenDependencies =\n <Variables, GivenState, WhenState>(\n statement: (...args: any[]) => string,\n parsers?: Parser<any>[]\n ) =>\n <\n GivenDeps extends RequiredOrOptional<GivenState>,\n WhenDeps extends RequiredOrOptional<WhenState>,\n >(dependencies: {\n given?: GivenDeps;\n when?: WhenDeps;\n }) => ({\n step: addStep<\n WhenInput<\n Variables,\n Restrict<GivenState, GivenDeps>,\n Restrict<WhenState, WhenDeps>\n >,\n WhenOutput<WhenState>\n >(\n statement,\n WHEN,\n {\n given: dependencies.given ?? {},\n when: dependencies.when ?? {},\n then: {},\n } as FullDependencies,\n parsers\n ),\n });\n\nconst whenParsers =\n <Variables extends any[], GivenState, WhenState>(\n statement: (...args: any[]) => string\n ) =>\n <Parsers extends { [K in keyof Variables]: Parser<Variables[K]> }>(\n parsers: Parsers\n ) => ({\n dependencies: whenDependencies<Variables, GivenState, WhenState>(\n statement,\n parsers as unknown as Parser<any>[]\n ),\n step: addStep<WhenInput<Variables, never, never>, WhenOutput<WhenState>>(\n statement,\n WHEN,\n undefined,\n parsers as unknown as Parser<any>[]\n ),\n });\n\nconst whenStatement =\n <GivenState, WhenState>() =>\n <Statement extends ((...args: [...any]) => string) | string>(\n statement: Statement\n ) => {\n const normalizedStatement: (...args: any[]) => string = isString(statement)\n ? () => statement\n : (statement as (...args: any[]) => string);\n\n type Variables = Statement extends string ? [] : GetFunctionArgs<Statement>;\n return {\n dependencies: whenDependencies<Variables, GivenState, WhenState>(\n normalizedStatement\n ),\n parsers: whenParsers<Variables, GivenState, WhenState>(\n normalizedStatement\n ),\n step: addStep<WhenInput<Variables, never, never>, WhenOutput<WhenState>>(\n normalizedStatement,\n WHEN\n ),\n };\n };\n\nexport const whenBuilder = <GivenState, WhenState>() => ({\n statement: whenStatement<GivenState, WhenState>(),\n});\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport {\n FullDependencies,\n GetFunctionArgs,\n isString,\n RequiredOrOptional,\n Restrict,\n StepType,\n} from \"./builderTypeUtils\";\nimport { addStep } from \"./common\";\nimport { Parser } from \"./parsers\";\n\nconst THEN: StepType = \"then\";\n\n// The object a then step receives: all three phases are reachable (each narrowed\n// to declared dependencies, or `never` when none). Then steps may also return\n// nothing (assertion-only), hence the `void` in the output type.\ntype ThenInput<Variables, Given, When, Then> = {\n variables: Variables;\n given: Given;\n when: When;\n then: Then;\n};\ntype ThenOutput<ThenState> =\n | Partial<ThenState>\n | Promise<Partial<ThenState>>\n | void\n | Promise<void>;\n\nconst thenDependencies =\n <Variables, GivenState, WhenState, ThenState>(\n statement: (...args: any[]) => string,\n parsers?: Parser<any>[]\n ) =>\n <\n GivenDeps extends RequiredOrOptional<GivenState>,\n WhenDeps extends RequiredOrOptional<WhenState>,\n ThenDeps extends RequiredOrOptional<ThenState>,\n >(dependencies: {\n given?: GivenDeps;\n when?: WhenDeps;\n then?: ThenDeps;\n }) => ({\n step: addStep<\n ThenInput<\n Variables,\n Restrict<GivenState, GivenDeps>,\n Restrict<WhenState, WhenDeps>,\n Restrict<ThenState, ThenDeps>\n >,\n ThenOutput<ThenState>\n >(\n statement,\n THEN,\n {\n given: dependencies.given ?? {},\n when: dependencies.when ?? {},\n then: dependencies.then ?? {},\n } as FullDependencies,\n parsers\n ),\n });\n\nconst thenParsers =\n <Variables extends any[], GivenState, WhenState, ThenState>(\n statement: (...args: any[]) => string\n ) =>\n <Parsers extends { [K in keyof Variables]: Parser<Variables[K]> }>(\n parsers: Parsers\n ) => ({\n dependencies: thenDependencies<Variables, GivenState, WhenState, ThenState>(\n statement,\n parsers as unknown as Parser<any>[]\n ),\n step: addStep<\n ThenInput<Variables, never, never, never>,\n ThenOutput<ThenState>\n >(statement, THEN, undefined, parsers as unknown as Parser<any>[]),\n });\n\nconst thenStatement =\n <GivenState, WhenState, ThenState>() =>\n <Statement extends ((...args: [...any]) => string) | string>(\n statement: Statement\n ) => {\n const normalizedStatement: (...args: any[]) => string = isString(statement)\n ? () => statement\n : (statement as (...args: any[]) => string);\n\n type Variables = Statement extends string ? [] : GetFunctionArgs<Statement>;\n return {\n dependencies: thenDependencies<\n Variables,\n GivenState,\n WhenState,\n ThenState\n >(normalizedStatement),\n parsers: thenParsers<Variables, GivenState, WhenState, ThenState>(\n normalizedStatement\n ),\n step: addStep<\n ThenInput<Variables, never, never, never>,\n ThenOutput<ThenState>\n >(normalizedStatement, THEN),\n };\n };\n\nexport const thenBuilder = <GivenState, WhenState, ThenState>() => ({\n statement: thenStatement<GivenState, WhenState, ThenState>(),\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 { 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","import { givenBuilder } from \"./given\";\nimport { thenBuilder } from \"./then\";\nimport { whenBuilder } from \"./when\";\n\nexport const createBuilders = <GivenState, WhenState, ThenState>() => {\n return {\n Given: givenBuilder<GivenState>().statement,\n When: whenBuilder<GivenState, WhenState>().statement,\n Then: thenBuilder<GivenState, WhenState, ThenState>().statement,\n };\n};\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n globalHookRegistry,\n PlainHookFn,\n ScenarioHookFn,\n ScenarioInfo,\n} from \"./runtime/hooks\";\nimport { MergeableWorld } from \"./world\";\n\n/**\n * Run before every scenario, with access to that scenario's fresh world. For\n * side effects only (reset a mock, seed an external system) — return values are\n * ignored; seed test *state* with given steps so the dependency graph stays the\n * single source of truth.\n */\nexport function beforeScenario<\n World extends MergeableWorld<any, any, any> = MergeableWorld<any, any, any>,\n>(\n fn: (context: {\n world: World;\n scenario: ScenarioInfo;\n }) => void | Promise<void>\n): void {\n globalHookRegistry.add({\n scope: \"scenario\",\n timing: \"before\",\n fn: fn as ScenarioHookFn,\n });\n}\n\n/**\n * Run after every scenario (even when a step failed), with access to that\n * scenario's world. Runs in reverse registration order so teardown unwinds\n * setup. For cleanup side effects only.\n */\nexport function afterScenario<\n World extends MergeableWorld<any, any, any> = MergeableWorld<any, any, any>,\n>(\n fn: (context: {\n world: World;\n scenario: ScenarioInfo;\n }) => void | Promise<void>\n): void {\n globalHookRegistry.add({\n scope: \"scenario\",\n timing: \"after\",\n fn: fn as ScenarioHookFn,\n });\n}\n\n/** Run once at the start of each feature file. No world exists at this boundary. */\nexport function beforeFeature(fn: PlainHookFn): void {\n globalHookRegistry.add({ scope: \"feature\", timing: \"before\", fn });\n}\n\n/** Run once at the end of each feature file (reverse registration order). */\nexport function afterFeature(fn: PlainHookFn): void {\n globalHookRegistry.add({ scope: \"feature\", timing: \"after\", fn });\n}\n\n/** Run once before the entire test run, across all feature files. */\nexport function beforeAll(fn: PlainHookFn): void {\n globalHookRegistry.add({ scope: \"global\", timing: \"before\", fn });\n}\n\n/** Run once after the entire test run (reverse registration order). */\nexport function afterAll(fn: PlainHookFn): void {\n globalHookRegistry.add({ scope: \"global\", timing: \"after\", fn });\n}\n","import { givenBuilder } from \"./given\";\nimport { whenBuilder } from \"./when\";\nimport { thenBuilder } from \"./then\";\nimport { BasicWorld } from \"./world\";\nimport {\n stringParser,\n intParser,\n numberParser,\n booleanParser,\n} from \"./parsers\";\nimport type { Parser } from \"./parsers\";\nimport {\n analyze,\n extractStepDefinitions,\n parseFeatureFiles,\n parseFeatureContent,\n matchScenarioSteps,\n findMatchingDefinitions,\n defaultRules,\n} from \"./analyzer/index\";\nimport type {\n AnalyzerConfig,\n AnalyzeOptions,\n AnalysisRule,\n Diagnostic,\n StepDefinitionMeta,\n ParsedScenario,\n ParsedStep,\n MatchedStep,\n} from \"./analyzer/index\";\nimport { createBuilders } from \"./init\";\nimport {\n beforeScenario,\n afterScenario,\n beforeFeature,\n afterFeature,\n beforeAll,\n afterAll,\n} from \"./hooks\";\n\nexport {\n givenBuilder,\n whenBuilder,\n thenBuilder,\n BasicWorld,\n stringParser,\n intParser,\n numberParser,\n booleanParser,\n createBuilders,\n beforeScenario,\n afterScenario,\n beforeFeature,\n afterFeature,\n beforeAll,\n afterAll,\n};\n\nexport type { Parser };\nexport type {\n ScenarioInfo,\n PlainHookFn,\n ScenarioHookFn,\n} from \"./runtime/hooks\";\nexport type { StateFromDependencies } from \"./typeHelpers\";\n\nexport const analyzer = {\n analyze,\n extractStepDefinitions,\n parseFeatureFiles,\n parseFeatureContent,\n matchScenarioSteps,\n findMatchingDefinitions,\n defaultRules,\n};\n\nexport type {\n AnalyzerConfig,\n AnalyzeOptions,\n AnalysisRule,\n Diagnostic,\n StepDefinitionMeta,\n ParsedScenario,\n ParsedStep,\n MatchedStep,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"step-forge.js","names":[],"sources":["../../src/builderTypeUtils.ts","../../src/parsers.ts","../../src/utils.ts","../../src/common.ts","../../src/given.ts","../../src/when.ts","../../src/then.ts","../../src/analyzer/stepExtractor.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","../../src/init.ts","../../src/hooks.ts","../../src/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nexport type StepType = \"given\" | \"when\" | \"then\";\n\n/** A dependency declaration: each key of a phase's state marked required/optional. */\nexport type RequiredOrOptional<T> = {\n [K in keyof T]?: \"required\" | \"optional\";\n};\n\n/** The dependency map for a single phase, keyed by state property name. */\nexport type DepMap = Record<string, \"required\" | \"optional\">;\n\n/** The fully-resolved dependency declaration passed to `addStep` at runtime. */\nexport type FullDependencies = {\n given: DepMap;\n when: DepMap;\n then: DepMap;\n};\n\n/**\n * Narrows a phase's full state to only the keys named in a dependency map.\n * Optional dependencies widen to `| undefined`; keys not present in the state\n * are dropped. This is the shape a step function sees for `given`/`when`/`then`.\n */\nexport type Restrict<State, Deps extends RequiredOrOptional<State>> = {\n [K in keyof State as K extends keyof Deps\n ? K\n : never]: Deps[K] extends \"optional\" ? State[K] | undefined : State[K];\n};\n\nexport type GetFunctionArgs<T> = T extends (...args: infer A) => any\n ? A\n : never;\n\nexport const isString = (\n statement: string | ((...args: [...any]) => string)\n): statement is string => typeof statement === \"string\";\n","/**\n * A Parser is a typed cucumber-expression *parameter type*: it declares how a\n * value is recognised in a Gherkin step (`regexp`) and how the matched text is\n * turned into a TypeScript value (`parse`). The step expression uses `{name}`\n * as the placeholder.\n *\n * The engine folds each parser straight into the cucumber-expression matcher —\n * `parse` is the parameter type's transform, so coercion happens exactly once,\n * during matching (no second pass). This is also what lets a parser introduce a\n * brand-new placeholder like `{color}`: it is registered as a real parameter\n * type, so text that doesn't match `regexp` simply doesn't match the step.\n *\n * The four built-ins below reuse cucumber-expressions' own built-in parameter\n * types (`{int}`, `{float}`, `{string}`) — those names are already registered,\n * so the library performs the match + coercion and each parser's `regexp`/\n * `parse` serve to document intent and to drive TypeScript inference of the\n * variable type. `booleanParser` has no built-in equivalent, so it is a genuine\n * custom parameter type whose `parse` runs at match time.\n */\nexport type Parser<T> = {\n /** Parameter-type name; the step expression uses `{name}` as the placeholder. */\n name: string;\n /** How cucumber-expressions recognises the value in the step text. */\n regexp: RegExp | RegExp[];\n /** Transform the matched text into the typed value. */\n parse: (value: string) => T;\n};\n\n/** Matches a quoted string (`{string}`) and strips the surrounding quotes. */\nexport const stringParser: Parser<string> = {\n name: \"string\",\n regexp: [/\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\"/, /'([^'\\\\]*(\\\\.[^'\\\\]*)*)'/],\n parse: value => {\n const match = /^\"([\\s\\S]*)\"$/.exec(value) ?? /^'([\\s\\S]*)'$/.exec(value);\n return match ? match[1].replace(/\\\\([\"'])/g, \"$1\") : value;\n },\n};\n\n/** Matches an unquoted integer (`{int}`). */\nexport const intParser: Parser<number> = {\n name: \"int\",\n regexp: /-?\\d+/,\n parse: value => parseInt(value, 10),\n};\n\n/** Matches an unquoted floating point number (`{float}`). */\nexport const numberParser: Parser<number> = {\n name: \"float\",\n regexp: /-?\\d*\\.?\\d+/,\n parse: value => parseFloat(value),\n};\n\n/**\n * Matches an unquoted `true`/`false` word (`{boolean}`) and parses it to a\n * boolean. Unlike the others this is a genuine custom parameter type — cucumber\n * has no built-in `boolean` — so its `regexp`/`parse` are what the matcher uses.\n */\nexport const booleanParser: Parser<boolean> = {\n name: \"boolean\",\n regexp: /true|false/,\n parse: value => value === \"true\",\n};\n","import { MergeableWorld } from \"./world\";\n\nexport const requireFromGiven = <G>(\n keys: (keyof G)[],\n world: MergeableWorld<G, unknown, unknown>\n) => {\n keys.forEach(key => {\n if (!world.given[key]) {\n throw new Error(`Key ${String(key)} is required in given state`);\n }\n });\n return keys.reduce(\n (acc, key) => {\n return {\n ...acc,\n [key]: world.given[key],\n };\n },\n {} as { [key in keyof G]: G[key] }\n );\n};\nexport const requireFromWhen = <W>(\n keys: (keyof W)[],\n world: MergeableWorld<unknown, W, unknown>\n) => {\n keys.forEach(key => {\n if (!world.when[key]) {\n throw new Error(`Key ${String(key)} is required in when state`);\n }\n });\n return keys.reduce(\n (acc, key) => {\n return {\n ...acc,\n [key]: world.when[key],\n };\n },\n {} as { [key in keyof W]: W[key] }\n );\n};\nexport const requireFromThen = <T>(\n keys: (keyof T)[],\n world: MergeableWorld<unknown, unknown, T>\n) => {\n keys.forEach(key => {\n if (!world.then[key]) {\n throw new Error(`Key ${String(key)} is required in then state`);\n }\n });\n return keys.reduce(\n (acc, key) => {\n return {\n ...acc,\n [key]: world.then[key],\n };\n },\n {} as { [key in keyof T]: T[key] }\n );\n};\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport _ from \"lodash\";\n\nimport { DepMap, FullDependencies, StepType } from \"./builderTypeUtils\";\nimport { Parser, stringParser } from \"./parsers\";\nimport { globalRegistry } from \"./runtime/registry\";\nimport { captureDefinitionSite } from \"./sourceLocation\";\nimport { requireFromGiven, requireFromThen, requireFromWhen } from \"./utils\";\nimport { MergeableWorld } from \"./world\";\n\n/**\n * The runtime core of the builder chain. `addStep` is deliberately phase-agnostic:\n * the calling builder (given/when/then) has already computed the exact type of the\n * step function via its two type parameters:\n *\n * - `StepFnInput` — the `{ variables, given, when, then }` object the step receives,\n * with each phase already narrowed to its declared dependencies.\n * - `StepFnOutput` — the phase-appropriate return type (`Partial<State>`, or `void`).\n *\n * Everything else is plain runtime data (a statement function, the step type, the\n * dependency map, the parsers), so `addStep` carries no generics for them.\n */\nexport const addStep =\n <StepFnInput, StepFnOutput>(\n statement: (...args: any[]) => string,\n stepType: StepType,\n dependencies: FullDependencies = {\n given: {},\n when: {},\n then: {},\n },\n declaredParsers?: Parser<any>[]\n ) =>\n (stepFunction: (input: StepFnInput) => StepFnOutput) => {\n const {\n given: givenDependencies,\n when: whenDependencies,\n then: thenDependencies,\n } = dependencies;\n // Resolve the parsers up front, defaulting every variable to `stringParser`\n // (the `{string}` placeholder, value passed through unchanged) when none are\n // provided. Numeric/boolean values are opt-in via explicit parsers.\n const argCount = statement.length;\n const parsers =\n declaredParsers ?? Array.from({ length: argCount }, () => stringParser);\n const expression = statement(...parsers.map(parser => `{${parser.name}}`));\n // The fully-wired step body, decoupled from any test runner: takes an\n // explicit world plus the values captured from a Gherkin step, validates +\n // narrows dependencies, runs the user's step, and merges the result. The\n // captured values arrive already coerced — each parser is registered as the\n // cucumber-expression parameter type, so `parse` runs during matching, not\n // here.\n const execute = async (\n world: MergeableWorld<any, any, any>,\n capturedArgs: unknown[]\n ) => {\n const requiredKeys = (deps: DepMap) =>\n Object.entries(deps)\n .filter(([, value]) => value === \"required\")\n .map(([key]) => key);\n const narrowedGiven = {\n ..._.pick(world.given, Object.keys(givenDependencies)),\n ...requireFromGiven(requiredKeys(givenDependencies), world),\n };\n const narrowedWhen = {\n ..._.pick(world.when, Object.keys(whenDependencies)),\n ...requireFromWhen(requiredKeys(whenDependencies), world),\n };\n const narrowedThen = {\n ..._.pick(world.then, Object.keys(thenDependencies)),\n ...requireFromThen(requiredKeys(thenDependencies), world),\n };\n const result = await stepFunction({\n variables: capturedArgs,\n given: narrowedGiven,\n when: narrowedWhen,\n then: narrowedThen,\n } as StepFnInput);\n world[stepType].merge({\n ...(result as any),\n });\n };\n\n // Registration is the terminal action of the builder chain: calling\n // `.step(fn)` makes the step matchable and executable by the runtime. We\n // capture *this* call site (the user's `.step(...)` line) so reporters can\n // show where a failing step is defined, Cucumber-style.\n const source = captureDefinitionSite();\n globalRegistry.add({ stepType, expression, parsers, execute, source });\n\n return {\n statement,\n expression,\n dependencies,\n stepType,\n stepFunction,\n };\n };\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport {\n FullDependencies,\n GetFunctionArgs,\n isString,\n RequiredOrOptional,\n Restrict,\n StepType,\n} from \"./builderTypeUtils\";\nimport { addStep } from \"./common\";\nimport { Parser } from \"./parsers\";\n\nconst GIVEN: StepType = \"given\";\n\n// The `{ variables, given, when, then }` object a given step receives. Only\n// `given` is reachable — when/then are `never`, enforcing phase restriction.\n// `Given` is the state already narrowed to declared dependencies (`never` when\n// no dependencies were declared).\ntype GivenInput<Variables, Given> = {\n variables: Variables;\n given: Given;\n when: never;\n then: never;\n};\ntype GivenOutput<GivenState> =\n | Partial<GivenState>\n | Promise<Partial<GivenState>>;\n\nconst givenDependencies =\n <Variables, GivenState>(\n statement: (...args: any[]) => string,\n parsers?: Parser<any>[]\n ) =>\n <GivenDeps extends RequiredOrOptional<GivenState>>(dependencies: {\n given: GivenDeps;\n }) => ({\n step: addStep<\n GivenInput<Variables, Restrict<GivenState, GivenDeps>>,\n GivenOutput<GivenState>\n >(\n statement,\n GIVEN,\n { ...dependencies, when: {}, then: {} } as FullDependencies,\n parsers\n ),\n });\n\nconst givenParsers =\n <Variables extends any[], GivenState>(\n statement: (...args: any[]) => string\n ) =>\n <Parsers extends { [K in keyof Variables]: Parser<Variables[K]> }>(\n parsers: Parsers\n ) => ({\n dependencies: givenDependencies<Variables, GivenState>(\n statement,\n parsers as unknown as Parser<any>[]\n ),\n step: addStep<GivenInput<Variables, never>, GivenOutput<GivenState>>(\n statement,\n GIVEN,\n undefined,\n parsers as unknown as Parser<any>[]\n ),\n });\n\nconst givenStatement =\n <GivenState>() =>\n <Statement extends ((...args: [...any]) => string) | string>(\n statement: Statement\n ) => {\n const normalizedStatement: (...args: any[]) => string = isString(statement)\n ? () => statement\n : (statement as (...args: any[]) => string);\n\n type Variables = Statement extends string ? [] : GetFunctionArgs<Statement>;\n return {\n dependencies: givenDependencies<Variables, GivenState>(\n normalizedStatement\n ),\n parsers: givenParsers<Variables, GivenState>(normalizedStatement),\n step: addStep<GivenInput<Variables, never>, GivenOutput<GivenState>>(\n normalizedStatement,\n GIVEN\n ),\n };\n };\n\nexport const givenBuilder = <GivenState>() => ({\n statement: givenStatement<GivenState>(),\n});\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport {\n FullDependencies,\n GetFunctionArgs,\n isString,\n RequiredOrOptional,\n Restrict,\n StepType,\n} from \"./builderTypeUtils\";\nimport { addStep } from \"./common\";\nimport { Parser } from \"./parsers\";\n\nconst WHEN: StepType = \"when\";\n\n// The object a when step receives: `given` and `when` are reachable (narrowed to\n// declared dependencies, or `never` when none), `then` is always out of reach.\ntype WhenInput<Variables, Given, When> = {\n variables: Variables;\n given: Given;\n when: When;\n then: never;\n};\ntype WhenOutput<WhenState> = Partial<WhenState> | Promise<Partial<WhenState>>;\n\nconst whenDependencies =\n <Variables, GivenState, WhenState>(\n statement: (...args: any[]) => string,\n parsers?: Parser<any>[]\n ) =>\n <\n GivenDeps extends RequiredOrOptional<GivenState>,\n WhenDeps extends RequiredOrOptional<WhenState>,\n >(dependencies: {\n given?: GivenDeps;\n when?: WhenDeps;\n }) => ({\n step: addStep<\n WhenInput<\n Variables,\n Restrict<GivenState, GivenDeps>,\n Restrict<WhenState, WhenDeps>\n >,\n WhenOutput<WhenState>\n >(\n statement,\n WHEN,\n {\n given: dependencies.given ?? {},\n when: dependencies.when ?? {},\n then: {},\n } as FullDependencies,\n parsers\n ),\n });\n\nconst whenParsers =\n <Variables extends any[], GivenState, WhenState>(\n statement: (...args: any[]) => string\n ) =>\n <Parsers extends { [K in keyof Variables]: Parser<Variables[K]> }>(\n parsers: Parsers\n ) => ({\n dependencies: whenDependencies<Variables, GivenState, WhenState>(\n statement,\n parsers as unknown as Parser<any>[]\n ),\n step: addStep<WhenInput<Variables, never, never>, WhenOutput<WhenState>>(\n statement,\n WHEN,\n undefined,\n parsers as unknown as Parser<any>[]\n ),\n });\n\nconst whenStatement =\n <GivenState, WhenState>() =>\n <Statement extends ((...args: [...any]) => string) | string>(\n statement: Statement\n ) => {\n const normalizedStatement: (...args: any[]) => string = isString(statement)\n ? () => statement\n : (statement as (...args: any[]) => string);\n\n type Variables = Statement extends string ? [] : GetFunctionArgs<Statement>;\n return {\n dependencies: whenDependencies<Variables, GivenState, WhenState>(\n normalizedStatement\n ),\n parsers: whenParsers<Variables, GivenState, WhenState>(\n normalizedStatement\n ),\n step: addStep<WhenInput<Variables, never, never>, WhenOutput<WhenState>>(\n normalizedStatement,\n WHEN\n ),\n };\n };\n\nexport const whenBuilder = <GivenState, WhenState>() => ({\n statement: whenStatement<GivenState, WhenState>(),\n});\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport {\n FullDependencies,\n GetFunctionArgs,\n isString,\n RequiredOrOptional,\n Restrict,\n StepType,\n} from \"./builderTypeUtils\";\nimport { addStep } from \"./common\";\nimport { Parser } from \"./parsers\";\n\nconst THEN: StepType = \"then\";\n\n// The object a then step receives: all three phases are reachable (each narrowed\n// to declared dependencies, or `never` when none). Then steps may also return\n// nothing (assertion-only), hence the `void` in the output type.\ntype ThenInput<Variables, Given, When, Then> = {\n variables: Variables;\n given: Given;\n when: When;\n then: Then;\n};\ntype ThenOutput<ThenState> =\n | Partial<ThenState>\n | Promise<Partial<ThenState>>\n | void\n | Promise<void>;\n\nconst thenDependencies =\n <Variables, GivenState, WhenState, ThenState>(\n statement: (...args: any[]) => string,\n parsers?: Parser<any>[]\n ) =>\n <\n GivenDeps extends RequiredOrOptional<GivenState>,\n WhenDeps extends RequiredOrOptional<WhenState>,\n ThenDeps extends RequiredOrOptional<ThenState>,\n >(dependencies: {\n given?: GivenDeps;\n when?: WhenDeps;\n then?: ThenDeps;\n }) => ({\n step: addStep<\n ThenInput<\n Variables,\n Restrict<GivenState, GivenDeps>,\n Restrict<WhenState, WhenDeps>,\n Restrict<ThenState, ThenDeps>\n >,\n ThenOutput<ThenState>\n >(\n statement,\n THEN,\n {\n given: dependencies.given ?? {},\n when: dependencies.when ?? {},\n then: dependencies.then ?? {},\n } as FullDependencies,\n parsers\n ),\n });\n\nconst thenParsers =\n <Variables extends any[], GivenState, WhenState, ThenState>(\n statement: (...args: any[]) => string\n ) =>\n <Parsers extends { [K in keyof Variables]: Parser<Variables[K]> }>(\n parsers: Parsers\n ) => ({\n dependencies: thenDependencies<Variables, GivenState, WhenState, ThenState>(\n statement,\n parsers as unknown as Parser<any>[]\n ),\n step: addStep<\n ThenInput<Variables, never, never, never>,\n ThenOutput<ThenState>\n >(statement, THEN, undefined, parsers as unknown as Parser<any>[]),\n });\n\nconst thenStatement =\n <GivenState, WhenState, ThenState>() =>\n <Statement extends ((...args: [...any]) => string) | string>(\n statement: Statement\n ) => {\n const normalizedStatement: (...args: any[]) => string = isString(statement)\n ? () => statement\n : (statement as (...args: any[]) => string);\n\n type Variables = Statement extends string ? [] : GetFunctionArgs<Statement>;\n return {\n dependencies: thenDependencies<\n Variables,\n GivenState,\n WhenState,\n ThenState\n >(normalizedStatement),\n parsers: thenParsers<Variables, GivenState, WhenState, ThenState>(\n normalizedStatement\n ),\n step: addStep<\n ThenInput<Variables, never, never, never>,\n ThenOutput<ThenState>\n >(normalizedStatement, THEN),\n };\n };\n\nexport const thenBuilder = <GivenState, WhenState, ThenState>() => ({\n statement: thenStatement<GivenState, WhenState, ThenState>(),\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 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 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 { 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","import { givenBuilder } from \"./given\";\nimport { thenBuilder } from \"./then\";\nimport { whenBuilder } from \"./when\";\n\nexport const createBuilders = <GivenState, WhenState, ThenState>() => {\n return {\n Given: givenBuilder<GivenState>().statement,\n When: whenBuilder<GivenState, WhenState>().statement,\n Then: thenBuilder<GivenState, WhenState, ThenState>().statement,\n };\n};\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n globalHookRegistry,\n PlainHookFn,\n ScenarioHookFn,\n ScenarioInfo,\n} from \"./runtime/hooks\";\nimport { MergeableWorld } from \"./world\";\n\n/**\n * Run before every scenario, with access to that scenario's fresh world. For\n * side effects only (reset a mock, seed an external system) — return values are\n * ignored; seed test *state* with given steps so the dependency graph stays the\n * single source of truth.\n */\nexport function beforeScenario<\n World extends MergeableWorld<any, any, any> = MergeableWorld<any, any, any>,\n>(\n fn: (context: {\n world: World;\n scenario: ScenarioInfo;\n }) => void | Promise<void>\n): void {\n globalHookRegistry.add({\n scope: \"scenario\",\n timing: \"before\",\n fn: fn as ScenarioHookFn,\n });\n}\n\n/**\n * Run after every scenario (even when a step failed), with access to that\n * scenario's world. Runs in reverse registration order so teardown unwinds\n * setup. For cleanup side effects only.\n */\nexport function afterScenario<\n World extends MergeableWorld<any, any, any> = MergeableWorld<any, any, any>,\n>(\n fn: (context: {\n world: World;\n scenario: ScenarioInfo;\n }) => void | Promise<void>\n): void {\n globalHookRegistry.add({\n scope: \"scenario\",\n timing: \"after\",\n fn: fn as ScenarioHookFn,\n });\n}\n\n/** Run once at the start of each feature file. No world exists at this boundary. */\nexport function beforeFeature(fn: PlainHookFn): void {\n globalHookRegistry.add({ scope: \"feature\", timing: \"before\", fn });\n}\n\n/** Run once at the end of each feature file (reverse registration order). */\nexport function afterFeature(fn: PlainHookFn): void {\n globalHookRegistry.add({ scope: \"feature\", timing: \"after\", fn });\n}\n\n/**\n * Run once before the entire run, ahead of any scenario (and before\n * concurrency starts). Multiple `beforeAll` hooks run **in parallel** with no\n * ordering between them — if a step of setup must precede another, sequence both\n * inside a single hook.\n */\nexport function beforeAll(fn: PlainHookFn): void {\n globalHookRegistry.add({ scope: \"global\", timing: \"before\", fn });\n}\n\n/**\n * Run once after the entire run, once every scenario is done. Multiple\n * `afterAll` hooks run **in parallel** with no ordering between them — sequence\n * dependent teardown inside a single hook.\n */\nexport function afterAll(fn: PlainHookFn): void {\n globalHookRegistry.add({ scope: \"global\", timing: \"after\", fn });\n}\n","import { givenBuilder } from \"./given\";\nimport { whenBuilder } from \"./when\";\nimport { thenBuilder } from \"./then\";\nimport { BasicWorld } from \"./world\";\nimport {\n stringParser,\n intParser,\n numberParser,\n booleanParser,\n} from \"./parsers\";\nimport type { Parser } from \"./parsers\";\nimport {\n analyze,\n extractStepDefinitions,\n parseFeatureFiles,\n parseFeatureContent,\n matchScenarioSteps,\n findMatchingDefinitions,\n defaultRules,\n} from \"./analyzer/index\";\nimport type {\n AnalyzerConfig,\n AnalyzeOptions,\n AnalysisRule,\n Diagnostic,\n StepDefinitionMeta,\n ParsedScenario,\n ParsedStep,\n MatchedStep,\n} from \"./analyzer/index\";\nimport { createBuilders } from \"./init\";\nimport {\n beforeScenario,\n afterScenario,\n beforeFeature,\n afterFeature,\n beforeAll,\n afterAll,\n} from \"./hooks\";\n\nexport {\n givenBuilder,\n whenBuilder,\n thenBuilder,\n BasicWorld,\n stringParser,\n intParser,\n numberParser,\n booleanParser,\n createBuilders,\n beforeScenario,\n afterScenario,\n beforeFeature,\n afterFeature,\n beforeAll,\n afterAll,\n};\n\nexport type { Parser };\nexport type {\n ScenarioInfo,\n PlainHookFn,\n ScenarioHookFn,\n} from \"./runtime/hooks\";\nexport type { StateFromDependencies } from \"./typeHelpers\";\n// Re-exported from the main entry so consumers can type their\n// `step-forge.config.ts` with a plain `@step-forge/step-forge` import.\nexport type { RunnerOptions } from \"./runtime/config\";\n\nexport const analyzer = {\n analyze,\n extractStepDefinitions,\n parseFeatureFiles,\n parseFeatureContent,\n matchScenarioSteps,\n findMatchingDefinitions,\n defaultRules,\n};\n\nexport type {\n AnalyzerConfig,\n AnalyzeOptions,\n AnalysisRule,\n Diagnostic,\n StepDefinitionMeta,\n ParsedScenario,\n ParsedStep,\n MatchedStep,\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@step-forge/step-forge",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"module": "./dist/step-forge.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "rm -rf build && tsc --noEmit && tsdown && cp package.json README.md RUNTIME.md LICENSE.md CHANGELOG.md build/",
|
|
45
45
|
"test": "bun run test:unit && bun run test:features",
|
|
46
|
-
"test:unit": "bun test src
|
|
46
|
+
"test:unit": "bun test src",
|
|
47
47
|
"test:features": "bun src/runtime/cli.ts",
|
|
48
48
|
"test:ci": "bun run test",
|
|
49
49
|
"lint": "eslint . --ext .ts",
|
|
@@ -1 +0,0 @@
|
|
|
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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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"engine-DPRxs6eC.js","names":[],"sources":["../../src/runtime/engine.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n CucumberExpression,\n ParameterType,\n ParameterTypeRegistry,\n} from \"@cucumber/cucumber-expressions\";\nimport { ParsedScenario, ParsedStep } from \"../analyzer/types\";\nimport { MergeableWorld } from \"../world\";\nimport { globalHookRegistry, HookRegistry, ScenarioHookFn } from \"./hooks\";\nimport { RegisteredStep, StepRegistry, StepType } from \"./registry\";\n\nconst keywordToStepType: Record<ParsedStep[\"effectiveKeyword\"], StepType> = {\n Given: \"given\",\n When: \"when\",\n Then: \"then\",\n};\n\n/** A registered step paired with its compiled Cucumber expression. */\nexport interface CompiledStep {\n step: RegisteredStep;\n expression: CucumberExpression;\n}\n\nexport class UndefinedStepError extends Error {\n constructor(public readonly step: ParsedStep) {\n super(`Undefined step: ${step.effectiveKeyword} ${step.text}`);\n this.name = \"UndefinedStepError\";\n }\n}\n\nexport class AmbiguousStepError extends Error {\n constructor(\n public readonly step: ParsedStep,\n public readonly matches: RegisteredStep[]\n ) {\n super(\n `Ambiguous step: \"${step.text}\" matched ${matches.length} definitions:\\n` +\n matches.map(m => ` - ${m.expression}`).join(\"\\n\")\n );\n this.name = \"AmbiguousStepError\";\n }\n}\n\n/**\n * Compile a registry's steps into matchable Cucumber expressions **once** per\n * run. The result is reused for every scenario — compilation is pure and depends\n * only on the registry, so recompiling per scenario (as an earlier version did)\n * was wasted work proportional to scenarios × steps.\n */\nexport function compileRegistry(registry: StepRegistry): CompiledStep[] {\n return registry.all().map(step => {\n // Each step gets its own parameter-type registry (seeded with the\n // built-ins). A parser whose name is already registered — the built-in\n // `{int}`/`{float}`/`{string}`, or a repeat within the same step — reuses\n // that type; a novel name (e.g. `{boolean}`, `{color}`) is registered from\n // the parser's regexp + parse, so matching and coercion happen in one pass.\n const paramRegistry = new ParameterTypeRegistry();\n for (const parser of step.parsers) {\n if (paramRegistry.lookupByTypeName(parser.name)) continue;\n const regexps = Array.isArray(parser.regexp)\n ? parser.regexp\n : [parser.regexp];\n paramRegistry.defineParameterType(\n new ParameterType(parser.name, regexps, null, (value: string) =>\n parser.parse(value)\n )\n );\n }\n return {\n step,\n expression: new CucumberExpression(step.expression, paramRegistry),\n };\n });\n}\n\n/**\n * Find the single step definition matching a Gherkin step. Matching is\n * opinionated and strict: the keyword must line up with the step type, exactly\n * one definition must match, and undefined/ambiguous both throw rather than\n * silently skipping (unlike Cucumber's pending/undefined dance).\n */\nfunction matchStep(\n step: ParsedStep,\n compiled: CompiledStep[]\n): { step: RegisteredStep; args: unknown[] } {\n const expectedType = keywordToStepType[step.effectiveKeyword];\n const matches: { step: RegisteredStep; args: unknown[] }[] = [];\n\n for (const { step: def, expression } of compiled) {\n if (def.stepType !== expectedType) continue;\n const result = expression.match(step.text);\n if (result) {\n // The parsers are registered as the expression's parameter types, so the\n // captured values are already coerced (`{int}` → number, `{color}` → the\n // parser's T). `execute` consumes them as-is.\n matches.push({ step: def, args: result.map(a => a.getValue(null)) });\n }\n }\n\n if (matches.length === 0) throw new UndefinedStepError(step);\n if (matches.length > 1) {\n throw new AmbiguousStepError(\n step,\n matches.map(m => m.step)\n );\n }\n return matches[0];\n}\n\nexport interface StepResult {\n step: ParsedStep;\n status: \"passed\" | \"failed\" | \"skipped\";\n error?: Error;\n durationMs?: number;\n}\n\nexport interface ScenarioResult {\n scenario: ParsedScenario;\n status: \"passed\" | \"failed\";\n steps: StepResult[];\n /**\n * The scenario's first error, if it failed. Usually the same object as the\n * failing step's `error` (with a synthetic `.feature` stack frame attached),\n * but for a hook failure there's no step to point at, so this is the only\n * place it surfaces. Reporters read this; the runner never throws it.\n */\n error?: Error;\n /** Wall-clock duration of the whole scenario, in milliseconds. */\n durationMs?: number;\n}\n\n/**\n * Run one scenario against a pre-compiled step table. A fresh world is created\n * per scenario (state never leaks between scenarios). On the first failing step\n * the remaining steps are marked skipped, matching Cucumber's execution\n * semantics.\n *\n * Never throws for step or hook failures: it always resolves to a\n * `ScenarioResult` carrying the per-step breakdown and (on failure) the first\n * `error` with a synthetic `.feature` stack frame attached. Callers decide what\n * to do with a failure — the CLI runner reports it, a test-runner adapter can\n * re-throw `result.error`. It still rejects for truly exceptional conditions\n * (e.g. a bug in the engine itself), never for a normal test failure.\n *\n * Pass the compiled table from {@link compileRegistry} once and reuse it across\n * every scenario in the run.\n */\nexport async function runScenario(\n scenario: ParsedScenario,\n compiled: CompiledStep[],\n makeWorld: () => MergeableWorld<any, any, any>,\n hooks: HookRegistry = globalHookRegistry\n): Promise<ScenarioResult> {\n const start = now();\n const world = makeWorld();\n const scenarioInfo = { name: scenario.name, file: scenario.file };\n const steps: StepResult[] = [];\n let failed = false;\n let firstError: Error | undefined;\n\n const fail = (err: unknown) => {\n if (failed) return;\n failed = true;\n firstError = err instanceof Error ? err : new Error(String(err));\n };\n\n // before-scenario hooks: a throw here aborts the scenario before any step.\n try {\n for (const hook of hooks.for(\"scenario\", \"before\")) {\n await (hook.fn as ScenarioHookFn)({ world, scenario: scenarioInfo });\n }\n } catch (err) {\n fail(err);\n }\n\n for (const step of scenario.steps) {\n if (failed) {\n steps.push({ step, status: \"skipped\" });\n continue;\n }\n try {\n const { step: def, args } = matchStep(step, compiled);\n await def.execute(world, args);\n steps.push({ step, status: \"passed\" });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n steps.push({ step, status: \"failed\", error });\n fail(error);\n }\n }\n\n // after-scenario hooks always run (teardown), even on failure. A hook failure\n // only becomes the scenario's error if nothing else failed first.\n for (const hook of hooks.for(\"scenario\", \"after\")) {\n try {\n await (hook.fn as ScenarioHookFn)({ world, scenario: scenarioInfo });\n } catch (err) {\n fail(err);\n }\n }\n\n if (failed && firstError) {\n // Surface the failing Gherkin line as a real stack frame so reporters render\n // a code frame from the `.feature` file itself. Hook failures have no step\n // to point at, so they surface with their own stack unchanged.\n const failing = steps.find(s => s.status === \"failed\");\n if (failing) attachFeatureFrame(firstError, scenario.file, failing.step);\n }\n\n return {\n scenario,\n status: failed ? \"failed\" : \"passed\",\n steps,\n error: firstError,\n durationMs: now() - start,\n };\n}\n\n/**\n * Monotonic-ish millisecond clock. `performance.now()` where available (Node &\n * Bun both expose it globally), falling back to `Date.now()`. Kept in one place\n * so timing is consistent across scenarios.\n */\nfunction now(): number {\n return typeof performance !== \"undefined\" ? performance.now() : Date.now();\n}\n\n/**\n * Prepend a synthetic stack frame pointing at the failing Gherkin step. Because\n * the frame's file is the real `.feature` on disk, the test runner treats it as\n * a source location and shows a code frame at the step — instead of us jamming\n * `file:line` into the error message. The frame goes *above* the real stack, so\n * the step-definition frames (the actual throw site) are preserved below it.\n */\nfunction attachFeatureFrame(\n error: Error,\n file: string,\n step: ParsedStep\n): void {\n const label = `${step.effectiveKeyword} ${step.text}`;\n const frame = ` at ${label} (${file}:${step.line}:${step.column})`;\n const stack = error.stack;\n if (!stack) {\n error.stack = `${error.name}: ${error.message}\\n${frame}`;\n return;\n }\n // A message can span multiple lines, so split on the first frame marker\n // rather than the first newline to find where the header ends.\n const firstFrame = stack.indexOf(\"\\n at \");\n if (firstFrame === -1) {\n error.stack = `${stack}\\n${frame}`;\n } else {\n error.stack =\n stack.slice(0, firstFrame) + `\\n${frame}` + stack.slice(firstFrame);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"engine-DWAIlwWp.cjs","names":["ParameterTypeRegistry","ParameterType","CucumberExpression","globalHookRegistry"],"sources":["../../src/runtime/engine.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n CucumberExpression,\n ParameterType,\n ParameterTypeRegistry,\n} from \"@cucumber/cucumber-expressions\";\nimport { ParsedScenario, ParsedStep } from \"../analyzer/types\";\nimport { MergeableWorld } from \"../world\";\nimport { globalHookRegistry, HookRegistry, ScenarioHookFn } from \"./hooks\";\nimport { RegisteredStep, StepRegistry, StepType } from \"./registry\";\n\nconst keywordToStepType: Record<ParsedStep[\"effectiveKeyword\"], StepType> = {\n Given: \"given\",\n When: \"when\",\n Then: \"then\",\n};\n\n/** A registered step paired with its compiled Cucumber expression. */\nexport interface CompiledStep {\n step: RegisteredStep;\n expression: CucumberExpression;\n}\n\nexport class UndefinedStepError extends Error {\n constructor(public readonly step: ParsedStep) {\n super(`Undefined step: ${step.effectiveKeyword} ${step.text}`);\n this.name = \"UndefinedStepError\";\n }\n}\n\nexport class AmbiguousStepError extends Error {\n constructor(\n public readonly step: ParsedStep,\n public readonly matches: RegisteredStep[]\n ) {\n super(\n `Ambiguous step: \"${step.text}\" matched ${matches.length} definitions:\\n` +\n matches.map(m => ` - ${m.expression}`).join(\"\\n\")\n );\n this.name = \"AmbiguousStepError\";\n }\n}\n\n/**\n * Compile a registry's steps into matchable Cucumber expressions **once** per\n * run. The result is reused for every scenario — compilation is pure and depends\n * only on the registry, so recompiling per scenario (as an earlier version did)\n * was wasted work proportional to scenarios × steps.\n */\nexport function compileRegistry(registry: StepRegistry): CompiledStep[] {\n return registry.all().map(step => {\n // Each step gets its own parameter-type registry (seeded with the\n // built-ins). A parser whose name is already registered — the built-in\n // `{int}`/`{float}`/`{string}`, or a repeat within the same step — reuses\n // that type; a novel name (e.g. `{boolean}`, `{color}`) is registered from\n // the parser's regexp + parse, so matching and coercion happen in one pass.\n const paramRegistry = new ParameterTypeRegistry();\n for (const parser of step.parsers) {\n if (paramRegistry.lookupByTypeName(parser.name)) continue;\n const regexps = Array.isArray(parser.regexp)\n ? parser.regexp\n : [parser.regexp];\n paramRegistry.defineParameterType(\n new ParameterType(parser.name, regexps, null, (value: string) =>\n parser.parse(value)\n )\n );\n }\n return {\n step,\n expression: new CucumberExpression(step.expression, paramRegistry),\n };\n });\n}\n\n/**\n * Find the single step definition matching a Gherkin step. Matching is\n * opinionated and strict: the keyword must line up with the step type, exactly\n * one definition must match, and undefined/ambiguous both throw rather than\n * silently skipping (unlike Cucumber's pending/undefined dance).\n */\nfunction matchStep(\n step: ParsedStep,\n compiled: CompiledStep[]\n): { step: RegisteredStep; args: unknown[] } {\n const expectedType = keywordToStepType[step.effectiveKeyword];\n const matches: { step: RegisteredStep; args: unknown[] }[] = [];\n\n for (const { step: def, expression } of compiled) {\n if (def.stepType !== expectedType) continue;\n const result = expression.match(step.text);\n if (result) {\n // The parsers are registered as the expression's parameter types, so the\n // captured values are already coerced (`{int}` → number, `{color}` → the\n // parser's T). `execute` consumes them as-is.\n matches.push({ step: def, args: result.map(a => a.getValue(null)) });\n }\n }\n\n if (matches.length === 0) throw new UndefinedStepError(step);\n if (matches.length > 1) {\n throw new AmbiguousStepError(\n step,\n matches.map(m => m.step)\n );\n }\n return matches[0];\n}\n\nexport interface StepResult {\n step: ParsedStep;\n status: \"passed\" | \"failed\" | \"skipped\";\n error?: Error;\n durationMs?: number;\n}\n\nexport interface ScenarioResult {\n scenario: ParsedScenario;\n status: \"passed\" | \"failed\";\n steps: StepResult[];\n /**\n * The scenario's first error, if it failed. Usually the same object as the\n * failing step's `error` (with a synthetic `.feature` stack frame attached),\n * but for a hook failure there's no step to point at, so this is the only\n * place it surfaces. Reporters read this; the runner never throws it.\n */\n error?: Error;\n /** Wall-clock duration of the whole scenario, in milliseconds. */\n durationMs?: number;\n}\n\n/**\n * Run one scenario against a pre-compiled step table. A fresh world is created\n * per scenario (state never leaks between scenarios). On the first failing step\n * the remaining steps are marked skipped, matching Cucumber's execution\n * semantics.\n *\n * Never throws for step or hook failures: it always resolves to a\n * `ScenarioResult` carrying the per-step breakdown and (on failure) the first\n * `error` with a synthetic `.feature` stack frame attached. Callers decide what\n * to do with a failure — the CLI runner reports it, a test-runner adapter can\n * re-throw `result.error`. It still rejects for truly exceptional conditions\n * (e.g. a bug in the engine itself), never for a normal test failure.\n *\n * Pass the compiled table from {@link compileRegistry} once and reuse it across\n * every scenario in the run.\n */\nexport async function runScenario(\n scenario: ParsedScenario,\n compiled: CompiledStep[],\n makeWorld: () => MergeableWorld<any, any, any>,\n hooks: HookRegistry = globalHookRegistry\n): Promise<ScenarioResult> {\n const start = now();\n const world = makeWorld();\n const scenarioInfo = { name: scenario.name, file: scenario.file };\n const steps: StepResult[] = [];\n let failed = false;\n let firstError: Error | undefined;\n\n const fail = (err: unknown) => {\n if (failed) return;\n failed = true;\n firstError = err instanceof Error ? err : new Error(String(err));\n };\n\n // before-scenario hooks: a throw here aborts the scenario before any step.\n try {\n for (const hook of hooks.for(\"scenario\", \"before\")) {\n await (hook.fn as ScenarioHookFn)({ world, scenario: scenarioInfo });\n }\n } catch (err) {\n fail(err);\n }\n\n for (const step of scenario.steps) {\n if (failed) {\n steps.push({ step, status: \"skipped\" });\n continue;\n }\n try {\n const { step: def, args } = matchStep(step, compiled);\n await def.execute(world, args);\n steps.push({ step, status: \"passed\" });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n steps.push({ step, status: \"failed\", error });\n fail(error);\n }\n }\n\n // after-scenario hooks always run (teardown), even on failure. A hook failure\n // only becomes the scenario's error if nothing else failed first.\n for (const hook of hooks.for(\"scenario\", \"after\")) {\n try {\n await (hook.fn as ScenarioHookFn)({ world, scenario: scenarioInfo });\n } catch (err) {\n fail(err);\n }\n }\n\n if (failed && firstError) {\n // Surface the failing Gherkin line as a real stack frame so reporters render\n // a code frame from the `.feature` file itself. Hook failures have no step\n // to point at, so they surface with their own stack unchanged.\n const failing = steps.find(s => s.status === \"failed\");\n if (failing) attachFeatureFrame(firstError, scenario.file, failing.step);\n }\n\n return {\n scenario,\n status: failed ? \"failed\" : \"passed\",\n steps,\n error: firstError,\n durationMs: now() - start,\n };\n}\n\n/**\n * Monotonic-ish millisecond clock. `performance.now()` where available (Node &\n * Bun both expose it globally), falling back to `Date.now()`. Kept in one place\n * so timing is consistent across scenarios.\n */\nfunction now(): number {\n return typeof performance !== \"undefined\" ? performance.now() : Date.now();\n}\n\n/**\n * Prepend a synthetic stack frame pointing at the failing Gherkin step. Because\n * the frame's file is the real `.feature` on disk, the test runner treats it as\n * a source location and shows a code frame at the step — instead of us jamming\n * `file:line` into the error message. The frame goes *above* the real stack, so\n * the step-definition frames (the actual throw site) are preserved below it.\n */\nfunction attachFeatureFrame(\n error: Error,\n file: string,\n step: ParsedStep\n): void {\n const label = `${step.effectiveKeyword} ${step.text}`;\n const frame = ` at ${label} (${file}:${step.line}:${step.column})`;\n const stack = error.stack;\n if (!stack) {\n error.stack = `${error.name}: ${error.message}\\n${frame}`;\n return;\n }\n // A message can span multiple lines, so split on the first frame marker\n // rather than the first newline to find where the header ends.\n const firstFrame = stack.indexOf(\"\\n at \");\n if (firstFrame === -1) {\n error.stack = `${stack}\\n${frame}`;\n } else {\n error.stack =\n stack.slice(0, firstFrame) + `\\n${frame}` + stack.slice(firstFrame);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gherkinParser-CHpkEwii.cjs","names":["_","path","fs","Parser","AstBuilder","messages","GherkinClassicTokenMatcher"],"sources":["../../src/world.ts","../../src/globFiles.ts","../../src/analyzer/gherkinParser.ts"],"sourcesContent":["import _ from \"lodash\";\n\nexport type WorldState<State> = {\n readonly [K in keyof State]?: State[K];\n};\n\nexport type MergeableWorldState<T> = WorldState<T> & {\n merge: (newState: Partial<T>) => void;\n};\n\nfunction mergeCustomizer(objValue: unknown, srcValue: unknown) {\n if (_.isArray(objValue)) {\n return objValue.concat(srcValue);\n } else if (objValue && !_.isPlainObject(objValue) && objValue !== srcValue) {\n throw new Error(\n `Merge would have destroyed previous value ${objValue} with ${srcValue}`\n );\n }\n return objValue;\n}\n\nexport const createMergeableState = <T>(\n state: WorldState<T>\n): MergeableWorldState<T> => {\n return {\n ...state,\n merge: (newState: Partial<T>) => {\n state = _.merge({ ...state }, newState, mergeCustomizer);\n },\n };\n};\n\nexport type MergeableWorld<Given, When, Then> = {\n given: MergeableWorldState<Given>;\n when: MergeableWorldState<When>;\n then: MergeableWorldState<Then>;\n};\n\nexport class BasicWorld<Given, When, Then> {\n private givenState: WorldState<Given> = {};\n private whenState: WorldState<When> = {};\n private thenState: WorldState<Then> = {};\n\n public get given(): MergeableWorldState<Given> {\n return {\n ...this.givenState,\n merge: (newState: Partial<Given>) => {\n this.givenState = _.merge(\n { ...this.givenState },\n newState,\n mergeCustomizer\n );\n },\n };\n }\n\n public get when(): MergeableWorldState<When> {\n return {\n ...this.whenState,\n merge: (newState: Partial<When>) => {\n this.whenState = _.merge(\n { ...this.whenState },\n newState,\n mergeCustomizer\n );\n },\n };\n }\n\n public get then(): MergeableWorldState<Then> {\n return {\n ...this.thenState,\n merge: (newState: Partial<Then>) => {\n this.thenState = _.merge(\n { ...this.thenState },\n newState,\n mergeCustomizer\n );\n },\n };\n }\n}\n\nexport const createBasicWorld = <Given, When, Then>(): MergeableWorld<\n Given,\n When,\n Then\n> => {\n return new BasicWorld<Given, When, Then>();\n};\n","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 * 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"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gherkinParser-CKARHgt4.js","names":[],"sources":["../../src/world.ts","../../src/globFiles.ts","../../src/analyzer/gherkinParser.ts"],"sourcesContent":["import _ from \"lodash\";\n\nexport type WorldState<State> = {\n readonly [K in keyof State]?: State[K];\n};\n\nexport type MergeableWorldState<T> = WorldState<T> & {\n merge: (newState: Partial<T>) => void;\n};\n\nfunction mergeCustomizer(objValue: unknown, srcValue: unknown) {\n if (_.isArray(objValue)) {\n return objValue.concat(srcValue);\n } else if (objValue && !_.isPlainObject(objValue) && objValue !== srcValue) {\n throw new Error(\n `Merge would have destroyed previous value ${objValue} with ${srcValue}`\n );\n }\n return objValue;\n}\n\nexport const createMergeableState = <T>(\n state: WorldState<T>\n): MergeableWorldState<T> => {\n return {\n ...state,\n merge: (newState: Partial<T>) => {\n state = _.merge({ ...state }, newState, mergeCustomizer);\n },\n };\n};\n\nexport type MergeableWorld<Given, When, Then> = {\n given: MergeableWorldState<Given>;\n when: MergeableWorldState<When>;\n then: MergeableWorldState<Then>;\n};\n\nexport class BasicWorld<Given, When, Then> {\n private givenState: WorldState<Given> = {};\n private whenState: WorldState<When> = {};\n private thenState: WorldState<Then> = {};\n\n public get given(): MergeableWorldState<Given> {\n return {\n ...this.givenState,\n merge: (newState: Partial<Given>) => {\n this.givenState = _.merge(\n { ...this.givenState },\n newState,\n mergeCustomizer\n );\n },\n };\n }\n\n public get when(): MergeableWorldState<When> {\n return {\n ...this.whenState,\n merge: (newState: Partial<When>) => {\n this.whenState = _.merge(\n { ...this.whenState },\n newState,\n mergeCustomizer\n );\n },\n };\n }\n\n public get then(): MergeableWorldState<Then> {\n return {\n ...this.thenState,\n merge: (newState: Partial<Then>) => {\n this.thenState = _.merge(\n { ...this.thenState },\n newState,\n mergeCustomizer\n );\n },\n };\n }\n}\n\nexport const createBasicWorld = <Given, When, Then>(): MergeableWorld<\n Given,\n When,\n Then\n> => {\n return new BasicWorld<Given, When, Then>();\n};\n","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 * 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"],"mappings":"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"}
|