<commit_before>import { AssertionError, ImplementationPendingError, TestCompromisedError, UnknownError } from '@serenity-js/core/lib/errors';
import { ErrorSerialiser, FileSystemLocation, Path } from '@serenity-js/core/lib/io';
import {
    ExecutionCompromised,
    ExecutionFailedWithAssertionError,
    ExecutionFailedWithError,
    ExecutionSkipped,
    ExecutionSuccessful,
    ImplementationPending, Name,
    Outcome,
} from '@serenity-js/core/lib/model';

import { AmbiguousStepDefinitionError } from '../errors';
import { Feature, Hook, ScenarioOutline, Step } from '../gherkin';
import { Scenario } from '../gherkin/model';
import { CucumberFormatterOptions } from './CucumberFormatterOptions';
import { Dependencies } from './Dependencies';

import { ensure, isDefined } from 'tiny-types';

interface Location {
    uri: string;
    line: number;
}

interface StepLocations {
    actionLocation?: Location;
    sourceLocation?: Location;
}

export function cucumberEventProtocolAdapter({ notifier, mapper, cache }: Dependencies) {
    return class CucumberEventProtocolAdapter {

        // note: exported class expression can't have private properties
        public readonly log: any;

        constructor({ eventBroadcaster, log }: CucumberFormatterOptions) {
            this.log = log;

            eventBroadcaster.on('gherkin-document', ({ uri, document }) => {
                ensure('gherkin-document :: uri', uri, isDefined());
                ensure('gherkin-document :: document', document, isDefined());

                const path = new Path(uri);
                cache.set(path, mapper.map(document, path));
            });

            eventBroadcaster.on('test-case-prepared', ({ steps, sourceLocation }: {
                steps: StepLocations[],
                sourceLocation: Location,
            }) => {
                ensure('test-case-prepared :: steps', steps, isDefined());
                ensure('test-case-prepared :: sourceLocation', sourceLocation, isDefined());

                const
                    path = new Path(sourceLocation.uri),
                    map = cache.get(path),
                    scenario = map.get(Scenario).onLine(sourceLocation.line);

                if (!! scenario.outline) {
                    const outline = map.get(ScenarioOutline).onLine(scenario.outline.line);

                    map.set(new ScenarioOutline(
                        outline.location,
                        outline.name,
                        outline.description,
                        interleaveStepsAndHooks(outline.steps, steps),
                        outline.parameters,
                    )).onLine(scenario.outline.line);
                } else {
                    map.set(new Scenario(
                        scenario.location,
                        scenario.name,
                        scenario.description,
                        interleaveStepsAndHooks(scenario.steps, steps),
                        scenario.tags,
                        scenario.outline,
                    )).onLine(sourceLocation.line);
                }
            });

            function interleaveStepsAndHooks(steps: Step[], stepsLocations: StepLocations[]): Array<Step | Hook> {
                const
                    isAHook = (stepLocations: StepLocations) =>
                        stepLocations.actionLocation && ! stepLocations.sourceLocation,
                    matching  = (location: StepLocations) =>
                        (step: Step) =>
                            step.location.path.value === location.sourceLocation.uri && step.location.line === location.sourceLocation.line;

                return stepsLocations.map(location =>
                    isAHook(location)
                        ?   new Hook(new FileSystemLocation(new Path(location.actionLocation.uri), location.actionLocation.line), new Name('Setup'))
                        :   steps.find(matching(location)),
                );
            }

            eventBroadcaster.on('test-case-started', ({ sourceLocation }) => {
                ensure('test-case-started :: sourceLocation', sourceLocation, isDefined());<commit_msg>fix(cucumber): Corrected how feature file paths are compared on Windows<commit_after>import { AssertionError, ImplementationPendingError, TestCompromisedError, UnknownError } from '@serenity-js/core/lib/errors';
import { ErrorSerialiser, FileSystemLocation, Path } from '@serenity-js/core/lib/io';
import {
    ExecutionCompromised,
    ExecutionFailedWithAssertionError,
    ExecutionFailedWithError,
    ExecutionSkipped,
    ExecutionSuccessful,
    ImplementationPending, Name,
    Outcome,
} from '@serenity-js/core/lib/model';

import { AmbiguousStepDefinitionError } from '../errors';
import { Feature, Hook, ScenarioOutline, Step } from '../gherkin';
import { Scenario } from '../gherkin/model';
import { CucumberFormatterOptions } from './CucumberFormatterOptions';
import { Dependencies } from './Dependencies';

import { ensure, isDefined } from 'tiny-types';

interface Location {
    uri: string;
    line: number;
}

interface StepLocations {
    actionLocation?: Location;
    sourceLocation?: Location;
}

export function cucumberEventProtocolAdapter({ notifier, mapper, cache }: Dependencies) {
    return class CucumberEventProtocolAdapter {

        // note: exported class expression can't have private properties
        public readonly log: any;

        constructor({ eventBroadcaster, log }: CucumberFormatterOptions) {
            this.log = log;

            eventBroadcaster.on('gherkin-document', ({ uri, document }) => {
                ensure('gherkin-document :: uri', uri, isDefined());
                ensure('gherkin-document :: document', document, isDefined());

                const path = new Path(uri);
                cache.set(path, mapper.map(document, path));
            });

            eventBroadcaster.on('test-case-prepared', ({ steps, sourceLocation }: {
                steps: StepLocations[],
                sourceLocation: Location,
            }) => {
                ensure('test-case-prepared :: steps', steps, isDefined());
                ensure('test-case-prepared :: sourceLocation', sourceLocation, isDefined());

                const
                    path = new Path(sourceLocation.uri),
                    map = cache.get(path),
                    scenario = map.get(Scenario).onLine(sourceLocation.line);

                if (!! scenario.outline) {
                    const outline = map.get(ScenarioOutline).onLine(scenario.outline.line);

                    map.set(new ScenarioOutline(
                        outline.location,
                        outline.name,
                        outline.description,
                        interleaveStepsAndHooks(outline.steps, steps),
                        outline.parameters,
                    )).onLine(scenario.outline.line);
                } else {
                    map.set(new Scenario(
                        scenario.location,
                        scenario.name,
                        scenario.description,
                        interleaveStepsAndHooks(scenario.steps, steps),
                        scenario.tags,
                        scenario.outline,
                    )).onLine(sourceLocation.line);
                }
            });

            function interleaveStepsAndHooks(steps: Step[], stepsLocations: StepLocations[]): Array<Step | Hook> {
                const
                    isAHook = (stepLocations: StepLocations) =>
                        stepLocations.actionLocation && ! stepLocations.sourceLocation,
                    matching  = (location: StepLocations) =>
                        (step: Step) =>
                            step.location.path.equals(new Path(location.sourceLocation.uri)) &&
                            step.location.line === location.sourceLocation.line;

                return stepsLocations.map(location =>
                    isAHook(location)
                        ?   new Hook(new FileSystemLocation(new Path(location.actionLocation.uri), location.actionLocation.line), new Name('Setup'))
                        :   steps.find(matching(location)),
                );
            }

            eventBroadcaster.on('test-case-started', ({ sourceLocation }) => {
                ensure('test-case-started :: sourceLocation', sourceLocation, isDefined());
{'fraction_non_alphanumeric': 0.10552525492055964, 'fraction_numerical': 0.0, 'mean_word_length': 1.9096240082787168, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4263412', 'n_tokens_mistral': 2128, 'n_tokens_neox': 2000, 'n_words': 402}