@step-forge/step-forge 0.0.25-alpha.4 → 0.0.25-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"gherkinParser-_ZgvELdy.cjs","names":["path","_","path","fs","Parser","AstBuilder","messages","GherkinClassicTokenMatcher"],"sources":["../../src/sourceLocation.ts","../../src/world.ts","../../src/globFiles.ts","../../src/analyzer/gherkinParser.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Directory holding the library's own compiled code. In development this is the\n * `src/` tree; in the published package it's `dist/` (this module is bundled\n * into the shipped chunks). Stack frames under it are internal plumbing —\n * builders, the engine, the runner — and are hidden from users so that a\n * failure points at *their* step, not ours.\n */\nconst LIB_ROOT = path.dirname(fileURLToPath(import.meta.url));\n\nexport interface SourceFrame {\n file: string;\n line: number;\n column: number;\n}\n\n/**\n * A frame belongs to user code if it's an absolute path that is neither inside\n * the library nor inside any `node_modules` (assertion libs, etc.). That leaves\n * exactly the frames a user cares about: their own step definitions.\n */\nfunction isUserFile(file: string): boolean {\n return (\n path.isAbsolute(file) &&\n !file.startsWith(LIB_ROOT + path.sep) &&\n !file.includes(`${path.sep}node_modules${path.sep}`)\n );\n}\n\n/** Parse one `Error.stack` line into a frame, tolerating V8/Bun variations. */\nfunction parseFrame(frameLine: string): SourceFrame | undefined {\n const m = /:(\\d+):(\\d+)\\)?\\s*$/.exec(frameLine);\n if (!m) return undefined;\n let file = frameLine.slice(0, m.index);\n const paren = file.lastIndexOf(\"(\");\n if (paren !== -1) file = file.slice(paren + 1);\n file = file.trim().replace(/^at\\s+/, \"\");\n if (file.startsWith(\"file://\")) {\n try {\n file = fileURLToPath(file);\n } catch {\n return undefined;\n }\n }\n return { file, line: Number(m[1]), column: Number(m[2]) };\n}\n\n/** Every user-code frame in a stack, nearest-first, internals removed. */\nexport function userFrames(stack: string | undefined): SourceFrame[] {\n if (!stack) return [];\n const frames: SourceFrame[] = [];\n for (const line of stack.split(\"\\n\")) {\n const frame = parseFrame(line);\n if (frame && isUserFile(frame.file)) frames.push(frame);\n }\n return frames;\n}\n\n/**\n * Capture the user source location of the current call site — the first\n * user-code frame above this function. Called from `.step()` registration so\n * each step remembers where it was defined (Cucumber-style), independent of\n * where an error is later thrown. Returns an absolute `file:line:column`, or\n * `undefined` if no user frame is visible.\n */\nexport function captureDefinitionSite(): string | undefined {\n const frame = userFrames(new Error().stack)[0];\n return frame ? `${frame.file}:${frame.line}:${frame.column}` : undefined;\n}\n\n/** Render an absolute `file:line:column` relative to `cwd` for display. */\nexport function relativeLocation(location: string, cwd: string): string {\n const m = /^(.*):(\\d+):(\\d+)$/.exec(location);\n if (!m) return location;\n return `${path.relative(cwd, m[1])}:${m[2]}:${m[3]}`;\n}\n\n/** Render a frame relative to `cwd`. */\nexport function relativeFrame(frame: SourceFrame, cwd: string): string {\n return `${path.relative(cwd, frame.file)}:${frame.line}:${frame.column}`;\n}\n","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 {\n GherkinClassicTokenMatcher,\n Parser,\n AstBuilder,\n} from \"@cucumber/gherkin\";\nimport * as messages from \"@cucumber/messages\";\nimport { ParsedScenario, ParsedStep } from \"./types.js\";\n\ntype GherkinKeyword = \"Given\" | \"When\" | \"Then\" | \"And\" | \"But\";\n\nexport function parseFeatureFiles(filePaths: string[]): ParsedScenario[] {\n const scenarios: ParsedScenario[] = [];\n\n for (const filePath of filePaths) {\n const content = fs.readFileSync(filePath, \"utf-8\");\n const parsed = parseFeatureContent(content, filePath);\n scenarios.push(...parsed);\n }\n\n return scenarios;\n}\n\n/**\n * One parsed feature file: its `Feature:` title (empty if unnamed) plus every\n * scenario (outline rows expanded). {@link parseFeatureContent} exposes just the\n * scenarios; {@link parseFeatureCatalog} keeps the title too, for UIs that label\n * scenarios by their feature.\n */\nexport interface ParsedFeature {\n file: string;\n name: string;\n scenarios: ParsedScenario[];\n}\n\n/** Build a fresh gherkin parser and parse `content` into a document. */\nfunction parseDocument(content: string): messages.GherkinDocument {\n const builder = new AstBuilder(messages.IdGenerator.uuid());\n const matcher = new GherkinClassicTokenMatcher();\n return new Parser(builder, matcher).parse(content);\n}\n\n/**\n * Parse feature files into a catalog of `{ file, name, scenarios }`, one entry\n * per file (one gherkin parse each). Used by interactive mode to offer\n * feature/scenario names as typeahead choices labelled by their `Feature:` title.\n */\nexport function parseFeatureCatalog(filePaths: string[]): ParsedFeature[] {\n return filePaths.map(file => {\n const document = parseDocument(fs.readFileSync(file, \"utf-8\"));\n return {\n file,\n name: document.feature?.name ?? \"\",\n scenarios: documentToScenarios(document, file),\n };\n });\n}\n\nexport function parseFeatureContent(\n content: string,\n filePath: string\n): ParsedScenario[] {\n return documentToScenarios(parseDocument(content), filePath);\n}\n\n/** Expand a parsed gherkin document into scenarios (outline rows included). */\nfunction documentToScenarios(\n gherkinDocument: messages.GherkinDocument,\n filePath: string\n): ParsedScenario[] {\n const feature = gherkinDocument.feature;\n if (!feature) return [];\n\n // Collect background steps at the feature level\n const featureBackground: messages.Step[] = [];\n const scenarios: ParsedScenario[] = [];\n const featureTags = tagNames(feature.tags);\n\n for (const child of feature.children) {\n if (child.background) {\n featureBackground.push(...child.background.steps);\n }\n\n if (child.scenario) {\n scenarios.push(\n ...expandScenario(\n child.scenario,\n featureBackground,\n filePath,\n featureTags\n )\n );\n }\n\n if (child.rule) {\n // Rules can have their own backgrounds and tags, both inherited by the\n // rule's scenarios.\n const ruleBackground: messages.Step[] = [...featureBackground];\n const ruleTags = [...featureTags, ...tagNames(child.rule.tags)];\n for (const ruleChild of child.rule.children) {\n if (ruleChild.background) {\n ruleBackground.push(...ruleChild.background.steps);\n }\n if (ruleChild.scenario) {\n scenarios.push(\n ...expandScenario(\n ruleChild.scenario,\n ruleBackground,\n filePath,\n ruleTags\n )\n );\n }\n }\n }\n }\n\n return scenarios;\n}\n\n/** Extract tag names (each keeping its leading `@`), deduped in order. */\nfunction tagNames(tags: readonly messages.Tag[] | undefined): string[] {\n return [...new Set((tags ?? []).map(t => t.name))];\n}\n\nfunction expandScenario(\n scenario: messages.Scenario,\n backgroundSteps: messages.Step[],\n filePath: string,\n inheritedTags: string[]\n): ParsedScenario[] {\n const scenarioTags = [...inheritedTags, ...tagNames(scenario.tags)];\n const hasExamples =\n scenario.examples.length > 0 &&\n scenario.examples.some(e => e.tableBody.length > 0);\n\n if (!hasExamples) {\n // Regular scenario\n const bgParsed = convertSteps(backgroundSteps);\n const scenarioParsed = convertSteps(scenario.steps);\n const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioParsed]);\n\n return [\n {\n name: scenario.name,\n file: filePath,\n line: scenario.location.line,\n steps: allSteps,\n tags: scenarioTags,\n },\n ];\n }\n\n // Scenario Outline — expand with each example row. Rows carry the outline's\n // base name so the runner can group them, plus the Examples-block tags.\n const results: ParsedScenario[] = [];\n for (const example of scenario.examples) {\n if (!example.tableHeader || example.tableBody.length === 0) continue;\n const headers = example.tableHeader.cells.map(c => c.value);\n const exampleTags = [...scenarioTags, ...tagNames(example.tags)];\n\n for (const row of example.tableBody) {\n const values = row.cells.map(c => c.value);\n const substitution: Record<string, string> = {};\n headers.forEach((h, i) => {\n substitution[h] = values[i];\n });\n\n const bgParsed = convertSteps(backgroundSteps);\n const scenarioSteps = convertSteps(scenario.steps).map(step => ({\n ...step,\n text: substituteExampleValues(step.text, substitution),\n }));\n const allSteps = resolveEffectiveKeywords([\n ...bgParsed,\n ...scenarioSteps,\n ]);\n\n results.push({\n name: headers.map((h, i) => `${h}=${values[i]}`).join(\", \"),\n file: filePath,\n line: row.location.line,\n steps: allSteps,\n tags: exampleTags,\n outline: { name: scenario.name },\n });\n }\n }\n\n return results;\n}\n\nfunction convertSteps(\n steps: readonly messages.Step[]\n): Omit<ParsedStep, \"effectiveKeyword\">[] {\n return steps.map(step => ({\n keyword: normalizeKeyword(step.keyword),\n text: step.text,\n line: step.location.line,\n // step.location.column points to the keyword start; shift past the\n // keyword (which includes a trailing space) so column points to the\n // start of the step text. This makes `column + text.length` produce\n // the correct end position for diagnostic ranges.\n column: (step.location.column ?? 1) + step.keyword.length,\n }));\n}\n\nfunction normalizeKeyword(keyword: string): GherkinKeyword {\n const trimmed = keyword.trim();\n // Gherkin keywords may include trailing space, e.g. \"Given \"\n if (trimmed === \"Given\") return \"Given\";\n if (trimmed === \"When\") return \"When\";\n if (trimmed === \"Then\") return \"Then\";\n if (trimmed === \"And\") return \"And\";\n if (trimmed === \"But\") return \"But\";\n // Fallback: treat as Given (shouldn't happen with valid Gherkin)\n return \"Given\";\n}\n\nfunction resolveEffectiveKeywords(\n steps: Omit<ParsedStep, \"effectiveKeyword\">[]\n): ParsedStep[] {\n let lastEffective: \"Given\" | \"When\" | \"Then\" = \"Given\";\n\n return steps.map(step => {\n let effectiveKeyword: \"Given\" | \"When\" | \"Then\";\n if (step.keyword === \"And\" || step.keyword === \"But\") {\n effectiveKeyword = lastEffective;\n } else {\n effectiveKeyword = step.keyword as \"Given\" | \"When\" | \"Then\";\n }\n lastEffective = effectiveKeyword;\n\n return {\n ...step,\n effectiveKeyword,\n };\n });\n}\n\nfunction substituteExampleValues(\n text: string,\n substitution: Record<string, string>\n): string {\n let result = text;\n for (const [key, value] of Object.entries(substitution)) {\n result = result.replace(new RegExp(`<${key}>`, \"g\"), value);\n }\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gherkinParser-CbBLr_uP.cjs","names":["path","_","path","fs","Parser","AstBuilder","messages","GherkinClassicTokenMatcher"],"sources":["../../src/sourceLocation.ts","../../src/world.ts","../../src/globFiles.ts","../../src/analyzer/gherkinParser.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Directory holding the library's own compiled code. In development this is the\n * `src/` tree; in the published package it's `dist/` (this module is bundled\n * into the shipped chunks). Stack frames under it are internal plumbing —\n * builders, the engine, the runner — and are hidden from users so that a\n * failure points at *their* step, not ours.\n */\nconst LIB_ROOT = path.dirname(fileURLToPath(import.meta.url));\n\nexport interface SourceFrame {\n file: string;\n line: number;\n column: number;\n}\n\n/**\n * A frame belongs to user code if it's an absolute path that is neither inside\n * the library nor inside any `node_modules` (assertion libs, etc.). That leaves\n * exactly the frames a user cares about: their own step definitions.\n */\nfunction isUserFile(file: string): boolean {\n return (\n path.isAbsolute(file) &&\n !file.startsWith(LIB_ROOT + path.sep) &&\n !file.includes(`${path.sep}node_modules${path.sep}`)\n );\n}\n\n/** Parse one `Error.stack` line into a frame, tolerating V8/Bun variations. */\nfunction parseFrame(frameLine: string): SourceFrame | undefined {\n const m = /:(\\d+):(\\d+)\\)?\\s*$/.exec(frameLine);\n if (!m) return undefined;\n let file = frameLine.slice(0, m.index);\n const paren = file.lastIndexOf(\"(\");\n if (paren !== -1) file = file.slice(paren + 1);\n file = file.trim().replace(/^at\\s+/, \"\");\n if (file.startsWith(\"file://\")) {\n try {\n file = fileURLToPath(file);\n } catch {\n return undefined;\n }\n }\n return { file, line: Number(m[1]), column: Number(m[2]) };\n}\n\n/** Every user-code frame in a stack, nearest-first, internals removed. */\nexport function userFrames(stack: string | undefined): SourceFrame[] {\n if (!stack) return [];\n const frames: SourceFrame[] = [];\n for (const line of stack.split(\"\\n\")) {\n const frame = parseFrame(line);\n if (frame && isUserFile(frame.file)) frames.push(frame);\n }\n return frames;\n}\n\n/**\n * Capture the user source location of the current call site — the first\n * user-code frame above this function. Called from `.step()` registration so\n * each step remembers where it was defined (Cucumber-style), independent of\n * where an error is later thrown. Returns an absolute `file:line:column`, or\n * `undefined` if no user frame is visible.\n */\nexport function captureDefinitionSite(): string | undefined {\n const frame = userFrames(new Error().stack)[0];\n return frame ? `${frame.file}:${frame.line}:${frame.column}` : undefined;\n}\n\n/** Render an absolute `file:line:column` relative to `cwd` for display. */\nexport function relativeLocation(location: string, cwd: string): string {\n const m = /^(.*):(\\d+):(\\d+)$/.exec(location);\n if (!m) return location;\n return `${path.relative(cwd, m[1])}:${m[2]}:${m[3]}`;\n}\n\n/** Render a frame relative to `cwd`. */\nexport function relativeFrame(frame: SourceFrame, cwd: string): string {\n return `${path.relative(cwd, frame.file)}:${frame.line}:${frame.column}`;\n}\n","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 {\n GherkinClassicTokenMatcher,\n Parser,\n AstBuilder,\n} from \"@cucumber/gherkin\";\nimport * as messages from \"@cucumber/messages\";\nimport { ParsedScenario, ParsedStep } from \"./types.js\";\n\ntype GherkinKeyword = \"Given\" | \"When\" | \"Then\" | \"And\" | \"But\";\n\nexport function parseFeatureFiles(filePaths: string[]): ParsedScenario[] {\n const scenarios: ParsedScenario[] = [];\n\n for (const filePath of filePaths) {\n const content = fs.readFileSync(filePath, \"utf-8\");\n const parsed = parseFeatureContent(content, filePath);\n scenarios.push(...parsed);\n }\n\n return scenarios;\n}\n\n/**\n * One parsed feature file: its `Feature:` title (empty if unnamed) plus every\n * scenario (outline rows expanded). {@link parseFeatureContent} exposes just the\n * scenarios; {@link parseFeatureCatalog} keeps the title too, for UIs that label\n * scenarios by their feature.\n */\nexport interface ParsedFeature {\n file: string;\n name: string;\n scenarios: ParsedScenario[];\n}\n\n/** Build a fresh gherkin parser and parse `content` into a document. */\nfunction parseDocument(content: string): messages.GherkinDocument {\n const builder = new AstBuilder(messages.IdGenerator.uuid());\n const matcher = new GherkinClassicTokenMatcher();\n return new Parser(builder, matcher).parse(content);\n}\n\n/**\n * Parse feature files into a catalog of `{ file, name, scenarios }`, one entry\n * per file (one gherkin parse each). Used by interactive mode to offer\n * feature/scenario names as typeahead choices labelled by their `Feature:` title.\n */\nexport function parseFeatureCatalog(filePaths: string[]): ParsedFeature[] {\n return filePaths.map(file => {\n const document = parseDocument(fs.readFileSync(file, \"utf-8\"));\n return {\n file,\n name: document.feature?.name ?? \"\",\n scenarios: documentToScenarios(document, file),\n };\n });\n}\n\nexport function parseFeatureContent(\n content: string,\n filePath: string\n): ParsedScenario[] {\n return documentToScenarios(parseDocument(content), filePath);\n}\n\n/** Expand a parsed gherkin document into scenarios (outline rows included). */\nfunction documentToScenarios(\n gherkinDocument: messages.GherkinDocument,\n filePath: string\n): ParsedScenario[] {\n const feature = gherkinDocument.feature;\n if (!feature) return [];\n\n // Collect background steps at the feature level\n const featureBackground: messages.Step[] = [];\n const scenarios: ParsedScenario[] = [];\n const featureTags = tagNames(feature.tags);\n\n for (const child of feature.children) {\n if (child.background) {\n featureBackground.push(...child.background.steps);\n }\n\n if (child.scenario) {\n scenarios.push(\n ...expandScenario(\n child.scenario,\n featureBackground,\n filePath,\n featureTags\n )\n );\n }\n\n if (child.rule) {\n // Rules can have their own backgrounds and tags, both inherited by the\n // rule's scenarios.\n const ruleBackground: messages.Step[] = [...featureBackground];\n const ruleTags = [...featureTags, ...tagNames(child.rule.tags)];\n for (const ruleChild of child.rule.children) {\n if (ruleChild.background) {\n ruleBackground.push(...ruleChild.background.steps);\n }\n if (ruleChild.scenario) {\n scenarios.push(\n ...expandScenario(\n ruleChild.scenario,\n ruleBackground,\n filePath,\n ruleTags\n )\n );\n }\n }\n }\n }\n\n return scenarios;\n}\n\n/** Extract tag names (each keeping its leading `@`), deduped in order. */\nfunction tagNames(tags: readonly messages.Tag[] | undefined): string[] {\n return [...new Set((tags ?? []).map(t => t.name))];\n}\n\nfunction expandScenario(\n scenario: messages.Scenario,\n backgroundSteps: messages.Step[],\n filePath: string,\n inheritedTags: string[]\n): ParsedScenario[] {\n const scenarioTags = [...inheritedTags, ...tagNames(scenario.tags)];\n const hasExamples =\n scenario.examples.length > 0 &&\n scenario.examples.some(e => e.tableBody.length > 0);\n\n if (!hasExamples) {\n // Regular scenario\n const bgParsed = convertSteps(backgroundSteps);\n const scenarioParsed = convertSteps(scenario.steps);\n const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioParsed]);\n\n return [\n {\n name: scenario.name,\n file: filePath,\n line: scenario.location.line,\n steps: allSteps,\n tags: scenarioTags,\n },\n ];\n }\n\n // Scenario Outline — expand with each example row. Rows carry the outline's\n // base name so the runner can group them, plus the Examples-block tags.\n const results: ParsedScenario[] = [];\n for (const example of scenario.examples) {\n if (!example.tableHeader || example.tableBody.length === 0) continue;\n const headers = example.tableHeader.cells.map(c => c.value);\n const exampleTags = [...scenarioTags, ...tagNames(example.tags)];\n\n for (const row of example.tableBody) {\n const values = row.cells.map(c => c.value);\n const substitution: Record<string, string> = {};\n headers.forEach((h, i) => {\n substitution[h] = values[i];\n });\n\n const bgParsed = convertSteps(backgroundSteps);\n const scenarioSteps = convertSteps(scenario.steps).map(step => ({\n ...step,\n text: substituteExampleValues(step.text, substitution),\n }));\n const allSteps = resolveEffectiveKeywords([\n ...bgParsed,\n ...scenarioSteps,\n ]);\n\n results.push({\n name: headers.map((h, i) => `${h}=${values[i]}`).join(\", \"),\n file: filePath,\n line: row.location.line,\n steps: allSteps,\n tags: exampleTags,\n outline: { name: scenario.name },\n });\n }\n }\n\n return results;\n}\n\nfunction convertSteps(\n steps: readonly messages.Step[]\n): Omit<ParsedStep, \"effectiveKeyword\">[] {\n return steps.map(step => ({\n keyword: normalizeKeyword(step.keyword),\n text: step.text,\n line: step.location.line,\n // step.location.column points to the keyword start; shift past the\n // keyword (which includes a trailing space) so column points to the\n // start of the step text. This makes `column + text.length` produce\n // the correct end position for diagnostic ranges.\n column: (step.location.column ?? 1) + step.keyword.length,\n }));\n}\n\nfunction normalizeKeyword(keyword: string): GherkinKeyword {\n const trimmed = keyword.trim();\n // Gherkin keywords may include trailing space, e.g. \"Given \"\n if (trimmed === \"Given\") return \"Given\";\n if (trimmed === \"When\") return \"When\";\n if (trimmed === \"Then\") return \"Then\";\n if (trimmed === \"And\") return \"And\";\n if (trimmed === \"But\") return \"But\";\n // Fallback: treat as Given (shouldn't happen with valid Gherkin)\n return \"Given\";\n}\n\nfunction resolveEffectiveKeywords(\n steps: Omit<ParsedStep, \"effectiveKeyword\">[]\n): ParsedStep[] {\n let lastEffective: \"Given\" | \"When\" | \"Then\" = \"Given\";\n\n return steps.map(step => {\n let effectiveKeyword: \"Given\" | \"When\" | \"Then\";\n if (step.keyword === \"And\" || step.keyword === \"But\") {\n effectiveKeyword = lastEffective;\n } else {\n effectiveKeyword = step.keyword as \"Given\" | \"When\" | \"Then\";\n }\n lastEffective = effectiveKeyword;\n\n return {\n ...step,\n effectiveKeyword,\n };\n });\n}\n\nfunction substituteExampleValues(\n text: string,\n substitution: Record<string, string>\n): string {\n let result = text;\n for (const [key, value] of Object.entries(substitution)) {\n result = result.replace(new RegExp(`<${key}>`, \"g\"), value);\n }\n return result;\n}\n"],"mappings":"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"}
@@ -1,9 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_gherkinParser = require("./gherkinParser-_ZgvELdy.cjs");
2
+ const require_gherkinParser = require("./gherkinParser-CbBLr_uP.cjs");
3
3
  const require_hooks = require("./hooks-Be0cjULN.cjs");
