Skip to content

Nested capture groups are not passed to custom param definition transformerΒ #225

@notaphplover

Description

@notaphplover

πŸ‘“ What did you see?

When creating a custom param definition, the transformer funcion does not received nested capturing groups.

βœ… What did you expect to see?

As javascript developer, I expect regex capturing groups to conform Regex Ecamscript standard and capture nested groups

πŸ“¦ Which tool/library version are you using?

https://www.npmjs.com/package/@cucumber/cucumber-expressions/v/16.1.2

πŸ”¬ How could we reproduce it?

Create a new npm package with your prefered typescript setup (the bug is also recreated with plain javascript).

In the package.json, install @cucumber/[email protected] dependency

Define a custom parameter with nested captured groups:

import { defineParameterType } from '@cucumber/cucumber';

defineParameterType({
  name: 'foo',
  regexp: /((foo))/,
  transformer: function (
    firstGroup: string,
    secondGroup: string
  ): string[] {
   return [firstGroup, secondGroup]
  },
});

Then, write any step definition and feature involving this custom definition

import { Given, Then, When } from '@cucumber/cucumber';

Given('{foo}', function(foo: string[]): void { console.log(foo) });
When('bar', function(): void {});
Then('baz', function(): void {});
Given foo
When bar
Then baz

Then, create a minimal config testing the feature processing the custom parameter and the step definitions and have a look at the console. Instead of ['foo', 'foo'], ['foo', undefined] is printed instead.

πŸ“š Any additional context?

It seems the capturing groups are internally represented as a tree. Cucumber relies on TreeRegexp to create a GroupBuilder for the purpose of capturing regex groups. These groups are represented as a tree and only the root ones are passed to the custom definition transformer, ignoring any children groups.

Having a look at the Argument class:

public getValue<T>(thisObj: unknown): T | null {
  const groupValues = this.group ? this.group.values : null
  return this.parameterType.transform(thisObj, groupValues)
}

group.values are passed to the transformer:

export default class Group {
  constructor(
    public readonly value: string,
    public readonly start: number | undefined,
    public readonly end: number | undefined,
    public readonly children: readonly Group[]
  ) {}

  get values(): string[] | null {
    return (this.children.length === 0 ? [this] : this.children).map((g) => g.value)
  }
}

As you can see granchildren groups are never included. I would expect nested groups to be included as Ecmascript Regex spec states


This text was originally generated from a template, then edited by hand. You can modify the template here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions