@step-forge/step-forge 0.0.25-alpha.3 → 0.0.25-alpha.4
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/dist/{analyzer-gYyAKIpM.js → analyzer-5H699Eg4.js} +106 -16
- package/dist/analyzer-5H699Eg4.js.map +1 -0
- package/dist/{analyzer-DIZPMxzN.cjs → analyzer-DRudicCk.cjs} +106 -16
- package/dist/analyzer-DRudicCk.cjs.map +1 -0
- package/dist/{analyzer-DnfK54Dw.js → analyzer-NsNpSFox.js} +106 -16
- package/dist/analyzer-NsNpSFox.js.map +1 -0
- package/dist/analyzer-cli.js +1 -1
- package/dist/analyzer.js +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/step-forge.cjs +1 -1
- package/dist/step-forge.js +1 -1
- package/package.json +1 -1
- package/dist/analyzer-DIZPMxzN.cjs.map +0 -1
- package/dist/analyzer-DnfK54Dw.js.map +0 -1
- package/dist/analyzer-gYyAKIpM.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer-DRudicCk.cjs","names":["ts","ParameterTypeRegistry","ParameterType","CucumberExpression","globFiles","parseFeatureFiles"],"sources":["../../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"],"sourcesContent":["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 * Built-in parsers exported by the library, mapped to the cucumber-expression\n * placeholder they register. A step's `.parsers([...])` drives which `{name}`\n * each variable becomes; without this the extractor defaulted every variable to\n * `{string}`, so `the deposit amount is {int}` was recorded as `{string}` and a\n * plain number like `100` looked unmatched. Custom parsers are resolved from\n * their `name` property (see {@link resolveCustomParserName}); anything we can't\n * resolve falls back to `string`.\n */\nconst BUILTIN_PARSER_PLACEHOLDERS: Record<string, string> = {\n intParser: \"int\",\n numberParser: \"float\",\n stringParser: \"string\",\n booleanParser: \"boolean\",\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 statementCall: ts.CallExpression | 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 // Placeholder names per variable, from `.parsers([...])` (positional).\n let placeholders: string[] | null = null;\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 === \"parsers\") {\n placeholders = resolveParserPlaceholders(link, sourceFile);\n continue;\n }\n\n if (name === \"statement\") {\n statementCall = 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 // Build the expression after the whole chain is seen, so `.parsers([...])` —\n // which the chain visits before `.statement(...)` here, but conceptually\n // annotates it — determines each variable's placeholder.\n if (statementCall)\n expression = extractExpression(statementCall, placeholders);\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(\n statementCall: ts.CallExpression,\n placeholders: string[] | null\n): 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, placeholders);\n }\n\n return null;\n}\n\nfunction extractExpressionFromArrowFunction(\n fn: ts.ArrowFunction,\n placeholders: string[] | null\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, placeholders);\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 placeholders: string[] | null\n): string {\n let result = template.head.text;\n\n for (const span of template.templateSpans) {\n // A variable interpolation becomes its declared parser placeholder (default\n // `{string}`); a non-variable interpolation falls back to `{string}`.\n let placeholder = \"string\";\n if (\n ts.isIdentifier(span.expression) &&\n paramNames.includes(span.expression.text)\n ) {\n const index = paramNames.indexOf(span.expression.text);\n placeholder = placeholders?.[index] ?? \"string\";\n }\n result += `{${placeholder}}`;\n result += span.literal.text;\n }\n\n return result;\n}\n\n/**\n * Resolve `.parsers([intParser, colorParser])` to the placeholder name for each\n * variable, positionally. Built-ins map via {@link BUILTIN_PARSER_PLACEHOLDERS};\n * a custom parser is resolved from its `name` property in the same source file;\n * anything unresolved defaults to `string`, matching the runtime default parser.\n */\nfunction resolveParserPlaceholders(\n parsersCall: ts.CallExpression,\n sourceFile: ts.SourceFile\n): string[] | null {\n const arg = parsersCall.arguments[0];\n if (!arg || !ts.isArrayLiteralExpression(arg)) return null;\n return arg.elements.map(element => {\n if (ts.isIdentifier(element)) {\n const builtin = BUILTIN_PARSER_PLACEHOLDERS[element.text];\n if (builtin) return builtin;\n const custom = resolveCustomParserName(element.text, sourceFile);\n if (custom) return custom;\n }\n return \"string\";\n });\n}\n\n/**\n * Find `const <identifier> = { name: \"...\", ... }` in `sourceFile` and return\n * its `name` — the cucumber placeholder a custom `Parser` registers. Scoped to\n * the file, so a parser imported from elsewhere won't resolve (it falls back to\n * `string`); the common in-file `const colorParser: Parser<Color> = {...}` does.\n */\nfunction resolveCustomParserName(\n identifier: string,\n sourceFile: ts.SourceFile\n): string | null {\n let found: string | null = null;\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.name.text === identifier &&\n node.initializer &&\n ts.isObjectLiteralExpression(node.initializer)\n ) {\n for (const prop of node.initializer.properties) {\n if (\n ts.isPropertyAssignment(prop) &&\n ts.isIdentifier(prop.name) &&\n prop.name.text === \"name\" &&\n ts.isStringLiteralLike(prop.initializer)\n ) {\n found = prop.initializer.text;\n return;\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return found;\n}\n\nfunction extractDependencies(\n depsCall: ts.CallExpression\n): StepDefinitionMeta[\"dependencies\"] {\n const deps: StepDefinitionMeta[\"dependencies\"] = {\n given: {},\n when: {},\n then: {},\n };\n\n const arg = depsCall.arguments[0];\n if (!arg || !ts.isObjectLiteralExpression(arg)) return deps;\n\n for (const prop of arg.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n\n const phase = prop.name.text as \"given\" | \"when\" | \"then\";\n if (!deps[phase]) continue;\n\n if (ts.isObjectLiteralExpression(prop.initializer)) {\n for (const innerProp of prop.initializer.properties) {\n if (\n ts.isPropertyAssignment(innerProp) &&\n ts.isIdentifier(innerProp.name) &&\n ts.isStringLiteral(innerProp.initializer)\n ) {\n const val = innerProp.initializer.text;\n if (val === \"required\" || val === \"optional\") {\n deps[phase][innerProp.name.text] = val;\n }\n }\n }\n }\n }\n\n return deps;\n}\n\nfunction extractProducedKeys(\n stepCall: ts.CallExpression,\n ctx: ExtractCtx\n): string[] {\n const callback = stepCall.arguments[0];\n if (!callback) return [];\n\n // Try to get the return type of the callback by analyzing its body\n if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {\n return extractProducedKeysFromCallback(callback, ctx);\n }\n\n return [];\n}\n\nfunction extractProducedKeysFromCallback(\n callback: ts.ArrowFunction | ts.FunctionExpression,\n ctx: ExtractCtx\n): string[] {\n const body = callback.body;\n\n // Concise arrow: () => ({ key: value })\n if (!ts.isBlock(body)) {\n return extractKeysFromExpression(body, ctx);\n }\n\n // Block body: look at return statements\n const keys = new Set<string>();\n function visitReturn(node: ts.Node) {\n if (ts.isReturnStatement(node) && node.expression) {\n for (const key of extractKeysFromExpression(node.expression, ctx)) {\n keys.add(key);\n }\n }\n ts.forEachChild(node, visitReturn);\n }\n visitReturn(body);\n return [...keys];\n}\n\nfunction extractKeysFromExpression(\n expr: ts.Expression,\n ctx: ExtractCtx\n): string[] {\n // Unwrap parenthesized expressions\n while (ts.isParenthesizedExpression(expr)) {\n expr = expr.expression;\n }\n\n // Object literal: { user: ..., token: ... }\n if (ts.isObjectLiteralExpression(expr)) {\n return expr.properties\n .filter(\n (p): p is ts.PropertyAssignment | ts.ShorthandPropertyAssignment =>\n ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)\n )\n .map(p => p.name.getText())\n .filter(Boolean);\n }\n\n // Non-literal return (a variable, a spread-only object, a function call): the\n // keys can only come from the type checker. On the parse-only pass we have\n // none — flag it so the caller retries with a real Program.\n if (!ctx.checker) {\n ctx.needsChecker = true;\n return [];\n }\n try {\n const type = ctx.checker.getTypeAtLocation(expr);\n return type\n .getProperties()\n .map(p => p.name)\n .filter(n => n !== \"merge\");\n } catch {\n return [];\n }\n}\n\ninterface ReExportResult {\n stepType: StepType;\n expression: string | null;\n}\n\nfunction resolveReExportedCall(\n chain: ts.CallExpression[],\n ctx: ExtractCtx\n): ReExportResult | null {\n // Look for the pattern: Variable(\"...\")... where Variable was assigned from builderType().statement\n // The last call in the chain (furthest from register) should be the variable call\n\n const lastCall = chain[chain.length - 1];\n if (!lastCall) return null;\n\n const expr = lastCall.expression;\n\n // If it's an identifier (like \"Given\", \"When\", \"Then\"), trace its declaration\n let identifier: ts.Identifier | null = null;\n if (ts.isIdentifier(expr)) {\n identifier = expr;\n } else if (\n ts.isPropertyAccessExpression(expr) &&\n ts.isIdentifier(expr.expression)\n ) {\n identifier = expr.expression;\n }\n\n if (!identifier) return null;\n\n // Tracing the re-exported builder's declaration needs symbol resolution,\n // which only a real Program provides. Flag for the checked retry.\n if (!ctx.checker) {\n ctx.needsChecker = true;\n return null;\n }\n\n const symbol = ctx.checker.getSymbolAtLocation(identifier);\n if (!symbol) return null;\n\n const decl = symbol.valueDeclaration;\n if (!decl || !ts.isVariableDeclaration(decl) || !decl.initializer)\n return null;\n\n // Check if initializer is builderType<T>().statement\n const init = decl.initializer;\n\n // Pattern: givenBuilder<T>().statement (PropertyAccessExpression)\n if (ts.isPropertyAccessExpression(init) && init.name.text === \"statement\") {\n const callExpr = init.expression;\n if (ts.isCallExpression(callExpr)) {\n const callee = callExpr.expression;\n if (ts.isIdentifier(callee) && BUILDER_NAMES[callee.text]) {\n // The lastCall IS the statement call — extract expression from it.\n // The re-exported-builder pattern carries no `.parsers(...)`, so\n // variables fall back to the default `{string}` placeholder.\n const expression = extractExpression(lastCall, null);\n return {\n stepType: BUILDER_NAMES[callee.text],\n expression,\n };\n }\n }\n }\n\n return null;\n}\n","import {\n CucumberExpression,\n ParameterType,\n ParameterTypeRegistry,\n} from \"@cucumber/cucumber-expressions\";\nimport { MatchedStep, ParsedScenario, StepDefinitionMeta } from \"./types.js\";\n\ninterface CompiledPattern {\n expression: CucumberExpression;\n definition: StepDefinitionMeta;\n}\n\n/** `{name}` tokens in a cucumber expression (empty name is the anonymous `{}`). */\nconst PARAM_TOKEN = /\\{([^}]*)\\}/g;\n\n/**\n * Compile each definition's cucumber expression with the **same** engine the\n * runtime uses (`@cucumber/cucumber-expressions`), so the analyzer and the\n * runtime agree on what a step matches.\n *\n * A hand-rolled `{param}` → `(.+)` regex used to stand in here, but it treated\n * cucumber-expression alternative (`a/b`) and optional (`text(s)`) syntax as\n * literal characters — so any step definition using them (e.g.\n * `there should be {int} error/errors`) never matched, and every such step was\n * wrongly reported as an undefined step regardless of the real state.\n *\n * Built-in placeholders (`{int}`, `{float}`, `{string}`, `{word}`) keep their\n * real, strict regexes so the analyzer disambiguates the way the runtime does\n * (e.g. `no` isn't an `{int}`). The analyzer has only the expression string, not\n * a custom parser's regexp, so each custom `{name}` is registered as a permissive\n * parameter type (any value) — lenient about the value's exact shape, but still\n * anchored by the surrounding literal text.\n */\nfunction compileDefinitions(\n definitions: StepDefinitionMeta[]\n): CompiledPattern[] {\n const compiled: CompiledPattern[] = [];\n for (const def of definitions) {\n try {\n const registry = new ParameterTypeRegistry();\n for (const [, name] of def.expression.matchAll(PARAM_TOKEN)) {\n if (!name || registry.lookupByTypeName(name)) continue;\n registry.defineParameterType(\n new ParameterType(name, /.+/, null, (value: string) => value)\n );\n }\n compiled.push({\n expression: new CucumberExpression(def.expression, registry),\n definition: def,\n });\n } catch {\n // Skip definitions whose expression can't be compiled.\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 { expression, definition } of compiled) {\n if (definition.stepType !== expectedStepType) continue;\n if (expression.match(text) != null) 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 { expression, definition } of compiled) {\n if (expression.match(text) != null) 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,5 +1,6 @@
|
|
|
1
1
|
import { i as globFiles, n as parseFeatureContent, r as parseFeatureFiles } from "./gherkinParser-i-Q7M6mi.js";
|
|
2
2
|
import ts from "typescript";
|
|
3
|
+
import { CucumberExpression, ParameterType, ParameterTypeRegistry } from "@cucumber/cucumber-expressions";
|
|
3
4
|
//#region \0rolldown/runtime.js
|
|
4
5
|
var __defProp = Object.defineProperty;
|
|
5
6
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -18,6 +19,21 @@ const BUILDER_NAMES = {
|
|
|
18
19
|
whenBuilder: "when",
|
|
19
20
|
thenBuilder: "then"
|
|
20
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* Built-in parsers exported by the library, mapped to the cucumber-expression
|
|
24
|
+
* placeholder they register. A step's `.parsers([...])` drives which `{name}`
|
|
25
|
+
* each variable becomes; without this the extractor defaulted every variable to
|
|
26
|
+
* `{string}`, so `the deposit amount is {int}` was recorded as `{string}` and a
|
|
27
|
+
* plain number like `100` looked unmatched. Custom parsers are resolved from
|
|
28
|
+
* their `name` property (see {@link resolveCustomParserName}); anything we can't
|
|
29
|
+
* resolve falls back to `string`.
|
|
30
|
+
*/
|
|
31
|
+
const BUILTIN_PARSER_PLACEHOLDERS = {
|
|
32
|
+
intParser: "int",
|
|
33
|
+
numberParser: "float",
|
|
34
|
+
stringParser: "string",
|
|
35
|
+
booleanParser: "boolean"
|
|
36
|
+
};
|
|
21
37
|
function extractStepDefinitions(filePaths, tsConfigPath) {
|
|
22
38
|
const fast = extractWithSources(filePaths.map(parseSourceFile).filter((s) => s !== null), null);
|
|
23
39
|
if (!fast.needsChecker) return fast.results;
|
|
@@ -76,6 +92,7 @@ function extractFromSourceFile(sourceFile, ctx) {
|
|
|
76
92
|
function extractFromRegisterCall(registerCall, sourceFile, ctx) {
|
|
77
93
|
const chain = collectCallChain(registerCall);
|
|
78
94
|
let stepType = null;
|
|
95
|
+
let statementCall = null;
|
|
79
96
|
let expression = null;
|
|
80
97
|
let dependencies = {
|
|
81
98
|
given: {},
|
|
@@ -83,6 +100,7 @@ function extractFromRegisterCall(registerCall, sourceFile, ctx) {
|
|
|
83
100
|
then: {}
|
|
84
101
|
};
|
|
85
102
|
let produces = [];
|
|
103
|
+
let placeholders = null;
|
|
86
104
|
for (const link of chain) {
|
|
87
105
|
const name = getCallName(link);
|
|
88
106
|
if (!name) continue;
|
|
@@ -95,8 +113,12 @@ function extractFromRegisterCall(registerCall, sourceFile, ctx) {
|
|
|
95
113
|
dependencies = extractDependencies(link);
|
|
96
114
|
continue;
|
|
97
115
|
}
|
|
116
|
+
if (name === "parsers") {
|
|
117
|
+
placeholders = resolveParserPlaceholders(link, sourceFile);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
98
120
|
if (name === "statement") {
|
|
99
|
-
|
|
121
|
+
statementCall = link;
|
|
100
122
|
continue;
|
|
101
123
|
}
|
|
102
124
|
if (BUILDER_NAMES[name]) {
|
|
@@ -104,6 +126,7 @@ function extractFromRegisterCall(registerCall, sourceFile, ctx) {
|
|
|
104
126
|
continue;
|
|
105
127
|
}
|
|
106
128
|
}
|
|
129
|
+
if (statementCall) expression = extractExpression(statementCall, placeholders);
|
|
107
130
|
if (!stepType || !expression) {
|
|
108
131
|
const reExport = resolveReExportedCall(chain, ctx);
|
|
109
132
|
if (reExport) {
|
|
@@ -143,14 +166,14 @@ function getCallName(call) {
|
|
|
143
166
|
if (ts.isIdentifier(expr)) return expr.text;
|
|
144
167
|
return null;
|
|
145
168
|
}
|
|
146
|
-
function extractExpression(statementCall) {
|
|
169
|
+
function extractExpression(statementCall, placeholders) {
|
|
147
170
|
const arg = statementCall.arguments[0];
|
|
148
171
|
if (!arg) return null;
|
|
149
172
|
if (ts.isStringLiteral(arg)) return arg.text;
|
|
150
|
-
if (ts.isArrowFunction(arg)) return extractExpressionFromArrowFunction(arg);
|
|
173
|
+
if (ts.isArrowFunction(arg)) return extractExpressionFromArrowFunction(arg, placeholders);
|
|
151
174
|
return null;
|
|
152
175
|
}
|
|
153
|
-
function extractExpressionFromArrowFunction(fn) {
|
|
176
|
+
function extractExpressionFromArrowFunction(fn, placeholders) {
|
|
154
177
|
const params = fn.parameters.map((p) => p.name.getText());
|
|
155
178
|
let body = fn.body;
|
|
156
179
|
if (ts.isBlock(body)) {
|
|
@@ -158,19 +181,63 @@ function extractExpressionFromArrowFunction(fn) {
|
|
|
158
181
|
if (returnStmt?.expression) body = returnStmt.expression;
|
|
159
182
|
else return null;
|
|
160
183
|
}
|
|
161
|
-
if (ts.isTemplateExpression(body)) return reconstructExpressionFromTemplate(body, params);
|
|
184
|
+
if (ts.isTemplateExpression(body)) return reconstructExpressionFromTemplate(body, params, placeholders);
|
|
162
185
|
if (ts.isNoSubstitutionTemplateLiteral(body)) return body.text;
|
|
163
186
|
return null;
|
|
164
187
|
}
|
|
165
|
-
function reconstructExpressionFromTemplate(template, paramNames) {
|
|
188
|
+
function reconstructExpressionFromTemplate(template, paramNames, placeholders) {
|
|
166
189
|
let result = template.head.text;
|
|
167
190
|
for (const span of template.templateSpans) {
|
|
168
|
-
|
|
169
|
-
|
|
191
|
+
let placeholder = "string";
|
|
192
|
+
if (ts.isIdentifier(span.expression) && paramNames.includes(span.expression.text)) {
|
|
193
|
+
const index = paramNames.indexOf(span.expression.text);
|
|
194
|
+
placeholder = placeholders?.[index] ?? "string";
|
|
195
|
+
}
|
|
196
|
+
result += `{${placeholder}}`;
|
|
170
197
|
result += span.literal.text;
|
|
171
198
|
}
|
|
172
199
|
return result;
|
|
173
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Resolve `.parsers([intParser, colorParser])` to the placeholder name for each
|
|
203
|
+
* variable, positionally. Built-ins map via {@link BUILTIN_PARSER_PLACEHOLDERS};
|
|
204
|
+
* a custom parser is resolved from its `name` property in the same source file;
|
|
205
|
+
* anything unresolved defaults to `string`, matching the runtime default parser.
|
|
206
|
+
*/
|
|
207
|
+
function resolveParserPlaceholders(parsersCall, sourceFile) {
|
|
208
|
+
const arg = parsersCall.arguments[0];
|
|
209
|
+
if (!arg || !ts.isArrayLiteralExpression(arg)) return null;
|
|
210
|
+
return arg.elements.map((element) => {
|
|
211
|
+
if (ts.isIdentifier(element)) {
|
|
212
|
+
const builtin = BUILTIN_PARSER_PLACEHOLDERS[element.text];
|
|
213
|
+
if (builtin) return builtin;
|
|
214
|
+
const custom = resolveCustomParserName(element.text, sourceFile);
|
|
215
|
+
if (custom) return custom;
|
|
216
|
+
}
|
|
217
|
+
return "string";
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Find `const <identifier> = { name: "...", ... }` in `sourceFile` and return
|
|
222
|
+
* its `name` — the cucumber placeholder a custom `Parser` registers. Scoped to
|
|
223
|
+
* the file, so a parser imported from elsewhere won't resolve (it falls back to
|
|
224
|
+
* `string`); the common in-file `const colorParser: Parser<Color> = {...}` does.
|
|
225
|
+
*/
|
|
226
|
+
function resolveCustomParserName(identifier, sourceFile) {
|
|
227
|
+
let found = null;
|
|
228
|
+
const visit = (node) => {
|
|
229
|
+
if (found) return;
|
|
230
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === identifier && node.initializer && ts.isObjectLiteralExpression(node.initializer)) {
|
|
231
|
+
for (const prop of node.initializer.properties) if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === "name" && ts.isStringLiteralLike(prop.initializer)) {
|
|
232
|
+
found = prop.initializer.text;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
ts.forEachChild(node, visit);
|
|
237
|
+
};
|
|
238
|
+
visit(sourceFile);
|
|
239
|
+
return found;
|
|
240
|
+
}
|
|
174
241
|
function extractDependencies(depsCall) {
|
|
175
242
|
const deps = {
|
|
176
243
|
given: {},
|
|
@@ -244,7 +311,7 @@ function resolveReExportedCall(chain, ctx) {
|
|
|
244
311
|
if (ts.isCallExpression(callExpr)) {
|
|
245
312
|
const callee = callExpr.expression;
|
|
246
313
|
if (ts.isIdentifier(callee) && BUILDER_NAMES[callee.text]) {
|
|
247
|
-
const expression = extractExpression(lastCall);
|
|
314
|
+
const expression = extractExpression(lastCall, null);
|
|
248
315
|
return {
|
|
249
316
|
stepType: BUILDER_NAMES[callee.text],
|
|
250
317
|
expression
|
|
@@ -256,13 +323,36 @@ function resolveReExportedCall(chain, ctx) {
|
|
|
256
323
|
}
|
|
257
324
|
//#endregion
|
|
258
325
|
//#region src/analyzer/stepMatcher.ts
|
|
326
|
+
/** `{name}` tokens in a cucumber expression (empty name is the anonymous `{}`). */
|
|
327
|
+
const PARAM_TOKEN = /\{([^}]*)\}/g;
|
|
328
|
+
/**
|
|
329
|
+
* Compile each definition's cucumber expression with the **same** engine the
|
|
330
|
+
* runtime uses (`@cucumber/cucumber-expressions`), so the analyzer and the
|
|
331
|
+
* runtime agree on what a step matches.
|
|
332
|
+
*
|
|
333
|
+
* A hand-rolled `{param}` → `(.+)` regex used to stand in here, but it treated
|
|
334
|
+
* cucumber-expression alternative (`a/b`) and optional (`text(s)`) syntax as
|
|
335
|
+
* literal characters — so any step definition using them (e.g.
|
|
336
|
+
* `there should be {int} error/errors`) never matched, and every such step was
|
|
337
|
+
* wrongly reported as an undefined step regardless of the real state.
|
|
338
|
+
*
|
|
339
|
+
* Built-in placeholders (`{int}`, `{float}`, `{string}`, `{word}`) keep their
|
|
340
|
+
* real, strict regexes so the analyzer disambiguates the way the runtime does
|
|
341
|
+
* (e.g. `no` isn't an `{int}`). The analyzer has only the expression string, not
|
|
342
|
+
* a custom parser's regexp, so each custom `{name}` is registered as a permissive
|
|
343
|
+
* parameter type (any value) — lenient about the value's exact shape, but still
|
|
344
|
+
* anchored by the surrounding literal text.
|
|
345
|
+
*/
|
|
259
346
|
function compileDefinitions(definitions) {
|
|
260
347
|
const compiled = [];
|
|
261
348
|
for (const def of definitions) try {
|
|
262
|
-
const
|
|
263
|
-
const
|
|
349
|
+
const registry = new ParameterTypeRegistry();
|
|
350
|
+
for (const [, name] of def.expression.matchAll(PARAM_TOKEN)) {
|
|
351
|
+
if (!name || registry.lookupByTypeName(name)) continue;
|
|
352
|
+
registry.defineParameterType(new ParameterType(name, /.+/, null, (value) => value));
|
|
353
|
+
}
|
|
264
354
|
compiled.push({
|
|
265
|
-
|
|
355
|
+
expression: new CucumberExpression(def.expression, registry),
|
|
266
356
|
definition: def
|
|
267
357
|
});
|
|
268
358
|
} catch {}
|
|
@@ -288,13 +378,13 @@ function findMatches(text, effectiveKeyword, compiled) {
|
|
|
288
378
|
Then: "then"
|
|
289
379
|
}[effectiveKeyword];
|
|
290
380
|
const typedMatches = [];
|
|
291
|
-
for (const {
|
|
381
|
+
for (const { expression, definition } of compiled) {
|
|
292
382
|
if (definition.stepType !== expectedStepType) continue;
|
|
293
|
-
if (
|
|
383
|
+
if (expression.match(text) != null) typedMatches.push(definition);
|
|
294
384
|
}
|
|
295
385
|
if (typedMatches.length > 0) return typedMatches;
|
|
296
386
|
const fallbackMatches = [];
|
|
297
|
-
for (const {
|
|
387
|
+
for (const { expression, definition } of compiled) if (expression.match(text) != null) fallbackMatches.push(definition);
|
|
298
388
|
return fallbackMatches;
|
|
299
389
|
}
|
|
300
390
|
//#endregion
|
|
@@ -414,4 +504,4 @@ async function analyze(config, options) {
|
|
|
414
504
|
//#endregion
|
|
415
505
|
export { matchScenarioSteps as a, findMatchingDefinitions as i, analyzer_exports as n, extractStepDefinitions as o, defaultRules as r, analyze as t };
|
|
416
506
|
|
|
417
|
-
//# sourceMappingURL=analyzer-
|
|
507
|
+
//# sourceMappingURL=analyzer-NsNpSFox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer-NsNpSFox.js","names":[],"sources":["../../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"],"sourcesContent":["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 * Built-in parsers exported by the library, mapped to the cucumber-expression\n * placeholder they register. A step's `.parsers([...])` drives which `{name}`\n * each variable becomes; without this the extractor defaulted every variable to\n * `{string}`, so `the deposit amount is {int}` was recorded as `{string}` and a\n * plain number like `100` looked unmatched. Custom parsers are resolved from\n * their `name` property (see {@link resolveCustomParserName}); anything we can't\n * resolve falls back to `string`.\n */\nconst BUILTIN_PARSER_PLACEHOLDERS: Record<string, string> = {\n intParser: \"int\",\n numberParser: \"float\",\n stringParser: \"string\",\n booleanParser: \"boolean\",\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 statementCall: ts.CallExpression | 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 // Placeholder names per variable, from `.parsers([...])` (positional).\n let placeholders: string[] | null = null;\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 === \"parsers\") {\n placeholders = resolveParserPlaceholders(link, sourceFile);\n continue;\n }\n\n if (name === \"statement\") {\n statementCall = 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 // Build the expression after the whole chain is seen, so `.parsers([...])` —\n // which the chain visits before `.statement(...)` here, but conceptually\n // annotates it — determines each variable's placeholder.\n if (statementCall)\n expression = extractExpression(statementCall, placeholders);\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(\n statementCall: ts.CallExpression,\n placeholders: string[] | null\n): 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, placeholders);\n }\n\n return null;\n}\n\nfunction extractExpressionFromArrowFunction(\n fn: ts.ArrowFunction,\n placeholders: string[] | null\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, placeholders);\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 placeholders: string[] | null\n): string {\n let result = template.head.text;\n\n for (const span of template.templateSpans) {\n // A variable interpolation becomes its declared parser placeholder (default\n // `{string}`); a non-variable interpolation falls back to `{string}`.\n let placeholder = \"string\";\n if (\n ts.isIdentifier(span.expression) &&\n paramNames.includes(span.expression.text)\n ) {\n const index = paramNames.indexOf(span.expression.text);\n placeholder = placeholders?.[index] ?? \"string\";\n }\n result += `{${placeholder}}`;\n result += span.literal.text;\n }\n\n return result;\n}\n\n/**\n * Resolve `.parsers([intParser, colorParser])` to the placeholder name for each\n * variable, positionally. Built-ins map via {@link BUILTIN_PARSER_PLACEHOLDERS};\n * a custom parser is resolved from its `name` property in the same source file;\n * anything unresolved defaults to `string`, matching the runtime default parser.\n */\nfunction resolveParserPlaceholders(\n parsersCall: ts.CallExpression,\n sourceFile: ts.SourceFile\n): string[] | null {\n const arg = parsersCall.arguments[0];\n if (!arg || !ts.isArrayLiteralExpression(arg)) return null;\n return arg.elements.map(element => {\n if (ts.isIdentifier(element)) {\n const builtin = BUILTIN_PARSER_PLACEHOLDERS[element.text];\n if (builtin) return builtin;\n const custom = resolveCustomParserName(element.text, sourceFile);\n if (custom) return custom;\n }\n return \"string\";\n });\n}\n\n/**\n * Find `const <identifier> = { name: \"...\", ... }` in `sourceFile` and return\n * its `name` — the cucumber placeholder a custom `Parser` registers. Scoped to\n * the file, so a parser imported from elsewhere won't resolve (it falls back to\n * `string`); the common in-file `const colorParser: Parser<Color> = {...}` does.\n */\nfunction resolveCustomParserName(\n identifier: string,\n sourceFile: ts.SourceFile\n): string | null {\n let found: string | null = null;\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.name.text === identifier &&\n node.initializer &&\n ts.isObjectLiteralExpression(node.initializer)\n ) {\n for (const prop of node.initializer.properties) {\n if (\n ts.isPropertyAssignment(prop) &&\n ts.isIdentifier(prop.name) &&\n prop.name.text === \"name\" &&\n ts.isStringLiteralLike(prop.initializer)\n ) {\n found = prop.initializer.text;\n return;\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return found;\n}\n\nfunction extractDependencies(\n depsCall: ts.CallExpression\n): StepDefinitionMeta[\"dependencies\"] {\n const deps: StepDefinitionMeta[\"dependencies\"] = {\n given: {},\n when: {},\n then: {},\n };\n\n const arg = depsCall.arguments[0];\n if (!arg || !ts.isObjectLiteralExpression(arg)) return deps;\n\n for (const prop of arg.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n\n const phase = prop.name.text as \"given\" | \"when\" | \"then\";\n if (!deps[phase]) continue;\n\n if (ts.isObjectLiteralExpression(prop.initializer)) {\n for (const innerProp of prop.initializer.properties) {\n if (\n ts.isPropertyAssignment(innerProp) &&\n ts.isIdentifier(innerProp.name) &&\n ts.isStringLiteral(innerProp.initializer)\n ) {\n const val = innerProp.initializer.text;\n if (val === \"required\" || val === \"optional\") {\n deps[phase][innerProp.name.text] = val;\n }\n }\n }\n }\n }\n\n return deps;\n}\n\nfunction extractProducedKeys(\n stepCall: ts.CallExpression,\n ctx: ExtractCtx\n): string[] {\n const callback = stepCall.arguments[0];\n if (!callback) return [];\n\n // Try to get the return type of the callback by analyzing its body\n if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {\n return extractProducedKeysFromCallback(callback, ctx);\n }\n\n return [];\n}\n\nfunction extractProducedKeysFromCallback(\n callback: ts.ArrowFunction | ts.FunctionExpression,\n ctx: ExtractCtx\n): string[] {\n const body = callback.body;\n\n // Concise arrow: () => ({ key: value })\n if (!ts.isBlock(body)) {\n return extractKeysFromExpression(body, ctx);\n }\n\n // Block body: look at return statements\n const keys = new Set<string>();\n function visitReturn(node: ts.Node) {\n if (ts.isReturnStatement(node) && node.expression) {\n for (const key of extractKeysFromExpression(node.expression, ctx)) {\n keys.add(key);\n }\n }\n ts.forEachChild(node, visitReturn);\n }\n visitReturn(body);\n return [...keys];\n}\n\nfunction extractKeysFromExpression(\n expr: ts.Expression,\n ctx: ExtractCtx\n): string[] {\n // Unwrap parenthesized expressions\n while (ts.isParenthesizedExpression(expr)) {\n expr = expr.expression;\n }\n\n // Object literal: { user: ..., token: ... }\n if (ts.isObjectLiteralExpression(expr)) {\n return expr.properties\n .filter(\n (p): p is ts.PropertyAssignment | ts.ShorthandPropertyAssignment =>\n ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)\n )\n .map(p => p.name.getText())\n .filter(Boolean);\n }\n\n // Non-literal return (a variable, a spread-only object, a function call): the\n // keys can only come from the type checker. On the parse-only pass we have\n // none — flag it so the caller retries with a real Program.\n if (!ctx.checker) {\n ctx.needsChecker = true;\n return [];\n }\n try {\n const type = ctx.checker.getTypeAtLocation(expr);\n return type\n .getProperties()\n .map(p => p.name)\n .filter(n => n !== \"merge\");\n } catch {\n return [];\n }\n}\n\ninterface ReExportResult {\n stepType: StepType;\n expression: string | null;\n}\n\nfunction resolveReExportedCall(\n chain: ts.CallExpression[],\n ctx: ExtractCtx\n): ReExportResult | null {\n // Look for the pattern: Variable(\"...\")... where Variable was assigned from builderType().statement\n // The last call in the chain (furthest from register) should be the variable call\n\n const lastCall = chain[chain.length - 1];\n if (!lastCall) return null;\n\n const expr = lastCall.expression;\n\n // If it's an identifier (like \"Given\", \"When\", \"Then\"), trace its declaration\n let identifier: ts.Identifier | null = null;\n if (ts.isIdentifier(expr)) {\n identifier = expr;\n } else if (\n ts.isPropertyAccessExpression(expr) &&\n ts.isIdentifier(expr.expression)\n ) {\n identifier = expr.expression;\n }\n\n if (!identifier) return null;\n\n // Tracing the re-exported builder's declaration needs symbol resolution,\n // which only a real Program provides. Flag for the checked retry.\n if (!ctx.checker) {\n ctx.needsChecker = true;\n return null;\n }\n\n const symbol = ctx.checker.getSymbolAtLocation(identifier);\n if (!symbol) return null;\n\n const decl = symbol.valueDeclaration;\n if (!decl || !ts.isVariableDeclaration(decl) || !decl.initializer)\n return null;\n\n // Check if initializer is builderType<T>().statement\n const init = decl.initializer;\n\n // Pattern: givenBuilder<T>().statement (PropertyAccessExpression)\n if (ts.isPropertyAccessExpression(init) && init.name.text === \"statement\") {\n const callExpr = init.expression;\n if (ts.isCallExpression(callExpr)) {\n const callee = callExpr.expression;\n if (ts.isIdentifier(callee) && BUILDER_NAMES[callee.text]) {\n // The lastCall IS the statement call — extract expression from it.\n // The re-exported-builder pattern carries no `.parsers(...)`, so\n // variables fall back to the default `{string}` placeholder.\n const expression = extractExpression(lastCall, null);\n return {\n stepType: BUILDER_NAMES[callee.text],\n expression,\n };\n }\n }\n }\n\n return null;\n}\n","import {\n CucumberExpression,\n ParameterType,\n ParameterTypeRegistry,\n} from \"@cucumber/cucumber-expressions\";\nimport { MatchedStep, ParsedScenario, StepDefinitionMeta } from \"./types.js\";\n\ninterface CompiledPattern {\n expression: CucumberExpression;\n definition: StepDefinitionMeta;\n}\n\n/** `{name}` tokens in a cucumber expression (empty name is the anonymous `{}`). */\nconst PARAM_TOKEN = /\\{([^}]*)\\}/g;\n\n/**\n * Compile each definition's cucumber expression with the **same** engine the\n * runtime uses (`@cucumber/cucumber-expressions`), so the analyzer and the\n * runtime agree on what a step matches.\n *\n * A hand-rolled `{param}` → `(.+)` regex used to stand in here, but it treated\n * cucumber-expression alternative (`a/b`) and optional (`text(s)`) syntax as\n * literal characters — so any step definition using them (e.g.\n * `there should be {int} error/errors`) never matched, and every such step was\n * wrongly reported as an undefined step regardless of the real state.\n *\n * Built-in placeholders (`{int}`, `{float}`, `{string}`, `{word}`) keep their\n * real, strict regexes so the analyzer disambiguates the way the runtime does\n * (e.g. `no` isn't an `{int}`). The analyzer has only the expression string, not\n * a custom parser's regexp, so each custom `{name}` is registered as a permissive\n * parameter type (any value) — lenient about the value's exact shape, but still\n * anchored by the surrounding literal text.\n */\nfunction compileDefinitions(\n definitions: StepDefinitionMeta[]\n): CompiledPattern[] {\n const compiled: CompiledPattern[] = [];\n for (const def of definitions) {\n try {\n const registry = new ParameterTypeRegistry();\n for (const [, name] of def.expression.matchAll(PARAM_TOKEN)) {\n if (!name || registry.lookupByTypeName(name)) continue;\n registry.defineParameterType(\n new ParameterType(name, /.+/, null, (value: string) => value)\n );\n }\n compiled.push({\n expression: new CucumberExpression(def.expression, registry),\n definition: def,\n });\n } catch {\n // Skip definitions whose expression can't be compiled.\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 { expression, definition } of compiled) {\n if (definition.stepType !== expectedStepType) continue;\n if (expression.match(text) != null) 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 { expression, definition } of compiled) {\n if (expression.match(text) != null) fallbackMatches.push(definition);\n }\n\n return fallbackMatches;\n}\n","import {\n AnalysisRule,\n Diagnostic,\n MatchedStep,\n ParsedScenario,\n} from \"../types.js\";\n\nexport const ambiguousStepRule: AnalysisRule = {\n name: \"ambiguous-step\",\n\n check(\n scenario: ParsedScenario,\n matchedSteps: MatchedStep[]\n ): Diagnostic[] {\n return matchedSteps\n .filter((step) => step.definitions.length > 1)\n .map((step) => {\n const locations = step.definitions\n .map((d) => `${d.sourceFile}:${d.line}`)\n .join(\", \");\n return {\n file: scenario.file,\n range: {\n startLine: step.line,\n startColumn: step.column,\n endLine: step.line,\n endColumn: step.column + step.text.length,\n },\n severity: \"error\" as const,\n message: `Step \"${step.text}\" matches multiple step definitions: ${locations}`,\n rule: \"ambiguous-step\",\n source: \"step-forge\" as const,\n };\n });\n },\n};\n","import {\n AnalysisRule,\n Diagnostic,\n MatchedStep,\n ParsedScenario,\n} from \"../types.js\";\n\nexport const dependencyRule: AnalysisRule = {\n name: \"dependency-check\",\n\n check(\n scenario: ParsedScenario,\n matchedSteps: MatchedStep[]\n ): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const produced = {\n given: new Set<string>(),\n when: new Set<string>(),\n then: new Set<string>(),\n };\n\n for (const step of matchedSteps) {\n if (step.definitions.length !== 1) continue;\n\n const { dependencies, produces, stepType } = step.definitions[0];\n\n // Collect all missing required dependencies for this step\n const missing: Record<string, string[]> = {};\n for (const phase of [\"given\", \"when\", \"then\"] as const) {\n for (const [key, requirement] of Object.entries(dependencies[phase])) {\n if (requirement === \"required\" && !produced[phase].has(key)) {\n if (!missing[phase]) {\n missing[phase] = [];\n }\n missing[phase].push(key);\n }\n }\n }\n\n if (Object.keys(missing).length > 0) {\n const lines = Object.entries(missing).map(\n ([phase, keys]) =>\n `${phase.charAt(0).toUpperCase() + phase.slice(1)}: ${keys.map((k) => `${phase}.${k}`).join(\", \")}`\n );\n const message =\n \"Missing required dependencies:\\n\" + lines.join(\"\\n\");\n\n diagnostics.push({\n file: scenario.file,\n range: {\n startLine: step.line,\n startColumn: step.column,\n endLine: step.line,\n endColumn: step.column + step.text.length,\n },\n severity: \"error\",\n message,\n rule: \"dependency-check\",\n source: \"step-forge\",\n });\n }\n\n // Add produced keys for this step\n for (const key of produces) {\n produced[stepType].add(key);\n }\n }\n\n return diagnostics;\n },\n};\n","import {\n AnalysisRule,\n Diagnostic,\n MatchedStep,\n ParsedScenario,\n} from \"../types.js\";\n\nexport const undefinedStepRule: AnalysisRule = {\n name: \"undefined-step\",\n\n check(\n scenario: ParsedScenario,\n matchedSteps: MatchedStep[]\n ): Diagnostic[] {\n return matchedSteps\n .filter((step) => step.definitions.length === 0)\n .map((step) => ({\n file: scenario.file,\n range: {\n startLine: step.line,\n startColumn: step.column,\n endLine: step.line,\n endColumn: step.column + step.text.length,\n },\n severity: \"error\" as const,\n message: `Step \"${step.text}\" does not match any step definition`,\n rule: \"undefined-step\",\n source: \"step-forge\" as const,\n }));\n },\n};\n","import {\n AnalysisRule,\n Diagnostic,\n MatchedStep,\n ParsedScenario,\n} from \"../types.js\";\nimport { ambiguousStepRule } from \"./ambiguousStepRule.js\";\nimport { dependencyRule } from \"./dependencyRule.js\";\nimport { undefinedStepRule } from \"./undefinedStepRule.js\";\n\nexport const defaultRules: AnalysisRule[] = [\n undefinedStepRule,\n ambiguousStepRule,\n dependencyRule,\n];\n\nexport function runRules(\n rules: AnalysisRule[],\n scenario: ParsedScenario,\n matchedSteps: MatchedStep[]\n): Diagnostic[] {\n return rules.flatMap((rule) => rule.check(scenario, matchedSteps));\n}\n","import { globFiles } from \"../globFiles.js\";\nimport { extractStepDefinitions } from \"./stepExtractor.js\";\nimport { parseFeatureFiles, parseFeatureContent } from \"./gherkinParser.js\";\nimport { matchScenarioSteps, findMatchingDefinitions } from \"./stepMatcher.js\";\nimport { defaultRules, runRules } from \"./rules/index.js\";\nimport type {\n AnalyzerConfig,\n AnalysisRule,\n Diagnostic,\n StepDefinitionMeta,\n ParsedScenario,\n MatchedStep,\n ParsedStep,\n} from \"./types.js\";\n\nexport type {\n AnalyzerConfig,\n AnalysisRule,\n Diagnostic,\n StepDefinitionMeta,\n ParsedScenario,\n MatchedStep,\n ParsedStep,\n};\n\nexport {\n extractStepDefinitions,\n parseFeatureFiles,\n parseFeatureContent,\n matchScenarioSteps,\n findMatchingDefinitions,\n defaultRules,\n};\n\nexport interface AnalyzeOptions {\n rules?: AnalysisRule[];\n}\n\nexport async function analyze(\n config: AnalyzerConfig,\n options?: AnalyzeOptions\n): Promise<Diagnostic[]> {\n const rules = options?.rules ?? defaultRules;\n\n // 1. Resolve file globs to paths\n const stepFilePaths = await globFiles(config.stepFiles);\n const featureFilePaths = await globFiles(config.featureFiles);\n\n if (stepFilePaths.length === 0) {\n return [];\n }\n if (featureFilePaths.length === 0) {\n return [];\n }\n\n // 2. Extract step metadata from step files\n const stepDefinitions = extractStepDefinitions(\n stepFilePaths,\n config.tsConfigPath\n );\n\n // 3. Parse feature files\n const scenarios = parseFeatureFiles(featureFilePaths);\n\n // 4. For each scenario, match steps and run rules\n const diagnostics: Diagnostic[] = [];\n for (const scenario of scenarios) {\n const matchedSteps = matchScenarioSteps(scenario, stepDefinitions);\n const scenarioDiags = runRules(rules, scenario, matchedSteps);\n diagnostics.push(...scenarioDiags);\n }\n\n return diagnostics;\n}\n"],"mappings":"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"}
|
package/dist/analyzer-cli.js
CHANGED
package/dist/analyzer.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as parseFeatureContent, i as matchScenarioSteps, n as defaultRules, o as parseFeatureFiles, r as findMatchingDefinitions, s as extractStepDefinitions, t as analyze } from "./analyzer-
|
|
1
|
+
import { a as parseFeatureContent, i as matchScenarioSteps, n as defaultRules, o as parseFeatureFiles, r as findMatchingDefinitions, s as extractStepDefinitions, t as analyze } from "./analyzer-5H699Eg4.js";
|
|
2
2
|
export { analyze, defaultRules, extractStepDefinitions, findMatchingDefinitions, matchScenarioSteps, parseFeatureContent, parseFeatureFiles };
|
package/dist/cli.cjs
CHANGED
|
@@ -1238,7 +1238,7 @@ var InteractiveSession = class {
|
|
|
1238
1238
|
async analyzeScenario(scenario) {
|
|
1239
1239
|
let analyzer;
|
|
1240
1240
|
try {
|
|
1241
|
-
analyzer = await Promise.resolve().then(() => require("./analyzer-
|
|
1241
|
+
analyzer = await Promise.resolve().then(() => require("./analyzer-DRudicCk.cjs")).then((n) => n.analyzer_exports);
|
|
1242
1242
|
} catch {
|
|
1243
1243
|
return [dim(" analysis skipped: install `typescript` to enable it")];
|
|
1244
1244
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1236,7 +1236,7 @@ var InteractiveSession = class {
|
|
|
1236
1236
|
async analyzeScenario(scenario) {
|
|
1237
1237
|
let analyzer;
|
|
1238
1238
|
try {
|
|
1239
|
-
analyzer = await import("./analyzer-
|
|
1239
|
+
analyzer = await import("./analyzer-NsNpSFox.js").then((n) => n.n);
|
|
1240
1240
|
} catch {
|
|
1241
1241
|
return [dim(" analysis skipped: install `typescript` to enable it")];
|
|
1242
1242
|
}
|
package/dist/step-forge.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_gherkinParser = require("./gherkinParser-_ZgvELdy.cjs");
|
|
3
3
|
const require_hooks = require("./hooks-Be0cjULN.cjs");
|
|
4
|
-
const require_analyzer = require("./analyzer-
|
|
4
|
+
const require_analyzer = require("./analyzer-DRudicCk.cjs");
|
|
5
5
|
let lodash = require("lodash");
|
|
6
6
|
lodash = require_gherkinParser.__toESM(lodash, 1);
|
|
7
7
|
//#region src/builderTypeUtils.ts
|
package/dist/step-forge.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as matchScenarioSteps, i as findMatchingDefinitions, o as extractStepDefinitions, r as defaultRules, t as analyze } from "./analyzer-
|
|
1
|
+
import { a as matchScenarioSteps, i as findMatchingDefinitions, o as extractStepDefinitions, r as defaultRules, t as analyze } from "./analyzer-NsNpSFox.js";
|
|
2
2
|
import { n as globalHookRegistry, o as globalRegistry } from "./hooks-BDCMKeNq.js";
|
|
3
3
|
import { a as BasicWorld, n as parseFeatureContent, o as captureDefinitionSite, r as parseFeatureFiles } from "./gherkinParser-i-Q7M6mi.js";
|
|
4
4
|
import _ from "lodash";
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer-DIZPMxzN.cjs","names":["ts","globFiles","parseFeatureFiles"],"sources":["../../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"],"sourcesContent":["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"],"mappings":"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"}
|