effect-bdd 0.1.2 → 0.1.3

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.
Files changed (91) hide show
  1. package/dist/Bdd.d.ts +0 -1
  2. package/dist/Bdd.js +1 -2
  3. package/dist/Errors.d.ts +0 -1
  4. package/dist/Errors.js +1 -2
  5. package/dist/bin.d.ts +0 -1
  6. package/dist/bin.js +1 -2
  7. package/dist/index.d.ts +0 -1
  8. package/dist/index.js +1 -2
  9. package/dist/internal/cli/errors.d.ts +0 -1
  10. package/dist/internal/cli/errors.js +1 -2
  11. package/dist/internal/cli/glob.d.ts +0 -1
  12. package/dist/internal/cli/glob.js +1 -2
  13. package/dist/internal/cli/loaders.d.ts +0 -1
  14. package/dist/internal/cli/loaders.js +1 -2
  15. package/dist/internal/cli/models.d.ts +0 -1
  16. package/dist/internal/cli/models.js +1 -2
  17. package/dist/internal/cli/moduleLoader.d.ts +0 -1
  18. package/dist/internal/cli/moduleLoader.js +1 -2
  19. package/dist/internal/cli/reporter.d.ts +0 -1
  20. package/dist/internal/cli/reporter.js +1 -2
  21. package/dist/internal/cli/runner.d.ts +0 -1
  22. package/dist/internal/cli/runner.js +1 -2
  23. package/dist/internal/cli/tagExpression.d.ts +0 -1
  24. package/dist/internal/cli/tagExpression.js +1 -2
  25. package/dist/internal/cucumberCompiler.d.ts +0 -1
  26. package/dist/internal/cucumberCompiler.js +1 -2
  27. package/dist/internal/expression.d.ts +0 -1
  28. package/dist/internal/expression.js +1 -2
  29. package/dist/internal/matching.d.ts +0 -1
  30. package/dist/internal/matching.js +1 -2
  31. package/dist/internal/parser.d.ts +0 -1
  32. package/dist/internal/parser.js +1 -2
  33. package/dist/internal/runner.d.ts +0 -1
  34. package/dist/internal/runner.js +1 -2
  35. package/dist/main.d.ts +0 -1
  36. package/dist/main.js +1 -2
  37. package/package.json +3 -7
  38. package/dist/Bdd.d.ts.map +0 -1
  39. package/dist/Bdd.js.map +0 -1
  40. package/dist/Errors.d.ts.map +0 -1
  41. package/dist/Errors.js.map +0 -1
  42. package/dist/bin.d.ts.map +0 -1
  43. package/dist/bin.js.map +0 -1
  44. package/dist/index.d.ts.map +0 -1
  45. package/dist/index.js.map +0 -1
  46. package/dist/internal/cli/errors.d.ts.map +0 -1
  47. package/dist/internal/cli/errors.js.map +0 -1
  48. package/dist/internal/cli/glob.d.ts.map +0 -1
  49. package/dist/internal/cli/glob.js.map +0 -1
  50. package/dist/internal/cli/loaders.d.ts.map +0 -1
  51. package/dist/internal/cli/loaders.js.map +0 -1
  52. package/dist/internal/cli/models.d.ts.map +0 -1
  53. package/dist/internal/cli/models.js.map +0 -1
  54. package/dist/internal/cli/moduleLoader.d.ts.map +0 -1
  55. package/dist/internal/cli/moduleLoader.js.map +0 -1
  56. package/dist/internal/cli/reporter.d.ts.map +0 -1
  57. package/dist/internal/cli/reporter.js.map +0 -1
  58. package/dist/internal/cli/runner.d.ts.map +0 -1
  59. package/dist/internal/cli/runner.js.map +0 -1
  60. package/dist/internal/cli/tagExpression.d.ts.map +0 -1
  61. package/dist/internal/cli/tagExpression.js.map +0 -1
  62. package/dist/internal/cucumberCompiler.d.ts.map +0 -1
  63. package/dist/internal/cucumberCompiler.js.map +0 -1
  64. package/dist/internal/expression.d.ts.map +0 -1
  65. package/dist/internal/expression.js.map +0 -1
  66. package/dist/internal/matching.d.ts.map +0 -1
  67. package/dist/internal/matching.js.map +0 -1
  68. package/dist/internal/parser.d.ts.map +0 -1
  69. package/dist/internal/parser.js.map +0 -1
  70. package/dist/internal/runner.d.ts.map +0 -1
  71. package/dist/internal/runner.js.map +0 -1
  72. package/dist/main.d.ts.map +0 -1
  73. package/dist/main.js.map +0 -1
  74. package/src/Bdd.ts +0 -575
  75. package/src/Errors.ts +0 -60
  76. package/src/bin.ts +0 -10
  77. package/src/index.ts +0 -155
  78. package/src/internal/cli/errors.ts +0 -20
  79. package/src/internal/cli/glob.ts +0 -37
  80. package/src/internal/cli/loaders.ts +0 -100
  81. package/src/internal/cli/models.ts +0 -118
  82. package/src/internal/cli/moduleLoader.ts +0 -41
  83. package/src/internal/cli/reporter.ts +0 -367
  84. package/src/internal/cli/runner.ts +0 -336
  85. package/src/internal/cli/tagExpression.ts +0 -173
  86. package/src/internal/cucumberCompiler.ts +0 -58
  87. package/src/internal/expression.ts +0 -103
  88. package/src/internal/matching.ts +0 -81
  89. package/src/internal/parser.ts +0 -155
  90. package/src/internal/runner.ts +0 -373
  91. package/src/main.ts +0 -169
