diff --git a/CHANGELOG.md b/CHANGELOG.md
index 07af2286a..41207c8e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,10 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO
### Changed
- Switch from `colors` to `chalk` for terminal coloring ([#1895](https://github.com/cucumber/cucumber-js/pull/1895))
+### Deprecated
+
+- `parseGherkinMessageStream` is deprecated in favour of `loadSources` ([#1957](https://github.com/cucumber/cucumber-js/pull/1957))
+
## [8.0.0-rc.2] - 2022-01-10
### Added
- Export cucumber version number. It is now possible to retrieve the current version
diff --git a/docs/api/cucumber.iloadsourcesresult.errors.md b/docs/api/cucumber.iloadsourcesresult.errors.md
new file mode 100644
index 000000000..9f98a5f87
--- /dev/null
+++ b/docs/api/cucumber.iloadsourcesresult.errors.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [ILoadSourcesResult](./cucumber.iloadsourcesresult.md) > [errors](./cucumber.iloadsourcesresult.errors.md)
+
+## ILoadSourcesResult.errors property
+
+Signature:
+
+```typescript
+errors: ISourcesError[];
+```
diff --git a/docs/api/cucumber.iloadsourcesresult.md b/docs/api/cucumber.iloadsourcesresult.md
new file mode 100644
index 000000000..730105f5f
--- /dev/null
+++ b/docs/api/cucumber.iloadsourcesresult.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [ILoadSourcesResult](./cucumber.iloadsourcesresult.md)
+
+## ILoadSourcesResult interface
+
+
+Signature:
+
+```typescript
+export interface ILoadSourcesResult
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [errors](./cucumber.iloadsourcesresult.errors.md) | [ISourcesError](./cucumber.isourceserror.md)\[\] | |
+| [plan](./cucumber.iloadsourcesresult.plan.md) | [IPlannedPickle](./cucumber.iplannedpickle.md)\[\] | |
+
diff --git a/docs/api/cucumber.iloadsourcesresult.plan.md b/docs/api/cucumber.iloadsourcesresult.plan.md
new file mode 100644
index 000000000..8847c0ed6
--- /dev/null
+++ b/docs/api/cucumber.iloadsourcesresult.plan.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [ILoadSourcesResult](./cucumber.iloadsourcesresult.md) > [plan](./cucumber.iloadsourcesresult.plan.md)
+
+## ILoadSourcesResult.plan property
+
+Signature:
+
+```typescript
+plan: IPlannedPickle[];
+```
diff --git a/docs/api/cucumber.iplannedpickle.location.md b/docs/api/cucumber.iplannedpickle.location.md
new file mode 100644
index 000000000..4aee3845b
--- /dev/null
+++ b/docs/api/cucumber.iplannedpickle.location.md
@@ -0,0 +1,14 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [IPlannedPickle](./cucumber.iplannedpickle.md) > [location](./cucumber.iplannedpickle.location.md)
+
+## IPlannedPickle.location property
+
+Signature:
+
+```typescript
+location: {
+ line: number;
+ column?: number;
+ };
+```
diff --git a/docs/api/cucumber.iplannedpickle.md b/docs/api/cucumber.iplannedpickle.md
new file mode 100644
index 000000000..4c6144682
--- /dev/null
+++ b/docs/api/cucumber.iplannedpickle.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [IPlannedPickle](./cucumber.iplannedpickle.md)
+
+## IPlannedPickle interface
+
+
+Signature:
+
+```typescript
+export interface IPlannedPickle
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [location](./cucumber.iplannedpickle.location.md) | { line: number; column?: number; } | |
+| [name](./cucumber.iplannedpickle.name.md) | string | |
+| [uri](./cucumber.iplannedpickle.uri.md) | string | |
+
diff --git a/docs/api/cucumber.iplannedpickle.name.md b/docs/api/cucumber.iplannedpickle.name.md
new file mode 100644
index 000000000..b09ab16b9
--- /dev/null
+++ b/docs/api/cucumber.iplannedpickle.name.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [IPlannedPickle](./cucumber.iplannedpickle.md) > [name](./cucumber.iplannedpickle.name.md)
+
+## IPlannedPickle.name property
+
+Signature:
+
+```typescript
+name: string;
+```
diff --git a/docs/api/cucumber.iplannedpickle.uri.md b/docs/api/cucumber.iplannedpickle.uri.md
new file mode 100644
index 000000000..905aefb67
--- /dev/null
+++ b/docs/api/cucumber.iplannedpickle.uri.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [IPlannedPickle](./cucumber.iplannedpickle.md) > [uri](./cucumber.iplannedpickle.uri.md)
+
+## IPlannedPickle.uri property
+
+Signature:
+
+```typescript
+uri: string;
+```
diff --git a/docs/api/cucumber.isourceserror.location.md b/docs/api/cucumber.isourceserror.location.md
new file mode 100644
index 000000000..eb37941cd
--- /dev/null
+++ b/docs/api/cucumber.isourceserror.location.md
@@ -0,0 +1,14 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [ISourcesError](./cucumber.isourceserror.md) > [location](./cucumber.isourceserror.location.md)
+
+## ISourcesError.location property
+
+Signature:
+
+```typescript
+location: {
+ line: number;
+ column?: number;
+ };
+```
diff --git a/docs/api/cucumber.isourceserror.md b/docs/api/cucumber.isourceserror.md
new file mode 100644
index 000000000..7e5f4fc3a
--- /dev/null
+++ b/docs/api/cucumber.isourceserror.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [ISourcesError](./cucumber.isourceserror.md)
+
+## ISourcesError interface
+
+
+Signature:
+
+```typescript
+export interface ISourcesError
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [location](./cucumber.isourceserror.location.md) | { line: number; column?: number; } | |
+| [message](./cucumber.isourceserror.message.md) | string | |
+| [uri](./cucumber.isourceserror.uri.md) | string | |
+
diff --git a/docs/api/cucumber.isourceserror.message.md b/docs/api/cucumber.isourceserror.message.md
new file mode 100644
index 000000000..42962131e
--- /dev/null
+++ b/docs/api/cucumber.isourceserror.message.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [ISourcesError](./cucumber.isourceserror.md) > [message](./cucumber.isourceserror.message.md)
+
+## ISourcesError.message property
+
+Signature:
+
+```typescript
+message: string;
+```
diff --git a/docs/api/cucumber.isourceserror.uri.md b/docs/api/cucumber.isourceserror.uri.md
new file mode 100644
index 000000000..5a27920dc
--- /dev/null
+++ b/docs/api/cucumber.isourceserror.uri.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [ISourcesError](./cucumber.isourceserror.md) > [uri](./cucumber.isourceserror.uri.md)
+
+## ISourcesError.uri property
+
+Signature:
+
+```typescript
+uri: string;
+```
diff --git a/docs/api/cucumber.loadsources.md b/docs/api/cucumber.loadsources.md
new file mode 100644
index 000000000..884c7edfb
--- /dev/null
+++ b/docs/api/cucumber.loadsources.md
@@ -0,0 +1,25 @@
+
+
+[Home](./index.md) > [@cucumber/cucumber](./cucumber.md) > [loadSources](./cucumber.loadsources.md)
+
+## loadSources() function
+
+Load and parse features, produce a filtered and ordered test plan and/or parse errors.
+
+Signature:
+
+```typescript
+export declare function loadSources(coordinates: ISourcesCoordinates, environment?: IRunEnvironment): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| coordinates | [ISourcesCoordinates](./cucumber.isourcescoordinates.md) | Coordinates required to find features |
+| environment | [IRunEnvironment](./cucumber.irunenvironment.md) | Project environment. |
+
+Returns:
+
+Promise<[ILoadSourcesResult](./cucumber.iloadsourcesresult.md)>
+
diff --git a/docs/api/cucumber.md b/docs/api/cucumber.md
index 915718081..afd0e5813 100644
--- a/docs/api/cucumber.md
+++ b/docs/api/cucumber.md
@@ -15,6 +15,7 @@ These docs cover the API used for running Cucumber programmatically. The entry p
| Function | Description |
| --- | --- |
| [loadConfiguration(options, environment)](./cucumber.loadconfiguration.md) | Load user-authored configuration to be used in a test run. |
+| [loadSources(coordinates, environment)](./cucumber.loadsources.md) | Load and parse features, produce a filtered and ordered test plan and/or parse errors. |
| [loadSupport(options, environment)](./cucumber.loadsupport.md) | Load support code for use in test runs. |
| [runCucumber(configuration, environment, onMessage)](./cucumber.runcucumber.md) | Execute a Cucumber test run. |
@@ -23,7 +24,9 @@ These docs cover the API used for running Cucumber programmatically. The entry p
| Interface | Description |
| --- | --- |
| [ILoadConfigurationOptions](./cucumber.iloadconfigurationoptions.md) | |
+| [ILoadSourcesResult](./cucumber.iloadsourcesresult.md) | |
| [ILoadSupportOptions](./cucumber.iloadsupportoptions.md) | |
+| [IPlannedPickle](./cucumber.iplannedpickle.md) | |
| [IResolvedConfiguration](./cucumber.iresolvedconfiguration.md) | |
| [IRunConfiguration](./cucumber.irunconfiguration.md) | |
| [IRunEnvironment](./cucumber.irunenvironment.md) | Contextual data about the project environment. |
@@ -32,6 +35,7 @@ These docs cover the API used for running Cucumber programmatically. The entry p
| [IRunOptionsRuntime](./cucumber.irunoptionsruntime.md) | |
| [IRunResult](./cucumber.irunresult.md) | Result of a Cucumber test run. |
| [ISourcesCoordinates](./cucumber.isourcescoordinates.md) | |
+| [ISourcesError](./cucumber.isourceserror.md) | |
| [ISupportCodeCoordinates](./cucumber.isupportcodecoordinates.md) | |
## Type Aliases
diff --git a/docs/javascript_api.md b/docs/javascript_api.md
index a533096d2..a888e7125 100644
--- a/docs/javascript_api.md
+++ b/docs/javascript_api.md
@@ -35,3 +35,17 @@ export async function runTests(directory, configFile, failFast) {
return success
}
```
+
+## Sources example
+
+You can use the `loadSources` function to load and parse your feature files, calculate the test plan (accounting for filtering and ordering) and report any parse errors:
+
+```javascript
+import { loadConfiguration, loadSources } from '@cucumber/cucumber/api'
+
+export async function calculatePlan() {
+ const { runConfiguration } = await loadConfiguration()
+ const { plan } = await loadSources(runConfiguration.sources)
+ return plan
+}
+```
diff --git a/package-lock.json b/package-lock.json
index be449c1b0..df72d27cc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"@cucumber/cucumber-expressions": "15.0.2",
"@cucumber/gherkin": "22.0.0",
"@cucumber/gherkin-streams": "4.0.0",
+ "@cucumber/gherkin-utils": "^7.0.0",
"@cucumber/html-formatter": "18.0.0",
"@cucumber/messages": "17.1.1",
"@cucumber/tag-expressions": "4.1.0",
@@ -589,6 +590,27 @@
"node": ">= 12"
}
},
+ "node_modules/@cucumber/gherkin-utils": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@cucumber/gherkin-utils/-/gherkin-utils-7.0.0.tgz",
+ "integrity": "sha512-tDkSRITTPA6Df501doqeRH3+1jAM4ls6+tlFEVvkvuzTH3C8DXwQ5xBPWmUNmDhR/gJeZ+yj7gDRbDWr7Qc6Zw==",
+ "dependencies": {
+ "@cucumber/messages": "^17.1.0",
+ "@teppeis/multimaps": "2.0.0",
+ "commander": "8.1.0"
+ },
+ "bin": {
+ "gherkin-utils": "bin/gherkin-utils"
+ }
+ },
+ "node_modules/@cucumber/gherkin-utils/node_modules/commander": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.1.0.tgz",
+ "integrity": "sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/@cucumber/html-formatter": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/@cucumber/html-formatter/-/html-formatter-18.0.0.tgz",
@@ -1185,7 +1207,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@teppeis/multimaps/-/multimaps-2.0.0.tgz",
"integrity": "sha512-TL1adzq1HdxUf9WYduLcQ/DNGYiz71U31QRgbnr0Ef1cPyOUOsBojxHVWpFeOSUucB6Lrs0LxFRA14ntgtkc9w==",
- "dev": true,
"engines": {
"node": ">=10.17"
}
@@ -8052,6 +8073,23 @@
}
}
},
+ "@cucumber/gherkin-utils": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@cucumber/gherkin-utils/-/gherkin-utils-7.0.0.tgz",
+ "integrity": "sha512-tDkSRITTPA6Df501doqeRH3+1jAM4ls6+tlFEVvkvuzTH3C8DXwQ5xBPWmUNmDhR/gJeZ+yj7gDRbDWr7Qc6Zw==",
+ "requires": {
+ "@cucumber/messages": "^17.1.0",
+ "@teppeis/multimaps": "2.0.0",
+ "commander": "8.1.0"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.1.0.tgz",
+ "integrity": "sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA=="
+ }
+ }
+ },
"@cucumber/html-formatter": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/@cucumber/html-formatter/-/html-formatter-18.0.0.tgz",
@@ -8553,8 +8591,7 @@
"@teppeis/multimaps": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@teppeis/multimaps/-/multimaps-2.0.0.tgz",
- "integrity": "sha512-TL1adzq1HdxUf9WYduLcQ/DNGYiz71U31QRgbnr0Ef1cPyOUOsBojxHVWpFeOSUucB6Lrs0LxFRA14ntgtkc9w==",
- "dev": true
+ "integrity": "sha512-TL1adzq1HdxUf9WYduLcQ/DNGYiz71U31QRgbnr0Ef1cPyOUOsBojxHVWpFeOSUucB6Lrs0LxFRA14ntgtkc9w=="
},
"@tsconfig/node10": {
"version": "1.0.8",
diff --git a/package.json b/package.json
index 79686baa6..903f9af1a 100644
--- a/package.json
+++ b/package.json
@@ -194,6 +194,7 @@
"@cucumber/cucumber-expressions": "15.0.2",
"@cucumber/gherkin": "22.0.0",
"@cucumber/gherkin-streams": "4.0.0",
+ "@cucumber/gherkin-utils": "^7.0.0",
"@cucumber/html-formatter": "18.0.0",
"@cucumber/messages": "17.1.1",
"@cucumber/tag-expressions": "4.1.0",
diff --git a/reports/cucumber.api.md b/reports/cucumber.api.md
index 852bec264..1d99b74a2 100644
--- a/reports/cucumber.api.md
+++ b/reports/cucumber.api.md
@@ -24,6 +24,14 @@ export interface ILoadConfigurationOptions {
provided?: Partial;
}
+// @public (undocumented)
+export interface ILoadSourcesResult {
+ // (undocumented)
+ errors: ISourcesError[];
+ // (undocumented)
+ plan: IPlannedPickle[];
+}
+
// @public (undocumented)
export interface ILoadSupportOptions {
// (undocumented)
@@ -32,6 +40,19 @@ export interface ILoadSupportOptions {
support: ISupportCodeCoordinates;
}
+// @public (undocumented)
+export interface IPlannedPickle {
+ // (undocumented)
+ location: {
+ line: number;
+ column?: number;
+ };
+ // (undocumented)
+ name: string;
+ // (undocumented)
+ uri: string;
+}
+
// @public (undocumented)
export interface IResolvedConfiguration {
runConfiguration: IRunConfiguration;
@@ -119,6 +140,19 @@ export interface ISourcesCoordinates {
tagExpression: string;
}
+// @public (undocumented)
+export interface ISourcesError {
+ // (undocumented)
+ location: {
+ line: number;
+ column?: number;
+ };
+ // (undocumented)
+ message: string;
+ // (undocumented)
+ uri: string;
+}
+
// @public (undocumented)
export interface ISupportCodeCoordinates {
// (undocumented)
@@ -135,6 +169,9 @@ export type ISupportCodeCoordinatesOrLibrary = ISupportCodeCoordinates | ISuppor
// @public
export function loadConfiguration(options?: ILoadConfigurationOptions, environment?: IRunEnvironment): Promise;
+// @public
+export function loadSources(coordinates: ISourcesCoordinates, environment?: IRunEnvironment): Promise;
+
// @public
export function loadSupport(options: ILoadSupportOptions, environment?: IRunEnvironment): Promise;
diff --git a/src/api/gherkin.ts b/src/api/gherkin.ts
new file mode 100644
index 000000000..8fd19c1f2
--- /dev/null
+++ b/src/api/gherkin.ts
@@ -0,0 +1,106 @@
+import {
+ GherkinStreams,
+ IGherkinStreamOptions,
+} from '@cucumber/gherkin-streams'
+import {
+ Envelope,
+ GherkinDocument,
+ IdGenerator,
+ Location,
+ ParseError,
+ Pickle,
+} from '@cucumber/messages'
+import { Query as GherkinQuery } from '@cucumber/gherkin-utils'
+import PickleFilter from '../pickle_filter'
+import { orderPickles } from '../cli/helpers'
+import { ISourcesCoordinates } from './types'
+
+interface PickleWithDocument {
+ gherkinDocument: GherkinDocument
+ location: Location
+ pickle: Pickle
+}
+
+export async function getFilteredPicklesAndErrors({
+ newId,
+ cwd,
+ logger,
+ unexpandedFeaturePaths,
+ featurePaths,
+ coordinates,
+ onEnvelope,
+}: {
+ newId: IdGenerator.NewId
+ cwd: string
+ logger: Console
+ unexpandedFeaturePaths: string[]
+ featurePaths: string[]
+ coordinates: ISourcesCoordinates
+ onEnvelope?: (envelope: Envelope) => void
+}): Promise<{
+ filteredPickles: PickleWithDocument[]
+ parseErrors: ParseError[]
+}> {
+ const gherkinQuery = new GherkinQuery()
+ const parseErrors: ParseError[] = []
+ await gherkinFromPaths(
+ featurePaths,
+ {
+ newId,
+ relativeTo: cwd,
+ defaultDialect: coordinates.defaultDialect,
+ },
+ (envelope) => {
+ gherkinQuery.update(envelope)
+ if (envelope.parseError) {
+ parseErrors.push(envelope.parseError)
+ }
+ onEnvelope?.(envelope)
+ }
+ )
+ const pickleFilter = new PickleFilter({
+ cwd,
+ featurePaths: unexpandedFeaturePaths,
+ names: coordinates.names,
+ tagExpression: coordinates.tagExpression,
+ })
+ const filteredPickles: PickleWithDocument[] = gherkinQuery
+ .getPickles()
+ .filter((pickle) => {
+ const gherkinDocument = gherkinQuery
+ .getGherkinDocuments()
+ .find((doc) => doc.uri === pickle.uri)
+ return pickleFilter.matches({ gherkinDocument, pickle })
+ })
+ .map((pickle) => {
+ const gherkinDocument = gherkinQuery
+ .getGherkinDocuments()
+ .find((doc) => doc.uri === pickle.uri)
+ const location = gherkinQuery.getLocation(
+ pickle.astNodeIds[pickle.astNodeIds.length - 1]
+ )
+ return {
+ gherkinDocument,
+ location,
+ pickle,
+ }
+ })
+ orderPickles(filteredPickles, coordinates.order, logger)
+ return {
+ filteredPickles,
+ parseErrors,
+ }
+}
+
+async function gherkinFromPaths(
+ paths: string[],
+ options: IGherkinStreamOptions,
+ onEnvelope: (envelope: Envelope) => void
+): Promise {
+ return new Promise((resolve, reject) => {
+ const gherkinMessageStream = GherkinStreams.fromPaths(paths, options)
+ gherkinMessageStream.on('data', onEnvelope)
+ gherkinMessageStream.on('end', resolve)
+ gherkinMessageStream.on('error', reject)
+ })
+}
diff --git a/src/api/index.ts b/src/api/index.ts
index 51dbcef03..ddf849e54 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -6,6 +6,7 @@
*/
export * from './load_configuration'
+export * from './load_sources'
export * from './load_support'
export * from './run_cucumber'
export * from './types'
diff --git a/src/api/load_sources.ts b/src/api/load_sources.ts
new file mode 100644
index 000000000..74e18c683
--- /dev/null
+++ b/src/api/load_sources.ts
@@ -0,0 +1,64 @@
+import {
+ ILoadSourcesResult,
+ IPlannedPickle,
+ IRunEnvironment,
+ ISourcesCoordinates,
+ ISourcesError,
+} from './types'
+import { resolvePaths } from './paths'
+import { IdGenerator } from '@cucumber/messages'
+import { Console } from 'console'
+import { mergeEnvironment } from './environment'
+import { getFilteredPicklesAndErrors } from './gherkin'
+
+/**
+ * Load and parse features, produce a filtered and ordered test plan and/or parse errors.
+ *
+ * @public
+ * @param coordinates - Coordinates required to find features
+ * @param environment - Project environment.
+ */
+export async function loadSources(
+ coordinates: ISourcesCoordinates,
+ environment: IRunEnvironment = {}
+): Promise {
+ const { cwd, stderr } = mergeEnvironment(environment)
+ const logger = new Console(stderr)
+ const newId = IdGenerator.uuid()
+ const { unexpandedFeaturePaths, featurePaths } = await resolvePaths(
+ cwd,
+ coordinates
+ )
+ if (featurePaths.length === 0) {
+ return {
+ plan: [],
+ errors: [],
+ }
+ }
+ const { filteredPickles, parseErrors } = await getFilteredPicklesAndErrors({
+ newId,
+ cwd,
+ logger,
+ unexpandedFeaturePaths,
+ featurePaths,
+ coordinates,
+ })
+ const plan: IPlannedPickle[] = filteredPickles.map(
+ ({ location, pickle }) => ({
+ name: pickle.name,
+ uri: pickle.uri,
+ location,
+ })
+ )
+ const errors: ISourcesError[] = parseErrors.map(({ source, message }) => {
+ return {
+ uri: source.uri,
+ location: source.location,
+ message,
+ }
+ })
+ return {
+ plan,
+ errors,
+ }
+}
diff --git a/src/api/load_sources_spec.ts b/src/api/load_sources_spec.ts
new file mode 100644
index 000000000..f43c88986
--- /dev/null
+++ b/src/api/load_sources_spec.ts
@@ -0,0 +1,149 @@
+import { expect } from 'chai'
+import path from 'path'
+import { PassThrough } from 'stream'
+import fs from 'mz/fs'
+import { IdGenerator } from '@cucumber/messages'
+import { IRunEnvironment } from './types'
+import { loadSources } from './load_sources'
+
+const newId = IdGenerator.uuid()
+
+async function setupEnvironment(): Promise> {
+ const cwd = path.join(__dirname, '..', '..', 'tmp', `loadSources_${newId()}`)
+ await fs.mkdir(path.join(cwd, 'features'), { recursive: true })
+ await fs.writeFile(
+ path.join(cwd, 'features', 'test.feature'),
+ `@tag1
+Feature: test fixture
+ Scenario: one
+ Given a step
+ Then another step
+
+ @tag2
+ Scenario: two
+ Given a step
+ Then another step
+
+ Scenario: three
+ Given a step
+ Then another step
+
+ Scenario Outline: four with
+ Given a step
+ Then another step\`
+ Examples:
+ | param |
+ | foo |
+ | bar |`
+ )
+ const stdout = new PassThrough()
+ return { cwd, stdout }
+}
+
+describe('loadSources', () => {
+ it('should produce a plan with all pickles', async () => {
+ const environment = await setupEnvironment()
+ const { plan } = await loadSources(
+ {
+ defaultDialect: 'en',
+ order: 'defined',
+ paths: [],
+ names: [],
+ tagExpression: '',
+ },
+ environment
+ )
+ expect(
+ plan.map((planned) => ({
+ ...planned,
+ uri: planned.uri.replace(/\\/g, '/'),
+ }))
+ ).to.deep.eq([
+ {
+ name: 'one',
+ uri: 'features/test.feature',
+ location: {
+ line: 3,
+ column: 3,
+ },
+ },
+ {
+ name: 'two',
+ uri: 'features/test.feature',
+ location: {
+ line: 8,
+ column: 3,
+ },
+ },
+ {
+ name: 'three',
+ uri: 'features/test.feature',
+ location: {
+ line: 12,
+ column: 3,
+ },
+ },
+ {
+ name: 'four with foo',
+ uri: 'features/test.feature',
+ location: {
+ line: 21,
+ column: 5,
+ },
+ },
+ {
+ name: 'four with bar',
+ uri: 'features/test.feature',
+ location: {
+ line: 22,
+ column: 5,
+ },
+ },
+ ])
+ })
+
+ it('should produce a plan with pickles filtered by path:line', async () => {
+ const environment = await setupEnvironment()
+ const { plan } = await loadSources(
+ {
+ defaultDialect: 'en',
+ order: 'defined',
+ paths: ['features/test.feature:8'],
+ names: [],
+ tagExpression: '',
+ },
+ environment
+ )
+ expect(plan.map((pickle) => pickle.name)).to.deep.eq(['two'])
+ })
+
+ it('should produce a plan with pickles filtered by name', async () => {
+ const environment = await setupEnvironment()
+ const { plan } = await loadSources(
+ {
+ defaultDialect: 'en',
+ order: 'defined',
+ paths: [],
+ names: ['two'],
+ tagExpression: '',
+ },
+ environment
+ )
+ expect(plan.map((pickle) => pickle.name)).to.deep.eq(['two'])
+ })
+
+ it('should produce a plan with pickles filtered by tags', async () => {
+ const environment = await setupEnvironment()
+ const { plan } = await loadSources(
+ {
+ defaultDialect: 'en',
+ order: 'defined',
+ paths: [],
+ names: [],
+ tagExpression: '@tag2',
+ },
+ environment
+ )
+ expect(plan.map((pickle) => pickle.name)).to.deep.eq(['two'])
+ })
+})
diff --git a/src/api/paths.ts b/src/api/paths.ts
index f6ecba74f..a92defe23 100644
--- a/src/api/paths.ts
+++ b/src/api/paths.ts
@@ -7,7 +7,11 @@ import { ISourcesCoordinates, ISupportCodeCoordinates } from './types'
export async function resolvePaths(
cwd: string,
sources: Pick,
- support: ISupportCodeCoordinates
+ support: ISupportCodeCoordinates = {
+ requireModules: [],
+ requirePaths: [],
+ importPaths: [],
+ }
): Promise<{
unexpandedFeaturePaths: string[]
featurePaths: string[]
diff --git a/src/api/run_cucumber.ts b/src/api/run_cucumber.ts
index f279c41c5..cf5faa05c 100644
--- a/src/api/run_cucumber.ts
+++ b/src/api/run_cucumber.ts
@@ -1,22 +1,15 @@
import { Envelope, IdGenerator, ParseError } from '@cucumber/messages'
import { EventEmitter } from 'events'
import { EventDataCollector } from '../formatter/helpers'
-import {
- emitMetaMessage,
- emitSupportCodeMessages,
- parseGherkinMessageStream,
-} from '../cli/helpers'
-import { GherkinStreams } from '@cucumber/gherkin-streams'
-import PickleFilter from '../pickle_filter'
+import { emitMetaMessage, emitSupportCodeMessages } from '../cli/helpers'
import { IRunOptions, IRunEnvironment, IRunResult } from './types'
import { resolvePaths } from './paths'
import { makeRuntime } from './runtime'
import { initializeFormatters } from './formatters'
import { getSupportCodeLibrary } from './support'
import { Console } from 'console'
-import * as messages from '@cucumber/messages'
-import { doesHaveValue } from '../value_checker'
import { mergeEnvironment } from './environment'
+import { getFilteredPicklesAndErrors } from './gherkin'
/**
* Execute a Cucumber test run.
@@ -73,35 +66,21 @@ export async function runCucumber(
})
await emitMetaMessage(eventBroadcaster, env)
- const gherkinMessageStream = GherkinStreams.fromPaths(featurePaths, {
- defaultDialect: configuration.sources.defaultDialect,
- newId,
- relativeTo: cwd,
- })
let pickleIds: string[] = []
- const parseErrors: ParseError[] = []
- gherkinMessageStream.on('data', (envelope: messages.Envelope) => {
- if (doesHaveValue(envelope.parseError)) {
- parseErrors.push(envelope.parseError)
- }
- })
-
+ let parseErrors: ParseError[] = []
if (featurePaths.length > 0) {
- pickleIds = await parseGherkinMessageStream({
+ const gherkinResult = await getFilteredPicklesAndErrors({
+ newId,
+ cwd,
logger,
- eventBroadcaster,
- eventDataCollector,
- gherkinMessageStream,
- order: configuration.sources.order ?? 'defined',
- pickleFilter: new PickleFilter({
- cwd,
- featurePaths: unexpandedFeaturePaths,
- names: configuration.sources.names,
- tagExpression: configuration.sources.tagExpression,
- }),
+ unexpandedFeaturePaths,
+ featurePaths,
+ coordinates: configuration.sources,
+ onEnvelope: (envelope) => eventBroadcaster.emit('envelope', envelope),
})
+ pickleIds = gherkinResult.filteredPickles.map(({ pickle }) => pickle.id)
+ parseErrors = gherkinResult.parseErrors
}
-
if (parseErrors.length) {
parseErrors.forEach((parseError) => {
logger.error(
diff --git a/src/api/types.ts b/src/api/types.ts
index 576428a32..046cb70b1 100644
--- a/src/api/types.ts
+++ b/src/api/types.ts
@@ -47,6 +47,38 @@ export interface ISourcesCoordinates {
order: PickleOrder
}
+/**
+ * @public
+ */
+export interface IPlannedPickle {
+ name: string
+ uri: string
+ location: {
+ line: number
+ column?: number
+ }
+}
+
+/**
+ * @public
+ */
+export interface ISourcesError {
+ uri: string
+ location: {
+ line: number
+ column?: number
+ }
+ message: string
+}
+
+/**
+ * @public
+ */
+export interface ILoadSourcesResult {
+ plan: IPlannedPickle[]
+ errors: ISourcesError[]
+}
+
/**
* @public
*/
diff --git a/src/api/wrapper.mjs b/src/api/wrapper.mjs
index f34a7ca7a..7f1119497 100644
--- a/src/api/wrapper.mjs
+++ b/src/api/wrapper.mjs
@@ -2,4 +2,5 @@ import api from './index.js'
export const loadConfiguration = api.loadConfiguration
export const loadSupport = api.loadSupport
+export const loadSources = api.loadSources
export const runCucumber = api.runCucumber
diff --git a/src/cli/helpers.ts b/src/cli/helpers.ts
index 1a1290d8f..1d54419f6 100644
--- a/src/cli/helpers.ts
+++ b/src/cli/helpers.ts
@@ -17,16 +17,26 @@ import { builtinParameterTypes } from '../support_code_library_builder'
import { version } from '../version'
interface IParseGherkinMessageStreamRequest {
- logger: Console
+ cwd?: string
eventBroadcaster: EventEmitter
eventDataCollector: EventDataCollector
gherkinMessageStream: Readable
- order: PickleOrder
+ order: string
pickleFilter: PickleFilter
}
+/**
+ * Process a stream of envelopes from Gherkin and resolve to an array of filtered, ordered pickle Ids
+ *
+ * @deprecated use `loadSources` instead
+ *
+ * @param eventBroadcaster
+ * @param eventDataCollector
+ * @param gherkinMessageStream
+ * @param order
+ * @param pickleFilter
+ */
export async function parseGherkinMessageStream({
- logger,
eventBroadcaster,
eventDataCollector,
gherkinMessageStream,
@@ -49,7 +59,7 @@ export async function parseGherkinMessageStream({
}
})
gherkinMessageStream.on('end', () => {
- orderPickleIds(result, order, logger)
+ orderPickles(result, order, console)
resolve(result)
})
gherkinMessageStream.on('error', reject)
@@ -57,8 +67,8 @@ export async function parseGherkinMessageStream({
}
// Orders the pickleIds in place - morphs input
-export function orderPickleIds(
- pickleIds: string[],
+export function orderPickles(
+ pickleIds: T[],
order: PickleOrder,
logger: Console
): void {
diff --git a/src/cli/helpers_spec.ts b/src/cli/helpers_spec.ts
index a412700b8..214269d60 100644
--- a/src/cli/helpers_spec.ts
+++ b/src/cli/helpers_spec.ts
@@ -47,7 +47,6 @@ async function testParseGherkinMessageStream(
eventBroadcaster.on('envelope', (e) => envelopes.push(e))
const eventDataCollector = new EventDataCollector(eventBroadcaster)
const result = await parseGherkinMessageStream({
- logger: console,
eventBroadcaster,
eventDataCollector,
gherkinMessageStream: options.gherkinMessageStream,