Skip to content

Commit d3db9d9

Browse files
authored
Detect and report state cycles precisely (#139)
1 parent 34a9c0a commit d3db9d9

File tree

2 files changed

+34
-14
lines changed

2 files changed

+34
-14
lines changed

cucumber-tsflow-specs/features/custom-context-objects.feature

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ Feature: Custom context objects
203203
And the output contains "The state is 'initial value'"
204204
And the output contains "The state is 'step value'"
205205

206-
Scenario: Circular dependencies are explicitly communicated to the developer
206+
Scenario: Cyclic imports are detected and communicated to the developer
207207
Given a file named "features/a.feature" with:
208208
"""feature
209209
Feature: some feature
@@ -276,11 +276,11 @@ Feature: Custom context objects
276276
Then it fails
277277
And the error output contains text:
278278
"""
279-
Undefined context type at index 0 for StateOne, do you possibly have a circular dependency?
279+
Error: Undefined dependency detected in StateOne. You possibly have an import cycle.
280+
See https://nodejs.org/api/modules.html#modules_cycles
280281
"""
281282

282-
283-
Scenario: Circular dependencies within the same file are vaguely communicated to the developer
283+
Scenario: Cyclic state dependencies are detected and communicated to the developer
284284
Given a file named "features/a.feature" with:
285285
"""feature
286286
Feature: some feature
@@ -346,10 +346,10 @@ Feature: Custom context objects
346346
Then it fails
347347
And the error output contains text:
348348
"""
349-
Undefined context type at index 0 for StepsTwo, do you possibly have a circular dependency?
349+
Error: Cyclic dependency detected: StateOne -> StateTwo -> StateOne
350350
"""
351351

352-
Scenario: In-file circular dependencies are thrown as maximum call stack exceeded errors
352+
Scenario: Cyclic single-file state dependencies are detected and communicated to the developer
353353
Given a file named "features/a.feature" with:
354354
"""feature
355355
Feature: some feature
@@ -401,4 +401,7 @@ Feature: Custom context objects
401401
"""
402402
When I run cucumber-js
403403
Then it fails
404-
And the output contains "RangeError: Maximum call stack size exceeded"
404+
And the error output contains text:
405+
"""
406+
Error: Cyclic dependency detected: StateOne -> StateTwo -> StateOne
407+
"""

cucumber-tsflow/src/binding-decorator.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,29 @@ const stepPatternRegistrations = new Map<StepPattern, StepBindingFlags>();
5151

5252
// tslint:disable:no-bitwise
5353

54+
function ensureNoCyclicDependencies(target: any, currentPath: any[] = []) {
55+
const dependencies = BindingRegistry.instance.getContextTypesForTarget(target.prototype);
56+
57+
if (dependencies.length === 0) {
58+
return;
59+
}
60+
61+
for (const dependency of dependencies) {
62+
if (dependency === undefined) {
63+
throw new Error(
64+
`Undefined dependency detected in ${target.name}. You possibly have an import cycle.\n`
65+
+ 'See https://nodejs.org/api/modules.html#modules_cycles'
66+
);
67+
}
68+
69+
if (currentPath.includes(dependency)) {
70+
throw new Error(`Cyclic dependency detected: ${dependency.name} -> ${target.name} -> ${currentPath.map((t) => t.name).join(' -> ')}`);
71+
}
72+
73+
ensureNoCyclicDependencies(dependency, [...currentPath, target]);
74+
}
75+
}
76+
5477
/**
5578
* A class decorator that marks the associated class as a CucumberJS binding.
5679
*
@@ -68,13 +91,7 @@ export function binding(requiredContextTypes?: ContextType[]): TypeDecorator {
6891
requiredContextTypes
6992
);
7093

71-
if (Array.isArray(requiredContextTypes)) {
72-
for (const i in requiredContextTypes) {
73-
if (typeof requiredContextTypes[i] === 'undefined') {
74-
throw new Error(`Undefined context type at index ${i} for ${target.name}, do you possibly have a circular dependency?`);
75-
}
76-
}
77-
}
94+
ensureNoCyclicDependencies(target);
7895

7996
const allBindings: StepBinding[] = [
8097
...bindingRegistry.getStepBindingsForTarget(target),

0 commit comments

Comments
 (0)