@@ -1,173 +0,0 @@
1
- import * as Arr from "effect/Array"
2
- import * as Effect from "effect/Effect"
3
- import { pipe } from "effect/Function"
4
- import * as Str from "effect/String"
5
- import { DiscoveryError } from "./errors.ts"
6
-
7
- /** @internal */
8
- export type TagPredicate = (tags: ReadonlyArray<string>) => boolean
9
-
10
- type Expression =
11
- | {
12
- readonly _tag: "Tag"
13
- readonly tag: string
14
- }
15
- | {
16
- readonly _tag: "Not"
17
- readonly expression: Expression
18
- }
19
- | {
20
- readonly _tag: "And"
21
- readonly left: Expression
22
- readonly right: Expression
23
- }
24
- | {
25
- readonly _tag: "Or"
26
- readonly left: Expression
27
- readonly right: Expression
28
- }
29
-
30
- interface ParseResult {
31
- readonly expression: Expression
32
- readonly index: number
33
- }
34
-
35
- /** @internal */
36
- export const compileAll = (
37
- expressions: ReadonlyArray<string>
38
- ): Effect.Effect<TagPredicate, DiscoveryError> =>
39
- Effect.forEach(expressions, compile).pipe(
40
- Effect.map((predicates) => (tags) => Arr.every(predicates, (predicate) => predicate(tags)))
41
- )
42
-
43
- const compile = (expression: string): Effect.Effect<TagPredicate, DiscoveryError> => {
44
- const tokens = tokenize(expression)
45
- if (tokens === undefined || tokens.length === 0) {
46
- return fail(expression, "Expected a tag expression")
47
- }
48
- const result = parseOr(tokens, 0)
49
- if (result === undefined || result.index !== tokens.length) {
50
- return fail(expression, "Could not parse tag expression")
51
- }
52
- return Effect.succeed((tags) => evaluate(result.expression, tags))
53
- }
54
-
55
- const parseOr = (tokens: ReadonlyArray<string>, index: number): ParseResult | undefined => {
56
- const left = parseAnd(tokens, index)
57
- if (left === undefined) {
58
- return undefined
59
- }
60
- return parseOrRest(tokens, left)
61
- }
62
-
63
- const parseOrRest = (tokens: ReadonlyArray<string>, left: ParseResult): ParseResult => {
64
- if (tokens[left.index] !== "or") {
65
- return left
66
- }
67
- const right = parseAnd(tokens, left.index + 1)
68
- if (right === undefined) {
69
- return left
70
- }
71
- return parseOrRest(tokens, {
72
- expression: {
73
- _tag: "Or",
74
- left: left.expression,
75
- right: right.expression
76
- },
77
- index: right.index
78
- })
79
- }
80
-
81
- const parseAnd = (tokens: ReadonlyArray<string>, index: number): ParseResult | undefined => {
82
- const left = parseUnary(tokens, index)
83
- if (left === undefined) {
84
- return undefined
85
- }
86
- return parseAndRest(tokens, left)
87
- }
88
-
89
- const parseAndRest = (tokens: ReadonlyArray<string>, left: ParseResult): ParseResult => {
90
- if (tokens[left.index] !== "and") {
91
- return left
92
- }
93
- const right = parseUnary(tokens, left.index + 1)
94
- if (right === undefined) {
95
- return left
96
- }
97
- return parseAndRest(tokens, {
98
- expression: {
99
- _tag: "And",
100
- left: left.expression,
101
- right: right.expression
102
- },
103
- index: right.index
104
- })
105
- }
106
-
107
- const parseUnary = (tokens: ReadonlyArray<string>, index: number): ParseResult | undefined =>
108
- tokens[index] === "not" ? parseNot(tokens, index) : parsePrimary(tokens, index)
109
-
110
- const parseNot = (tokens: ReadonlyArray<string>, index: number): ParseResult | undefined => {
111
- const result = parseUnary(tokens, index + 1)
112
- return result === undefined
113
- ? undefined
114
- : {
115
- expression: {
116
- _tag: "Not",
117
- expression: result.expression
118
- },
119
- index: result.index
120
- }
121
- }
122
-
123
- const parsePrimary = (tokens: ReadonlyArray<string>, index: number): ParseResult | undefined => {
124
- const token = tokens[index]
125
- if (token === undefined) {
126
- return undefined
127
- }
128
- if (token === "(") {
129
- const result = parseOr(tokens, index + 1)
130
- return result !== undefined && tokens[result.index] === ")"
131
- ? {
132
- expression: result.expression,
133
- index: result.index + 1
134
- }
135
- : undefined
136
- }
137
- return pipe(token, Str.startsWith("@"))
138
- ? {
139
- expression: {
140
- _tag: "Tag",
141
- tag: token
142
- },
143
- index: index + 1
144
- }
145
- : undefined
146
- }
147
-
148
- const evaluate = (expression: Expression, tags: ReadonlyArray<string>): boolean => {
149
- switch (expression._tag) {
150
- case "Tag": {
151
- return Arr.contains(expression.tag)(tags)
152
- }
153
- case "Not": {
154
- return !evaluate(expression.expression, tags)
155
- }
156
- case "And": {
157
- return evaluate(expression.left, tags) && evaluate(expression.right, tags)
158
- }
159
- case "Or": {
160
- return evaluate(expression.left, tags) || evaluate(expression.right, tags)
161
- }
162
- }
163
- }
164
-
165
- const tokenize = (expression: string): ReadonlyArray<string> | undefined => {
166
- const matches = expression.match(/\(|\)|\b(?:and|or|not)\b|@[A-Za-z0-9][A-Za-z0-9_-]*/g) ?? []
167
- const normalized = pipe(expression, Str.replace(/\s+/g, ""))
168
- const matched = pipe(matches, Arr.join(""))
169
- return normalized === matched ? matches : undefined
170
- }
171
-
172
- const fail = (expression: string, message: string): Effect.Effect<never, DiscoveryError> =>
173
- Effect.fail(new DiscoveryError({ message: `${message}: ${expression}` }))
@@ -1,58 +0,0 @@
1
- import { AstBuilder, compile, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin"
2
- import { IdGenerator } from "@cucumber/messages"
3
- import * as Effect from "effect/Effect"
4
- import * as Layer from "effect/Layer"
5
- import { ParseError } from "../Errors.ts"
6
- import { GherkinCompiler, type ParsedSource } from "./parser.ts"
7
-
8
- /** @internal */
9
- export const Cucumber = Layer.succeed(GherkinCompiler, {
10
- compile: (source, uri) =>
11
- Effect.try({
12
- try: () => compileWithCucumber(source, uri),
13
- catch: parseErrorFromCause
14
- })
15
- })
16
-
17
- const compileWithCucumber = (source: string, uri: string): ParsedSource => {
18
- const newId = IdGenerator.incrementing()
19
- const parser = new Parser(new AstBuilder(newId), new GherkinClassicTokenMatcher())
20
- const document = parser.parse(source)
21
- return {
22
- document,
23
- pickles: compile(document, uri, newId)
24
- }
25
- }
26
-
27
- const parseErrorFromCause = (cause: unknown): ParseError => {
28
- const location = causeLocation(cause)
29
- return new ParseError({
30
- message: causeMessage(cause),
31
- line: location?.line ?? 1,
32
- column: location?.column ?? 1
33
- })
34
- }
35
-
36
- const causeLocation = (cause: unknown): { readonly line: number; readonly column?: number } | undefined => {
37
- if (
38
- typeof cause === "object" && cause !== null && "errors" in cause && Array.isArray(cause.errors) &&
39
- cause.errors.length > 0
40
- ) {
41
- return causeLocation(cause.errors[0])
42
- }
43
- if (typeof cause === "object" && cause !== null && "location" in cause) {
44
- const location = cause.location
45
- if (typeof location === "object" && location !== null && "line" in location && typeof location.line === "number") {
46
- return {
47
- line: location.line,
48
- ...("column" in location && typeof location.column === "number" ? { column: location.column } : {})
49
- }
50
- }
51
- }
52
- return undefined
53
- }
54
-
55
- const causeMessage = (cause: unknown): string =>
56
- typeof cause === "object" && cause !== null && "message" in cause && typeof cause.message === "string"
57
- ? cause.message
58
- : String(cause)
@@ -1,103 +0,0 @@
1
- import * as Arr from "effect/Array"
2
- import { pipe } from "effect/Function"
3
- import * as Option from "effect/Option"
4
- import * as Record from "effect/Record"
5
- import * as Schema from "effect/Schema"
6
- import * as Str from "effect/String"
7
-
8
- /** @internal */
9
- export interface Capture<Name extends string, A> {
10
- readonly _tag: "Capture"
11
- readonly name: Name
12
- readonly schema: Schema.Codec<A, string>
13
- }
14
-
15
- /** @internal */
16
- export interface Matcher<A> {
17
- readonly source: string
18
- readonly match: (text: string) => Option.Option<A>
19
- }
20
-
21
- interface MatcherState {
22
- readonly names: ReadonlyArray<string>
23
- readonly captures: ReadonlyArray<Capture<string, unknown>>
24
- readonly source: string
25
- readonly pattern: string
26
- }
27
-
28
- /** @internal */
29
- export const makeCapture = <const Name extends string, A>(
30
- name: Name,
31
- schema: Schema.Codec<A, string>
32
- ): Capture<Name, A> => ({
33
- _tag: "Capture",
34
- name,
35
- schema
36
- })
37
-
38
- /** @internal */
39
- export const makeMatcher = <A>(
40
- strings: TemplateStringsArray,
41
- captures: ReadonlyArray<Capture<string, unknown>>
42
- ): Matcher<A> => {
43
- const state = pipe(
44
- strings,
45
- Arr.reduce(initialMatcherState, (state, literal, index) => appendTemplatePart(state, literal, captures[index]))
46
- )
47
- const regex = new globalThis.RegExp(`${state.pattern}$`)
48
- const decoders = Arr.map(state.captures, (capture) => Schema.decodeUnknownOption(capture.schema))
49
-
50
- return {
51
- source: state.source,
52
- match(text) {
53
- const match = regex.exec(text)
54
- if (match === null) {
55
- return Option.none()
56
- }
57
- return Option.map(decodeCaptures(state.names, decoders, match), (out) => out as A)
58
- }
59
- }
60
- }
61
-
62
- const initialMatcherState: MatcherState = {
63
- names: [],
64
- captures: [],
65
- source: "",
66
- pattern: "^"
67
- }
68
-
69
- const appendTemplatePart = (
70
- state: MatcherState,
71
- literal: string,
72
- capture: Capture<string, unknown> | undefined
73
- ): MatcherState => {
74
- const pattern = `${state.pattern}${escapeRegExp(literal)}`
75
- const source = `${state.source}${literal}`
76
- if (capture === undefined) {
77
- return { ...state, pattern, source }
78
- }
79
- return {
80
- names: Arr.append(state.names, capture.name),
81
- captures: Arr.append(state.captures, capture),
82
- pattern: `${pattern}(.+?)`,
83
- source: `${source}{${capture.name}}`
84
- }
85
- }
86
-
87
- const decodeCaptures = (
88
- names: ReadonlyArray<string>,
89
- decoders: ReadonlyArray<(input: unknown) => Option.Option<unknown>>,
90
- match: RegExpExecArray,
91
- index = 0,
92
- out: Record<string, unknown> = Record.empty()
93
- ): Option.Option<Record<string, unknown>> => {
94
- if (index >= names.length) {
95
- return Option.some(out)
96
- }
97
- return pipe(
98
- decoders[index](match[index + 1]),
99
- Option.flatMap((value) => decodeCaptures(names, decoders, match, index + 1, Record.set(out, names[index], value)))
100
- )
101
- }
102
-
103
- const escapeRegExp = Str.replace(/[/\\^$*+?.()|[\]{}]/g, "\\$&")
@@ -1,81 +0,0 @@
1
- import { type PickleStep, PickleStepType } from "@cucumber/messages"
2
- import * as Arr from "effect/Array"
3
- import { pipe } from "effect/Function"
4
- import * as Option from "effect/Option"
5
-
6
- /** @internal */
7
- export type StepKind = "Step" | "Given" | "When" | "Then"
8
-
9
- /** @internal */
10
- export type ConcreteStepKind = "Given" | "When" | "Then"
11
-
12
- /** @internal */
13
- export interface MatchableTransition {
14
- readonly kind: StepKind
15
- readonly expression: {
16
- readonly source: string
17
- readonly match: (text: string) => Option.Option<unknown>
18
- }
19
- }
20
-
21
- /** @internal */
22
- export interface MatchedTransition<Transition extends MatchableTransition> {
23
- readonly transition: Transition
24
- readonly captures: unknown
25
- }
26
-
27
- /** @internal */
28
- export const matchingTextTransitions = <Transition extends MatchableTransition>(
29
- transitions: ReadonlyArray<Transition>,
30
- text: string
31
- ): ReadonlyArray<MatchedTransition<Transition>> =>
32
- pipe(
33
- transitions,
34
- Arr.map((transition): Option.Option<MatchedTransition<Transition>> =>
35
- pipe(
36
- transition.expression.match(text),
37
- Option.map((captures) => ({ transition, captures }))
38
- )
39
- ),
40
- Arr.getSomes
41
- )
42
-
43
- /** @internal */
44
- export const matchingKeywordTransitions = <Transition extends MatchableTransition>(
45
- transitions: ReadonlyArray<MatchedTransition<Transition>>,
46
- kind: ConcreteStepKind
47
- ): ReadonlyArray<MatchedTransition<Transition>> =>
48
- Arr.filter(transitions, (match) => keywordMatches(match.transition.kind, kind))
49
-
50
- /** @internal */
51
- export const keywordMatches = (transition: StepKind, keyword: ConcreteStepKind): boolean =>
52
- transition === "Step" || transition === keyword
53
-
54
- /** @internal */
55
- export const concreteStepKind = (step: PickleStep): Option.Option<ConcreteStepKind> => {
56
- switch (step.type) {
57
- case PickleStepType.CONTEXT: {
58
- return Option.some("Given")
59
- }
60
- case PickleStepType.ACTION: {
61
- return Option.some("When")
62
- }
63
- case PickleStepType.OUTCOME: {
64
- return Option.some("Then")
65
- }
66
- default: {
67
- return Option.none()
68
- }
69
- }
70
- }
71
-
72
- /** @internal */
73
- export const renderTransitionKinds = <Transition extends MatchableTransition>(
74
- transitions: ReadonlyArray<Transition>
75
- ): string =>
76
- pipe(
77
- transitions,
78
- Arr.map((transition) => transition.kind),
79
- Arr.dedupe,
80
- Arr.join(", ")
81
- )
@@ -1,155 +0,0 @@
1
- import type {
2
- Background as CucumberBackground,
3
- GherkinDocument,
4
- Pickle,
5
- Rule as CucumberRule,
6
- Scenario as CucumberScenario,
7
- Step as CucumberStep
8
- } from "@cucumber/messages"
9
- import * as Context from "effect/Context"
10
- import * as Effect from "effect/Effect"
11
- import { pipe } from "effect/Function"
12
- import * as Option from "effect/Option"
13
- import { ParseError } from "../Errors.ts"
14
-
15
- /** @internal */
16
- export type Keyword = "Given" | "When" | "Then" | "And" | "But" | "*"
17
-
18
- /** @internal */
19
- export interface CompiledFeature {
20
- readonly name: string
21
- readonly line: number
22
- readonly pickles: ReadonlyArray<Pickle>
23
- readonly source: SourceIndex
24
- }
25
-
26
- /** @internal */
27
- export interface ParsedSource {
28
- readonly document: GherkinDocument
29
- readonly pickles: ReadonlyArray<Pickle>
30
- }
31
-
32
- /** @internal */
33
- export class GherkinCompiler extends Context.Service<GherkinCompiler, {
34
- readonly compile: (source: string, uri: string) => Effect.Effect<ParsedSource, ParseError>
35
- }>()("effect-bdd/GherkinCompiler") {}
36
-
37
- /** @internal */
38
- export interface SourceIndex {
39
- readonly steps: ReadonlyMap<string, CucumberStep>
40
- readonly scenarios: ReadonlyMap<string, {
41
- readonly scenario: CucumberScenario
42
- readonly rule: CucumberRule | undefined
43
- }>
44
- }
45
-
46
- /** @internal */
47
- export const parse = (source: string, uri = "<inline>"): Effect.Effect<CompiledFeature, ParseError, GherkinCompiler> =>
48
- Effect.flatMap(GherkinCompiler, (compiler) => pipe(compiler.compile(source, uri), Effect.flatMap(toFeature)))
49
-
50
- const toFeature: (parsed: ParsedSource) => Effect.Effect<CompiledFeature, ParseError> = Effect.fnUntraced(function*(
51
- parsed
52
- ) {
53
- const feature = parsed.document.feature
54
- if (feature === undefined) {
55
- return yield* parseError("Expected a Feature declaration", 1, 1)
56
- }
57
- if (parsed.pickles.length === 0) {
58
- return yield* parseError("Expected at least one Scenario", feature.location.line, feature.location.column ?? 1)
59
- }
60
-
61
- const source = indexDocument(parsed.document)
62
- return {
63
- name: feature.name,
64
- line: feature.location.line,
65
- pickles: parsed.pickles,
66
- source
67
- }
68
- })
69
-
70
- const indexDocument = (document: GherkinDocument): SourceIndex => {
71
- const steps = new Map<string, CucumberStep>()
72
- const scenarios = new Map<string, { readonly scenario: CucumberScenario; readonly rule: CucumberRule | undefined }>()
73
- const feature = document.feature
74
- if (feature === undefined) {
75
- return { steps, scenarios }
76
- }
77
- for (const child of feature.children) {
78
- if (child.background !== undefined) {
79
- indexBackground(steps, child.background)
80
- }
81
- if (child.scenario !== undefined) {
82
- indexScenario(steps, scenarios, child.scenario, undefined)
83
- }
84
- if (child.rule !== undefined) {
85
- for (const ruleChild of child.rule.children) {
86
- if (ruleChild.background !== undefined) {
87
- indexBackground(steps, ruleChild.background)
88
- }
89
- if (ruleChild.scenario !== undefined) {
90
- indexScenario(steps, scenarios, ruleChild.scenario, child.rule)
91
- }
92
- }
93
- }
94
- }
95
- return { steps, scenarios }
96
- }
97
-
98
- const indexBackground = (steps: Map<string, CucumberStep>, background: CucumberBackground): void => {
99
- for (const step of background.steps) {
100
- steps.set(step.id, step)
101
- }
102
- }
103
-
104
- const indexScenario = (
105
- steps: Map<string, CucumberStep>,
106
- scenarios: Map<string, { readonly scenario: CucumberScenario; readonly rule: CucumberRule | undefined }>,
107
- scenario: CucumberScenario,
108
- rule: CucumberRule | undefined
109
- ): void => {
110
- scenarios.set(scenario.id, { scenario, rule })
111
- for (const step of scenario.steps) {
112
- steps.set(step.id, step)
113
- }
114
- }
115
-
116
- /** @internal */
117
- export const findScenario = (pickle: Pickle, index: SourceIndex) =>
118
- pipe(
119
- pickle.astNodeIds,
120
- Option.liftPredicate((ids): ids is ReadonlyArray<string> => ids.length > 0),
121
- Option.flatMap(() => Option.fromNullishOr(pickle.astNodeIds.find((id) => index.scenarios.has(id)))),
122
- Option.flatMap((id) => Option.fromNullishOr(index.scenarios.get(id)))
123
- )
124
-
125
- /** @internal */
126
- export const findStep = (
127
- pickleStep: { readonly astNodeIds: ReadonlyArray<string> },
128
- index: SourceIndex
129
- ): CucumberStep | undefined =>
130
- pickleStep.astNodeIds
131
- .map((id) => index.steps.get(id))
132
- .find((step) => step !== undefined)
133
-
134
- /** @internal */
135
- export const stepLine = (
136
- step: { readonly astNodeIds: ReadonlyArray<string> },
137
- index: SourceIndex
138
- ): number => findStep(step, index)?.location.line ?? 1
139
-
140
- /** @internal */
141
- export const stepKeyword = (
142
- step: { readonly astNodeIds: ReadonlyArray<string> },
143
- index: SourceIndex
144
- ): Keyword => {
145
- const source = findStep(step, index)
146
- return source === undefined ? "Given" : normalizeKeyword(source.keyword)
147
- }
148
-
149
- const normalizeKeyword = (keyword: string): Keyword => {
150
- const trimmed = keyword.trim()
151
- return trimmed === "*" ? "*" : trimmed as Keyword
152
- }
153
-
154
- const parseError = (message: string, line: number, column: number): Effect.Effect<never, ParseError> =>
155
- Effect.fail(new ParseError({ message, line, column }))