@step-forge/step-forge 0.0.7 → 0.0.8
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/package.json +11 -3
- package/rolldown.config.mjs +21 -10
- package/src/analyzer/gherkinParser.ts +1 -1
- package/src/analyzer/index.ts +14 -2
- package/src/analyzer/rules/dependencyRule.ts +29 -17
- package/src/analyzer/stepMatcher.ts +40 -22
- package/src/index.ts +40 -0
package/package.json
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@step-forge/step-forge",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"module": "./dist/step-forge.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
|
-
"
|
|
8
|
+
"require": {
|
|
9
|
+
"types": "./dist/index.d.cts",
|
|
10
|
+
"default": "./dist/step-forge.cjs"
|
|
11
|
+
},
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/step-forge.js"
|
|
15
|
+
}
|
|
9
16
|
},
|
|
10
17
|
"./analyzer": {
|
|
18
|
+
"types": "./dist/analyzer.d.ts",
|
|
11
19
|
"import": "./dist/analyzer.js"
|
|
12
20
|
},
|
|
13
21
|
"./dist/": {
|
|
@@ -19,7 +27,7 @@
|
|
|
19
27
|
},
|
|
20
28
|
"types": "./dist/index.d.ts",
|
|
21
29
|
"scripts": {
|
|
22
|
-
"build": "rm -rf build && tsc && rolldown -c && dts-bundle-generator --config ./dts-bundle-generator.config.cjs && cp package.json build/",
|
|
30
|
+
"build": "rm -rf build && tsc && rolldown -c && dts-bundle-generator --config ./dts-bundle-generator.config.cjs && cp build/dist/index.d.ts build/dist/index.d.cts && cp package.json build/",
|
|
23
31
|
"test:cucumber": "NODE_ENV=test NODE_OPTIONS='--import tsx' cucumber-js -p default",
|
|
24
32
|
"test:ci": "NODE_ENV=test NODE_OPTIONS='--import tsx' cucumber-js -p ci",
|
|
25
33
|
"test": "NODE_ENV=test PORT=7888 LOG_LEVEL=none NODE_OPTIONS='--import tsx' cucumber-js -p all 2> /dev/null",
|
package/rolldown.config.mjs
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { defineConfig } from "rolldown";
|
|
2
2
|
|
|
3
|
+
const allExternal = [
|
|
4
|
+
"@cucumber/cucumber",
|
|
5
|
+
"@cucumber/gherkin",
|
|
6
|
+
"@cucumber/messages",
|
|
7
|
+
"@cucumber/cucumber-expressions",
|
|
8
|
+
"lodash",
|
|
9
|
+
"typescript",
|
|
10
|
+
/^node:/,
|
|
11
|
+
];
|
|
12
|
+
|
|
3
13
|
export default defineConfig([
|
|
4
14
|
{
|
|
5
15
|
input: "./src/index.ts",
|
|
@@ -8,7 +18,16 @@ export default defineConfig([
|
|
|
8
18
|
format: "esm",
|
|
9
19
|
sourcemap: true,
|
|
10
20
|
},
|
|
11
|
-
external:
|
|
21
|
+
external: allExternal,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
input: "./src/index.ts",
|
|
25
|
+
output: {
|
|
26
|
+
file: "./build/dist/step-forge.cjs",
|
|
27
|
+
format: "cjs",
|
|
28
|
+
sourcemap: true,
|
|
29
|
+
},
|
|
30
|
+
external: allExternal,
|
|
12
31
|
},
|
|
13
32
|
{
|
|
14
33
|
input: {
|
|
@@ -21,14 +40,6 @@ export default defineConfig([
|
|
|
21
40
|
sourcemap: true,
|
|
22
41
|
entryFileNames: "[name].js",
|
|
23
42
|
},
|
|
24
|
-
external:
|
|
25
|
-
"@cucumber/cucumber",
|
|
26
|
-
"@cucumber/gherkin",
|
|
27
|
-
"@cucumber/messages",
|
|
28
|
-
"@cucumber/cucumber-expressions",
|
|
29
|
-
"lodash",
|
|
30
|
-
"typescript",
|
|
31
|
-
/^node:/,
|
|
32
|
-
],
|
|
43
|
+
external: allExternal,
|
|
33
44
|
},
|
|
34
45
|
]);
|
package/src/analyzer/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { glob } from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { extractStepDefinitions } from "./stepExtractor.js";
|
|
4
|
-
import { parseFeatureFiles } from "./gherkinParser.js";
|
|
5
|
-
import {
|
|
4
|
+
import { parseFeatureFiles, parseFeatureContent } from "./gherkinParser.js";
|
|
5
|
+
import {
|
|
6
|
+
matchScenarioSteps,
|
|
7
|
+
findMatchingDefinitions,
|
|
8
|
+
} from "./stepMatcher.js";
|
|
6
9
|
import { defaultRules, runRules } from "./rules/index.js";
|
|
7
10
|
import type {
|
|
8
11
|
AnalyzerConfig,
|
|
@@ -24,6 +27,15 @@ export type {
|
|
|
24
27
|
ParsedStep,
|
|
25
28
|
};
|
|
26
29
|
|
|
30
|
+
export {
|
|
31
|
+
extractStepDefinitions,
|
|
32
|
+
parseFeatureFiles,
|
|
33
|
+
parseFeatureContent,
|
|
34
|
+
matchScenarioSteps,
|
|
35
|
+
findMatchingDefinitions,
|
|
36
|
+
defaultRules,
|
|
37
|
+
};
|
|
38
|
+
|
|
27
39
|
export interface AnalyzeOptions {
|
|
28
40
|
rules?: AnalysisRule[];
|
|
29
41
|
}
|
|
@@ -24,30 +24,42 @@ export const dependencyRule: AnalysisRule = {
|
|
|
24
24
|
|
|
25
25
|
const { dependencies, produces, stepType } = step.definitions[0];
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// Collect all missing required dependencies for this step
|
|
28
|
+
const missing: Record<string, string[]> = {};
|
|
28
29
|
for (const phase of ["given", "when", "then"] as const) {
|
|
29
30
|
for (const [key, requirement] of Object.entries(dependencies[phase])) {
|
|
30
31
|
if (requirement === "required" && !produced[phase].has(key)) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
file: scenario.file,
|
|
36
|
-
range: {
|
|
37
|
-
startLine: step.line,
|
|
38
|
-
startColumn: step.column,
|
|
39
|
-
endLine: step.line,
|
|
40
|
-
endColumn: step.column + step.text.length,
|
|
41
|
-
},
|
|
42
|
-
severity: "error",
|
|
43
|
-
message: `Step "${step.text}" requires '${phase}.${key}' but no preceding step produces it. Available ${phase} keys: ${availableStr}. Defined in: ${step.definitions[0].sourceFile}:${step.definitions[0].line}`,
|
|
44
|
-
rule: "dependency-check",
|
|
45
|
-
source: "step-forge",
|
|
46
|
-
});
|
|
32
|
+
if (!missing[phase]) {
|
|
33
|
+
missing[phase] = [];
|
|
34
|
+
}
|
|
35
|
+
missing[phase].push(key);
|
|
47
36
|
}
|
|
48
37
|
}
|
|
49
38
|
}
|
|
50
39
|
|
|
40
|
+
if (Object.keys(missing).length > 0) {
|
|
41
|
+
const lines = Object.entries(missing).map(
|
|
42
|
+
([phase, keys]) =>
|
|
43
|
+
`${phase.charAt(0).toUpperCase() + phase.slice(1)}: ${keys.map((k) => `${phase}.${k}`).join(", ")}`
|
|
44
|
+
);
|
|
45
|
+
const message =
|
|
46
|
+
"Missing required dependencies:\n" + lines.join("\n");
|
|
47
|
+
|
|
48
|
+
diagnostics.push({
|
|
49
|
+
file: scenario.file,
|
|
50
|
+
range: {
|
|
51
|
+
startLine: step.line,
|
|
52
|
+
startColumn: step.column,
|
|
53
|
+
endLine: step.line,
|
|
54
|
+
endColumn: step.column + step.text.length,
|
|
55
|
+
},
|
|
56
|
+
severity: "error",
|
|
57
|
+
message,
|
|
58
|
+
rule: "dependency-check",
|
|
59
|
+
source: "step-forge",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
// Add produced keys for this step
|
|
52
64
|
for (const key of produces) {
|
|
53
65
|
produced[stepType].add(key);
|
|
@@ -1,29 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CucumberExpression,
|
|
3
|
-
ParameterTypeRegistry,
|
|
4
|
-
} from "@cucumber/cucumber-expressions";
|
|
5
1
|
import { MatchedStep, ParsedScenario, StepDefinitionMeta } from "./types.js";
|
|
6
2
|
|
|
7
|
-
interface
|
|
8
|
-
|
|
3
|
+
interface CompiledPattern {
|
|
4
|
+
regex: RegExp;
|
|
9
5
|
definition: StepDefinitionMeta;
|
|
10
6
|
}
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
scenario: ParsedScenario,
|
|
8
|
+
function compileDefinitions(
|
|
14
9
|
definitions: StepDefinitionMeta[]
|
|
15
|
-
):
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const compiled: CompiledExpression[] = [];
|
|
10
|
+
): CompiledPattern[] {
|
|
11
|
+
const compiled: CompiledPattern[] = [];
|
|
19
12
|
for (const def of definitions) {
|
|
20
13
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
// Replace {paramType} placeholders with (.+) to match any value,
|
|
15
|
+
// matching the Step Forge runtime behavior where all params use {string}
|
|
16
|
+
// and values are coerced at runtime via typeCoercer.
|
|
17
|
+
const placeholder = "###PLACEHOLDER###";
|
|
18
|
+
const regexStr = def.expression
|
|
19
|
+
.replace(/\{[^}]+\}/g, placeholder)
|
|
20
|
+
.replace(/[.*+?^$()|[\]\\]/g, "\\$&")
|
|
21
|
+
.replace(new RegExp(placeholder, "g"), "(.+)");
|
|
22
|
+
compiled.push({
|
|
23
|
+
regex: new RegExp(`^${regexStr}$`, "i"),
|
|
24
|
+
definition: def,
|
|
25
|
+
});
|
|
23
26
|
} catch {
|
|
24
27
|
// Skip definitions with invalid expressions
|
|
25
28
|
}
|
|
26
29
|
}
|
|
30
|
+
return compiled;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function matchScenarioSteps(
|
|
34
|
+
scenario: ParsedScenario,
|
|
35
|
+
definitions: StepDefinitionMeta[]
|
|
36
|
+
): MatchedStep[] {
|
|
37
|
+
const compiled = compileDefinitions(definitions);
|
|
27
38
|
|
|
28
39
|
return scenario.steps.map((step) => {
|
|
29
40
|
const matches = findMatches(step.text, step.effectiveKeyword, compiled);
|
|
@@ -34,10 +45,19 @@ export function matchScenarioSteps(
|
|
|
34
45
|
});
|
|
35
46
|
}
|
|
36
47
|
|
|
48
|
+
export function findMatchingDefinitions(
|
|
49
|
+
text: string,
|
|
50
|
+
effectiveKeyword: "Given" | "When" | "Then",
|
|
51
|
+
definitions: StepDefinitionMeta[]
|
|
52
|
+
): StepDefinitionMeta[] {
|
|
53
|
+
const compiled = compileDefinitions(definitions);
|
|
54
|
+
return findMatches(text, effectiveKeyword, compiled);
|
|
55
|
+
}
|
|
56
|
+
|
|
37
57
|
function findMatches(
|
|
38
58
|
text: string,
|
|
39
59
|
effectiveKeyword: "Given" | "When" | "Then",
|
|
40
|
-
compiled:
|
|
60
|
+
compiled: CompiledPattern[]
|
|
41
61
|
): StepDefinitionMeta[] {
|
|
42
62
|
const keywordToStepType: Record<string, string> = {
|
|
43
63
|
Given: "given",
|
|
@@ -48,19 +68,17 @@ function findMatches(
|
|
|
48
68
|
|
|
49
69
|
// First try to match with the correct step type
|
|
50
70
|
const typedMatches: StepDefinitionMeta[] = [];
|
|
51
|
-
for (const {
|
|
71
|
+
for (const { regex, definition } of compiled) {
|
|
52
72
|
if (definition.stepType !== expectedStepType) continue;
|
|
53
|
-
|
|
54
|
-
if (result) typedMatches.push(definition);
|
|
73
|
+
if (regex.test(text)) typedMatches.push(definition);
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
if (typedMatches.length > 0) return typedMatches;
|
|
58
77
|
|
|
59
|
-
// Fallback: match any step type
|
|
78
|
+
// Fallback: match any step type
|
|
60
79
|
const fallbackMatches: StepDefinitionMeta[] = [];
|
|
61
|
-
for (const {
|
|
62
|
-
|
|
63
|
-
if (result) fallbackMatches.push(definition);
|
|
80
|
+
for (const { regex, definition } of compiled) {
|
|
81
|
+
if (regex.test(text)) fallbackMatches.push(definition);
|
|
64
82
|
}
|
|
65
83
|
|
|
66
84
|
return fallbackMatches;
|
package/src/index.ts
CHANGED
|
@@ -2,5 +2,45 @@ import { givenBuilder } from "./given";
|
|
|
2
2
|
import { whenBuilder } from "./when";
|
|
3
3
|
import { thenBuilder } from "./then";
|
|
4
4
|
import { BasicWorld } from "./world";
|
|
5
|
+
import {
|
|
6
|
+
analyze,
|
|
7
|
+
extractStepDefinitions,
|
|
8
|
+
parseFeatureFiles,
|
|
9
|
+
parseFeatureContent,
|
|
10
|
+
matchScenarioSteps,
|
|
11
|
+
findMatchingDefinitions,
|
|
12
|
+
defaultRules,
|
|
13
|
+
} from "./analyzer/index";
|
|
14
|
+
import type {
|
|
15
|
+
AnalyzerConfig,
|
|
16
|
+
AnalyzeOptions,
|
|
17
|
+
AnalysisRule,
|
|
18
|
+
Diagnostic,
|
|
19
|
+
StepDefinitionMeta,
|
|
20
|
+
ParsedScenario,
|
|
21
|
+
ParsedStep,
|
|
22
|
+
MatchedStep,
|
|
23
|
+
} from "./analyzer/index";
|
|
5
24
|
|
|
6
25
|
export { givenBuilder, whenBuilder, thenBuilder, BasicWorld };
|
|
26
|
+
|
|
27
|
+
export const analyzer = {
|
|
28
|
+
analyze,
|
|
29
|
+
extractStepDefinitions,
|
|
30
|
+
parseFeatureFiles,
|
|
31
|
+
parseFeatureContent,
|
|
32
|
+
matchScenarioSteps,
|
|
33
|
+
findMatchingDefinitions,
|
|
34
|
+
defaultRules,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type {
|
|
38
|
+
AnalyzerConfig,
|
|
39
|
+
AnalyzeOptions,
|
|
40
|
+
AnalysisRule,
|
|
41
|
+
Diagnostic,
|
|
42
|
+
StepDefinitionMeta,
|
|
43
|
+
ParsedScenario,
|
|
44
|
+
ParsedStep,
|
|
45
|
+
MatchedStep,
|
|
46
|
+
};
|