Skip to content

Commit fad3283

Browse files
authored
feat: Support parsing all describable nodes in gherkin-32 mode (#361)
Historically, we have not supported parsing descriptions as a separate concept for Examples, Background, Scenario or Scenario Outline. Text following the keyword line was instead parsed as a multi-line title. This PR implements support for instead capturing these as standalone properties when running in cucumber/gherkin compatibility mode. This borrowed from and builds on work done by @jojo1981 in #254, but started from a clean branch as there have been a lot of changes since that PR was opened. Fixes #154 #211
1 parent 3617005 commit fad3283

File tree

51 files changed

+856
-134
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+856
-134
lines changed

src/Filter/LineFilter.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
namespace Behat\Gherkin\Filter;
1212

13-
use Behat\Gherkin\Node\ExampleTableNode;
1413
use Behat\Gherkin\Node\FeatureNode;
1514
use Behat\Gherkin\Node\OutlineNode;
1615
use Behat\Gherkin\Node\ScenarioInterface;
@@ -95,9 +94,7 @@ public function filterFeature(FeatureNode $feature)
9594
$filteredTable[$this->filterLine] = $table[$this->filterLine];
9695
}
9796

98-
$scenario = $scenario->withTables(
99-
[new ExampleTableNode($filteredTable, $exampleTable->getKeyword(), $exampleTable->getTags())],
100-
);
97+
$scenario = $scenario->withTables([$exampleTable->withTable($filteredTable)]);
10198
}
10299
}
103100
}

src/Filter/LineRangeFilter.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
namespace Behat\Gherkin\Filter;
1212

13-
use Behat\Gherkin\Node\ExampleTableNode;
1413
use Behat\Gherkin\Node\FeatureNode;
1514
use Behat\Gherkin\Node\OutlineNode;
1615
use Behat\Gherkin\Node\ScenarioInterface;
@@ -111,7 +110,7 @@ public function filterFeature(FeatureNode $feature)
111110
}
112111

113112
if (count($filteredTable) > 1) {
114-
$exampleTableNodes[] = new ExampleTableNode($filteredTable, $exampleTable->getKeyword(), $exampleTable->getTags());
113+
$exampleTableNodes[] = $exampleTable->withTable($filteredTable);
115114
}
116115
}
117116

src/Filter/NameFilter.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
namespace Behat\Gherkin\Filter;
1212

13+
use Behat\Gherkin\Node\DescribableNodeInterface;
1314
use Behat\Gherkin\Node\FeatureNode;
1415
use Behat\Gherkin\Node\ScenarioInterface;
1516

@@ -59,15 +60,25 @@ public function isFeatureMatch(FeatureNode $feature)
5960
*/
6061
public function isScenarioMatch(ScenarioInterface $scenario)
6162
{
62-
if ($scenario->getTitle() === null) {
63+
// Historically (and in legacy GherkinCompatibilityMode), multiline scenario text was all part of the title.
64+
// In new GherkinCompatibilityMode the text will be split into a single-line title & multiline description.
65+
// For BC, this filter should continue to match on the complete multiline text value.
66+
$textParts = array_filter([
67+
$scenario->getTitle(),
68+
$scenario instanceof DescribableNodeInterface ? $scenario->getDescription() : null,
69+
]);
70+
71+
if ($textParts === []) {
6372
return false;
6473
}
6574

66-
if ($this->filterString[0] === '/' && preg_match($this->filterString, $scenario->getTitle())) {
75+
$textToMatch = implode("\n", $textParts);
76+
77+
if ($this->filterString[0] === '/' && preg_match($this->filterString, $textToMatch)) {
6778
return true;
6879
}
6980

70-
if (str_contains($scenario->getTitle(), $this->filterString)) {
81+
if (str_contains($textToMatch, $this->filterString)) {
7182
return true;
7283
}
7384

src/GherkinCompatibilityMode.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,25 @@ public function shouldRemoveStepKeywordSpace(): bool
3535
/**
3636
* @internal
3737
*/
38-
public function shouldRemoveFeatureDescriptionPadding(): bool
38+
public function shouldRemoveDescriptionPadding(): bool
3939
{
4040
return match ($this) {
4141
self::LEGACY => true,
4242
default => false,
4343
};
4444
}
4545

46+
/**
47+
* @internal
48+
*/
49+
public function allowAllNodeDescriptions(): bool
50+
{
51+
return match ($this) {
52+
self::LEGACY => false,
53+
default => true,
54+
};
55+
}
56+
4657
/**
4758
* @internal
4859
*/

src/Lexer.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class Lexer
8080
private bool $allowLanguageTag = true;
8181
private bool $allowFeature = true;
8282
private bool $allowMultilineArguments = false;
83+
private bool $allowExamples = false;
8384
private bool $allowSteps = false;
8485
/**
8586
* @phpstan-var TDocStringSeparator|null
@@ -141,6 +142,7 @@ public function analyse(string $input, string $language = 'en')
141142
$this->allowFeature = true;
142143
$this->allowMultilineArguments = false;
143144
$this->allowSteps = false;
145+
$this->allowExamples = false;
144146

145147
if (\func_num_args() > 1) {
146148
// @codeCoverageIgnoreStart
@@ -584,6 +586,7 @@ protected function scanScenario()
584586

585587
$this->allowMultilineArguments = false;
586588
$this->allowSteps = true;
589+
$this->allowExamples = true;
587590

588591
return $token;
589592
}
@@ -605,6 +608,7 @@ protected function scanOutline()
605608

606609
$this->allowMultilineArguments = false;
607610
$this->allowSteps = true;
611+
$this->allowExamples = true;
608612

609613
return $token;
610614
}
@@ -618,6 +622,9 @@ protected function scanOutline()
618622
*/
619623
protected function scanExamples()
620624
{
625+
if (!$this->allowExamples) {
626+
return null;
627+
}
621628
$token = $this->scanTitleLine($this->currentDialect->getExamplesKeywords(), 'Examples');
622629

623630
if ($token === null) {

src/Node/BackgroundNode.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*
1818
* @final since 4.15.0
1919
*/
20-
class BackgroundNode implements ScenarioLikeInterface
20+
class BackgroundNode implements ScenarioLikeInterface, DescribableNodeInterface
2121
{
2222
/**
2323
* @param StepNode[] $steps
@@ -27,6 +27,7 @@ public function __construct(
2727
private readonly array $steps,
2828
private readonly string $keyword,
2929
private readonly int $line,
30+
private readonly ?string $description = null,
3031
) {
3132
}
3233

@@ -50,6 +51,11 @@ public function getTitle()
5051
return $this->title;
5152
}
5253

54+
public function getDescription(): ?string
55+
{
56+
return $this->description;
57+
}
58+
5359
/**
5460
* Checks if background has steps.
5561
*
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Behat Gherkin Parser.
5+
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
11+
namespace Behat\Gherkin\Node;
12+
13+
interface DescribableNodeInterface
14+
{
15+
/**
16+
* @return ?string
17+
*/
18+
public function getDescription();
19+
}

src/Node/ExampleTableNode.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*
1818
* @final since 4.15.0
1919
*/
20-
class ExampleTableNode extends TableNode implements TaggedNodeInterface
20+
class ExampleTableNode extends TableNode implements TaggedNodeInterface, DescribableNodeInterface
2121
{
2222
use TaggedNodeTrait;
2323

@@ -29,6 +29,8 @@ public function __construct(
2929
array $table,
3030
private readonly string $keyword,
3131
private readonly array $tags = [],
32+
private readonly ?string $name = null,
33+
private readonly ?string $description = null,
3234
) {
3335
parent::__construct($table);
3436
}
@@ -43,6 +45,16 @@ public function getNodeType()
4345
return 'ExampleTable';
4446
}
4547

48+
public function getName(): ?string
49+
{
50+
return $this->name;
51+
}
52+
53+
public function getDescription(): ?string
54+
{
55+
return $this->description;
56+
}
57+
4658
public function getTags()
4759
{
4860
return $this->tags;
@@ -57,4 +69,18 @@ public function getKeyword()
5769
{
5870
return $this->keyword;
5971
}
72+
73+
/**
74+
* @param array<int, list<string>> $table Table in form of [$rowLineNumber => [$val1, $val2, $val3]]
75+
*/
76+
public function withTable(array $table): self
77+
{
78+
return new self(
79+
$table,
80+
$this->keyword,
81+
$this->tags,
82+
$this->name,
83+
$this->description,
84+
);
85+
}
6086
}

src/Node/FeatureNode.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
*
2222
* @final since 4.15.0
2323
*/
24-
class FeatureNode implements KeywordNodeInterface, TaggedNodeInterface
24+
class FeatureNode implements KeywordNodeInterface, TaggedNodeInterface, DescribableNodeInterface
2525
{
2626
use TaggedNodeTrait;
2727

src/Node/OutlineNode.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*
1818
* @final since 4.15.0
1919
*/
20-
class OutlineNode implements ScenarioInterface
20+
class OutlineNode implements ScenarioInterface, DescribableNodeInterface
2121
{
2222
use TaggedNodeTrait;
2323

@@ -42,6 +42,7 @@ public function __construct(
4242
array|ExampleTableNode $tables,
4343
private readonly string $keyword,
4444
private readonly int $line,
45+
private readonly ?string $description = null,
4546
) {
4647
$this->tables = is_array($tables) ? $tables : [$tables];
4748
}
@@ -66,6 +67,11 @@ public function getTitle()
6667
return $this->title;
6768
}
6869

70+
public function getDescription(): ?string
71+
{
72+
return $this->description;
73+
}
74+
6975
public function getTags()
7076
{
7177
return $this->tags;
@@ -104,7 +110,7 @@ public function hasExamples()
104110
/**
105111
* Builds and returns examples table for the outline.
106112
*
107-
* WARNING: it returns a merged table with tags lost.
113+
* WARNING: it returns a merged table with tags, names & descriptions lost.
108114
*
109115
* @return ExampleTableNode
110116
*
@@ -181,6 +187,7 @@ public function withTables(array $exampleTables): self
181187
$exampleTables,
182188
$this->keyword,
183189
$this->line,
190+
$this->description,
184191
);
185192
}
186193

0 commit comments

Comments
 (0)