4
- const require_analyzer = require("./analyzer-DRudicCk.cjs");
5
4
  let lodash = require("lodash");
6
5
  lodash = require_gherkinParser.__toESM(lodash, 1);
6
+ let typescript = require("typescript");
7
+ typescript = require_gherkinParser.__toESM(typescript, 1);
7
8
  //#region src/builderTypeUtils.ts
8
9
  const isString = (statement) => typeof statement === "string";
9
10
  //#endregion
@@ -196,6 +197,396 @@ const thenStatement = () => (statement) => {
196
197
  };
197
198
  const thenBuilder = () => ({ statement: thenStatement() });
198
199
  //#endregion
200
+ //#region src/analyzer/stepExtractor.ts
201
+ const BUILDER_NAMES = {
202
+ givenBuilder: "given",
203
+ whenBuilder: "when",
204
+ thenBuilder: "then"
205
+ };
206
+ function extractStepDefinitions(filePaths, tsConfigPath) {
207
+ const fast = extractWithSources(filePaths.map(parseSourceFile).filter((s) => s !== null), null);
208
+ if (!fast.needsChecker) return fast.results;
209
+ const program = typescript.default.createProgram(filePaths, resolveCompilerOptions(tsConfigPath));
210
+ const checker = program.getTypeChecker();
211
+ return extractWithSources(filePaths.map((fp) => program.getSourceFile(fp)).filter((s) => s !== void 0), checker).results;
212
+ }
213
+ /** Parse a single file into an AST with no type resolution. */
214
+ function parseSourceFile(filePath) {
215
+ const text = typescript.default.sys.readFile(filePath);
216
+ if (text === void 0) return null;
217
+ const scriptKind = filePath.endsWith(".tsx") ? typescript.default.ScriptKind.TSX : typescript.default.ScriptKind.TS;
218
+ return typescript.default.createSourceFile(filePath, text, typescript.default.ScriptTarget.ESNext, true, scriptKind);
219
+ }
220
+ /** Resolve compiler options from tsconfig (fallback to sane defaults). */
221
+ function resolveCompilerOptions(tsConfigPath) {
222
+ const configPath = tsConfigPath ?? typescript.default.findConfigFile(process.cwd(), typescript.default.sys.fileExists);
223
+ let compilerOptions = {
224
+ target: typescript.default.ScriptTarget.ESNext,
225
+ module: typescript.default.ModuleKind.ESNext,
226
+ moduleResolution: typescript.default.ModuleResolutionKind.Node10,
227
+ strict: true,
228
+ esModuleInterop: true
229
+ };
230
+ if (configPath) {
231
+ const configFile = typescript.default.readConfigFile(configPath, typescript.default.sys.readFile);
232
+ if (!configFile.error) compilerOptions = typescript.default.parseJsonConfigFileContent(configFile.config, typescript.default.sys, configPath.replace(/[/\\][^/\\]+$/, "")).options;
233
+ }
234
+ compilerOptions.noEmit = true;
235
+ return compilerOptions;
236
+ }
237
+ function extractWithSources(sources, checker) {
238
+ const ctx = {
239
+ checker,
240
+ needsChecker: false
241
+ };
242
+ const results = [];
243
+ for (const sourceFile of sources) results.push(...extractFromSourceFile(sourceFile, ctx));
244
+ return {
245
+ results,
246
+ needsChecker: ctx.needsChecker
247
+ };
248
+ }
249
+ function extractFromSourceFile(sourceFile, ctx) {
250
+ const results = [];
251
+ function visit(node) {
252
+ if (typescript.default.isCallExpression(node) && typescript.default.isPropertyAccessExpression(node.expression) && node.expression.name.text === "step") {
253
+ const meta = extractFromRegisterCall(node, sourceFile, ctx);
254
+ if (meta) results.push(meta);
255
+ }
256
+ typescript.default.forEachChild(node, visit);
257
+ }
258
+ visit(sourceFile);
259
+ return results;
260
+ }
261
+ function extractFromRegisterCall(registerCall, sourceFile, ctx) {
262
+ const chain = collectCallChain(registerCall);
263
+ let stepType = null;
264
+ let expression = null;
265
+ let dependencies = {
266
+ given: {},
267
+ when: {},
268
+ then: {}
269
+ };
270
+ let produces = [];
271
+ for (const link of chain) {
272
+ const name = getCallName(link);
273
+ if (!name) continue;
274
+ if (name === "register") continue;
275
+ if (name === "step") {
276
+ produces = extractProducedKeys(link, ctx);
277
+ continue;
278
+ }
279
+ if (name === "dependencies") {
280
+ dependencies = extractDependencies(link);
281
+ continue;
282
+ }
283
+ if (name === "statement") {
284
+ expression = extractExpression(link);
285
+ continue;
286
+ }
287
+ if (BUILDER_NAMES[name]) {
288
+ stepType = BUILDER_NAMES[name];
289
+ continue;
290
+ }
291
+ }
292
+ if (!stepType || !expression) {
293
+ const reExport = resolveReExportedCall(chain, ctx);
294
+ if (reExport) {
295
+ if (!stepType) stepType = reExport.stepType;
296
+ if (!expression) expression = reExport.expression;
297
+ }
298
+ }
299
+ if (!stepType || !expression) return null;
300
+ const line = sourceFile.getLineAndCharacterOfPosition(registerCall.getStart()).line + 1;
301
+ return {
302
+ stepType,
303
+ expression,
304
+ dependencies,
305
+ produces,
306
+ sourceFile: sourceFile.fileName,
307
+ line
308
+ };
309
+ }
310
+ /**
311
+ * Collect all call expressions in the method chain, from register() back to the origin.
312
+ */
313
+ function collectCallChain(call) {
314
+ const chain = [call];
315
+ let current = call.expression;
316
+ if (typescript.default.isPropertyAccessExpression(current)) current = current.expression;
317
+ while (typescript.default.isCallExpression(current)) {
318
+ chain.push(current);
319
+ current = current.expression;
320
+ if (typescript.default.isPropertyAccessExpression(current)) current = current.expression;
321
+ }
322
+ return chain;
323
+ }
324
+ function getCallName(call) {
325
+ const expr = call.expression;
326
+ if (typescript.default.isPropertyAccessExpression(expr)) return expr.name.text;
327
+ if (typescript.default.isIdentifier(expr)) return expr.text;
328
+ return null;
329
+ }
330
+ function extractExpression(statementCall) {
331
+ const arg = statementCall.arguments[0];
332
+ if (!arg) return null;
333
+ if (typescript.default.isStringLiteral(arg)) return arg.text;
334
+ if (typescript.default.isArrowFunction(arg)) return extractExpressionFromArrowFunction(arg);
335
+ return null;
336
+ }
337
+ function extractExpressionFromArrowFunction(fn) {
338
+ const params = fn.parameters.map((p) => p.name.getText());
339
+ let body = fn.body;
340
+ if (typescript.default.isBlock(body)) {
341
+ const returnStmt = body.statements.find(typescript.default.isReturnStatement);
342
+ if (returnStmt?.expression) body = returnStmt.expression;
343
+ else return null;
344
+ }
345
+ if (typescript.default.isTemplateExpression(body)) return reconstructExpressionFromTemplate(body, params);
346
+ if (typescript.default.isNoSubstitutionTemplateLiteral(body)) return body.text;
347
+ return null;
348
+ }
349
+ function reconstructExpressionFromTemplate(template, paramNames) {
350
+ let result = template.head.text;
351
+ for (const span of template.templateSpans) {
352
+ if (typescript.default.isIdentifier(span.expression) && paramNames.includes(span.expression.text)) result += "{string}";
353
+ else result += "{string}";
354
+ result += span.literal.text;
355
+ }
356
+ return result;
357
+ }
358
+ function extractDependencies(depsCall) {
359
+ const deps = {
360
+ given: {},
361
+ when: {},
362
+ then: {}
363
+ };
364
+ const arg = depsCall.arguments[0];
365
+ if (!arg || !typescript.default.isObjectLiteralExpression(arg)) return deps;
366
+ for (const prop of arg.properties) {
367
+ if (!typescript.default.isPropertyAssignment(prop) || !typescript.default.isIdentifier(prop.name)) continue;
368
+ const phase = prop.name.text;
369
+ if (!deps[phase]) continue;
370
+ if (typescript.default.isObjectLiteralExpression(prop.initializer)) {
371
+ for (const innerProp of prop.initializer.properties) if (typescript.default.isPropertyAssignment(innerProp) && typescript.default.isIdentifier(innerProp.name) && typescript.default.isStringLiteral(innerProp.initializer)) {
372
+ const val = innerProp.initializer.text;
373
+ if (val === "required" || val === "optional") deps[phase][innerProp.name.text] = val;
374
+ }
375
+ }
376
+ }
377
+ return deps;
378
+ }
379
+ function extractProducedKeys(stepCall, ctx) {
380
+ const callback = stepCall.arguments[0];
381
+ if (!callback) return [];
382
+ if (typescript.default.isArrowFunction(callback) || typescript.default.isFunctionExpression(callback)) return extractProducedKeysFromCallback(callback, ctx);
383
+ return [];
384
+ }
385
+ function extractProducedKeysFromCallback(callback, ctx) {
386
+ const body = callback.body;
387
+ if (!typescript.default.isBlock(body)) return extractKeysFromExpression(body, ctx);
388
+ const keys = /* @__PURE__ */ new Set();
389
+ function visitReturn(node) {
390
+ if (typescript.default.isReturnStatement(node) && node.expression) for (const key of extractKeysFromExpression(node.expression, ctx)) keys.add(key);
391
+ typescript.default.forEachChild(node, visitReturn);
392
+ }
393
+ visitReturn(body);
394
+ return [...keys];
395
+ }
396
+ function extractKeysFromExpression(expr, ctx) {
397
+ while (typescript.default.isParenthesizedExpression(expr)) expr = expr.expression;
398
+ if (typescript.default.isObjectLiteralExpression(expr)) return expr.properties.filter((p) => typescript.default.isPropertyAssignment(p) || typescript.default.isShorthandPropertyAssignment(p)).map((p) => p.name.getText()).filter(Boolean);
399
+ if (!ctx.checker) {
400
+ ctx.needsChecker = true;
401
+ return [];
402
+ }
403
+ try {
404
+ return ctx.checker.getTypeAtLocation(expr).getProperties().map((p) => p.name).filter((n) => n !== "merge");
405
+ } catch {
406
+ return [];
407
+ }
408
+ }
409
+ function resolveReExportedCall(chain, ctx) {
410
+ const lastCall = chain[chain.length - 1];
411
+ if (!lastCall) return null;
412
+ const expr = lastCall.expression;
413
+ let identifier = null;
414
+ if (typescript.default.isIdentifier(expr)) identifier = expr;
415
+ else if (typescript.default.isPropertyAccessExpression(expr) && typescript.default.isIdentifier(expr.expression)) identifier = expr.expression;
416
+ if (!identifier) return null;
417
+ if (!ctx.checker) {
418
+ ctx.needsChecker = true;
419
+ return null;
420
+ }
421
+ const symbol = ctx.checker.getSymbolAtLocation(identifier);
422
+ if (!symbol) return null;
423
+ const decl = symbol.valueDeclaration;
424
+ if (!decl || !typescript.default.isVariableDeclaration(decl) || !decl.initializer) return null;
425
+ const init = decl.initializer;
426
+ if (typescript.default.isPropertyAccessExpression(init) && init.name.text === "statement") {
427
+ const callExpr = init.expression;
428
+ if (typescript.default.isCallExpression(callExpr)) {
429
+ const callee = callExpr.expression;
430
+ if (typescript.default.isIdentifier(callee) && BUILDER_NAMES[callee.text]) {
431
+ const expression = extractExpression(lastCall);
432
+ return {
433
+ stepType: BUILDER_NAMES[callee.text],
434
+ expression
435
+ };
436
+ }
437
+ }
438
+ }
439
+ return null;
440
+ }
441
+ //#endregion
442
+ //#region src/analyzer/stepMatcher.ts
443
+ function compileDefinitions(definitions) {
444
+ const compiled = [];
445
+ for (const def of definitions) try {
446
+ const placeholder = "###PLACEHOLDER###";
447
+ const regexStr = def.expression.replace(/\{[^}]+\}/g, placeholder).replace(/[.*+?^$()|[\]\\]/g, "\\$&").replace(new RegExp(placeholder, "g"), "(.+)");
448
+ compiled.push({
449
+ regex: new RegExp(`^${regexStr}$`, "i"),
450
+ definition: def
451
+ });
452
+ } catch {}
453
+ return compiled;
454
+ }
455
+ function matchScenarioSteps(scenario, definitions) {
456
+ const compiled = compileDefinitions(definitions);
457
+ return scenario.steps.map((step) => {
458
+ const matches = findMatches(step.text, step.effectiveKeyword, compiled);
459
+ return {
460
+ ...step,
461
+ definitions: matches
462
+ };
463
+ });
464
+ }
465
+ function findMatchingDefinitions(text, effectiveKeyword, definitions) {
466
+ return findMatches(text, effectiveKeyword, compileDefinitions(definitions));
467
+ }
468
+ function findMatches(text, effectiveKeyword, compiled) {
469
+ const expectedStepType = {
470
+ Given: "given",
471
+ When: "when",
472
+ Then: "then"
473
+ }[effectiveKeyword];
474
+ const typedMatches = [];
475
+ for (const { regex, definition } of compiled) {
476
+ if (definition.stepType !== expectedStepType) continue;
477
+ if (regex.test(text)) typedMatches.push(definition);
478
+ }
479
+ if (typedMatches.length > 0) return typedMatches;
480
+ const fallbackMatches = [];
481
+ for (const { regex, definition } of compiled) if (regex.test(text)) fallbackMatches.push(definition);
482
+ return fallbackMatches;
483
+ }
484
+ //#endregion
485
+ //#region src/analyzer/rules/index.ts
486
+ const defaultRules = [
487
+ {
488
+ name: "undefined-step",
489
+ check(scenario, matchedSteps) {
490
+ return matchedSteps.filter((step) => step.definitions.length === 0).map((step) => ({
491
+ file: scenario.file,
492
+ range: {
493
+ startLine: step.line,
494
+ startColumn: step.column,
495
+ endLine: step.line,
496
+ endColumn: step.column + step.text.length
497
+ },
498
+ severity: "error",
499
+ message: `Step "${step.text}" does not match any step definition`,
500
+ rule: "undefined-step",
501
+ source: "step-forge"
502
+ }));
503
+ }
504
+ },
505
+ {
506
+ name: "ambiguous-step",
507
+ check(scenario, matchedSteps) {
508
+ return matchedSteps.filter((step) => step.definitions.length > 1).map((step) => {
509
+ const locations = step.definitions.map((d) => `${d.sourceFile}:${d.line}`).join(", ");
510
+ return {
511
+ file: scenario.file,
512
+ range: {
513
+ startLine: step.line,
514
+ startColumn: step.column,
515
+ endLine: step.line,
516
+ endColumn: step.column + step.text.length
517
+ },
518
+ severity: "error",
519
+ message: `Step "${step.text}" matches multiple step definitions: ${locations}`,
520
+ rule: "ambiguous-step",
521
+ source: "step-forge"
522
+ };
523
+ });
524
+ }
525
+ },
526
+ {
527
+ name: "dependency-check",
528
+ check(scenario, matchedSteps) {
529
+ const diagnostics = [];
530
+ const produced = {
531
+ given: /* @__PURE__ */ new Set(),
532
+ when: /* @__PURE__ */ new Set(),
533
+ then: /* @__PURE__ */ new Set()
534
+ };
535
+ for (const step of matchedSteps) {
536
+ if (step.definitions.length !== 1) continue;
537
+ const { dependencies, produces, stepType } = step.definitions[0];
538
+ const missing = {};
539
+ for (const phase of [
540
+ "given",
541
+ "when",
542
+ "then"
543
+ ]) for (const [key, requirement] of Object.entries(dependencies[phase])) if (requirement === "required" && !produced[phase].has(key)) {
544
+ if (!missing[phase]) missing[phase] = [];
545
+ missing[phase].push(key);
546
+ }
547
+ if (Object.keys(missing).length > 0) {
548
+ const message = "Missing required dependencies:\n" + Object.entries(missing).map(([phase, keys]) => `${phase.charAt(0).toUpperCase() + phase.slice(1)}: ${keys.map((k) => `${phase}.${k}`).join(", ")}`).join("\n");
549
+ diagnostics.push({
550
+ file: scenario.file,
551
+ range: {
552
+ startLine: step.line,
553
+ startColumn: step.column,
554
+ endLine: step.line,
555
+ endColumn: step.column + step.text.length
556
+ },
557
+ severity: "error",
558
+ message,
559
+ rule: "dependency-check",
560
+ source: "step-forge"
561
+ });
562
+ }
563
+ for (const key of produces) produced[stepType].add(key);
564
+ }
565
+ return diagnostics;
566
+ }
567
+ }
568
+ ];
569
+ function runRules(rules, scenario, matchedSteps) {
570
+ return rules.flatMap((rule) => rule.check(scenario, matchedSteps));
571
+ }
572
+ //#endregion
573
+ //#region src/analyzer/index.ts
574
+ async function analyze(config, options) {
575
+ const rules = options?.rules ?? defaultRules;
576
+ const stepFilePaths = await require_gherkinParser.globFiles(config.stepFiles);
577
+ const featureFilePaths = await require_gherkinParser.globFiles(config.featureFiles);
578
+ if (stepFilePaths.length === 0) return [];
579
+ if (featureFilePaths.length === 0) return [];
580
+ const stepDefinitions = extractStepDefinitions(stepFilePaths, config.tsConfigPath);
581
+ const scenarios = require_gherkinParser.parseFeatureFiles(featureFilePaths);
582
+ const diagnostics = [];
583
+ for (const scenario of scenarios) {
584
+ const scenarioDiags = runRules(rules, scenario, matchScenarioSteps(scenario, stepDefinitions));
585
+ diagnostics.push(...scenarioDiags);
586
+ }
587
+ return diagnostics;
588
+ }
589
+ //#endregion
199
590
  //#region src/init.ts
200
591
  const createBuilders = () => {
201
592
  return {
@@ -275,13 +666,13 @@ function afterAll(fn) {
275
666
  //#endregion
276
667
  //#region src/index.ts
277
668
  const analyzer = {
278
- analyze: require_analyzer.analyze,
279
- extractStepDefinitions: require_analyzer.extractStepDefinitions,
669
+ analyze,
670
+ extractStepDefinitions,
280
671
  parseFeatureFiles: require_gherkinParser.parseFeatureFiles,
281
672
  parseFeatureContent: require_gherkinParser.parseFeatureContent,
282
- matchScenarioSteps: require_analyzer.matchScenarioSteps,
283
- findMatchingDefinitions: require_analyzer.findMatchingDefinitions,
284
- defaultRules: require_analyzer.defaultRules
673
+ matchScenarioSteps,
674
+ findMatchingDefinitions,
675
+ defaultRules
285
676
  };
286
677
  //#endregion
287
678
  exports.BasicWorld = require_gherkinParser.BasicWorld;