Skip to content

Commit 6f0df24

Browse files
authored
Add gherkin to work with exact values (#21)
* add test for multiline different * add gehrking parser to be sure
1 parent 95151e3 commit 6f0df24

File tree

12 files changed

+121
-53
lines changed

12 files changed

+121
-53
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ This rule spots definitions that are no longer needed, so you can remove them.
108108

109109
<br>
110110

111-
### 4. Find duplicate scenario names (`duplicate-scenario-names`)
111+
### 4. Find duplicate scenario titles (`duplicate-scenario-titles`)
112112

113113
In Behat, each scenario should have a unique name to ensure clarity and avoid confusion during test execution and later debugging. This rule identifies scenarios that share the same name within your feature files:
114114

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
],
88
"require": {
99
"php": "^8.3",
10-
"nette/utils": "^4.0",
10+
"behat/gherkin": "^4.16",
1111
"entropy/entropy": "dev-main",
12+
"nette/utils": "^4.0",
1213
"nikic/php-parser": "^5.6",
1314
"symfony/finder": "^7.0",
1415
"webmozart/assert": "^1.12"
@@ -19,6 +20,7 @@
1920
"phpunit/phpunit": "^12.5",
2021
"rector/jack": "^0.5.1",
2122
"rector/rector": "^2.3.2",
23+
"shipmonk/composer-dependency-analyser": "^1.8",
2224
"symplify/easy-coding-standard": "^13.0",
2325
"tomasvotruba/class-leak": "^2.1",
2426
"tracy/tracy": "^2.10"

src/Analyzer/DuplicatedScenarioNamesAnalyzer.php renamed to src/Analyzer/DuplicatedScenarioTitlesAnalyzer.php

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55
namespace Rector\Behastan\Analyzer;
66

77
use Entropy\Attributes\RelatedTest;
8-
use Entropy\Utils\Regex;
9-
use Rector\Behastan\Tests\Analyzer\DuplicatedScenarioNamesAnalyzer\DuplicatedScenarioNamesAnalyzerTest;
8+
use Rector\Behastan\Gherkin\GherkinParser;
9+
use Rector\Behastan\Tests\Analyzer\DuplicatedScenarioNamesAnalyzer\DuplicatedScenarioTitlesAnalyzerTest;
1010
use Symfony\Component\Finder\SplFileInfo;
1111

12-
#[RelatedTest(DuplicatedScenarioNamesAnalyzerTest::class)]
13-
final class DuplicatedScenarioNamesAnalyzer
12+
#[RelatedTest(DuplicatedScenarioTitlesAnalyzerTest::class)]
13+
final readonly class DuplicatedScenarioTitlesAnalyzer
1414
{
15-
private const string SCENARIO_NAME_REGEX = '#\s+Scenario:\s+(?<name>.*?)\n#';
15+
public function __construct(
16+
private GherkinParser $gherkinParser
17+
) {
18+
}
1619

1720
/**
1821
* @param SplFileInfo[] $featureFiles
@@ -23,12 +26,10 @@ public function analyze(array $featureFiles): array
2326
$scenarioNamesToFiles = [];
2427

2528
foreach ($featureFiles as $featureFile) {
26-
// match Scenario: "<name>"
27-
$matches = Regex::matchAll($featureFile->getContents(), self::SCENARIO_NAME_REGEX);
29+
$featureGherkin = $this->gherkinParser->parseFile($featureFile->getRealPath());
2830

29-
foreach ($matches as $match) {
30-
$scenarioName = $match['name'];
31-
$scenarioNamesToFiles[$scenarioName][] = $featureFile->getRealPath();
31+
foreach ($featureGherkin->getScenarios() as $scenario) {
32+
$scenarioNamesToFiles[$scenario->getTitle()][] = $featureFile->getRealPath();
3233
}
3334
}
3435

src/Enum/RuleIdentifier.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ final class RuleIdentifier
88
{
99
public const string DUPLICATED_CONTENTS = 'duplicated-contents';
1010

11-
public const string DUPLICATED_SCENARIO_NAMES = 'duplicated-scenario-names';
11+
public const string DUPLICATED_SCENARIO_TITLES = 'duplicated-scenario-titles';
1212

1313
public const string DUPLICATED_PATTERNS = 'duplicated-patterns';
1414

src/Gherkin/GherkinParser.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Behastan\Gherkin;
6+
7+
use Behat\Gherkin\Keywords\ArrayKeywords;
8+
use Behat\Gherkin\Lexer;
9+
use Behat\Gherkin\Node\FeatureNode;
10+
use Behat\Gherkin\Parser;
11+
use Webmozart\Assert\Assert;
12+
13+
final class GherkinParser
14+
{
15+
private Parser $parser;
16+
17+
public function __construct()
18+
{
19+
$arrayKeywords = new ArrayKeywords([
20+
'en' => [
21+
'feature' => 'Feature',
22+
'background' => 'Background',
23+
'scenario' => 'Scenario',
24+
'scenario_outline' => 'Scenario Outline|Scenario Template',
25+
'examples' => 'Examples|Scenarios',
26+
'given' => 'Given',
27+
'when' => 'When',
28+
'then' => 'Then',
29+
'and' => 'And',
30+
'but' => 'But',
31+
],
32+
]);
33+
34+
$this->parser = new Parser(new Lexer($arrayKeywords));
35+
}
36+
37+
public function parseFile(string $filePath): FeatureNode
38+
{
39+
$featureNode = $this->parser->parseFile($filePath);
40+
Assert::isInstanceOf($featureNode, FeatureNode::class);
41+
42+
return $featureNode;
43+
}
44+
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@
44

55
namespace Rector\Behastan\Rule;
66

7-
use Rector\Behastan\Analyzer\DuplicatedScenarioNamesAnalyzer;
7+
use Rector\Behastan\Analyzer\DuplicatedScenarioTitlesAnalyzer;
88
use Rector\Behastan\Contract\RuleInterface;
99
use Rector\Behastan\Enum\RuleIdentifier;
1010
use Rector\Behastan\ValueObject\PatternCollection;
1111
use Rector\Behastan\ValueObject\RuleError;
1212
use Symfony\Component\Finder\SplFileInfo;
1313

14-
final readonly class DuplicatedScenarioNamesRule implements RuleInterface
14+
final readonly class DuplicatedScenarioTitleRule implements RuleInterface
1515
{
1616
public function __construct(
17-
private DuplicatedScenarioNamesAnalyzer $duplicatedScenarioNamesAnalyzer
17+
private DuplicatedScenarioTitlesAnalyzer $duplicatedScenarioNamesAnalyzer
1818
) {
1919
}
2020

@@ -47,6 +47,6 @@ public function process(
4747

4848
public function getIdentifier(): string
4949
{
50-
return RuleIdentifier::DUPLICATED_SCENARIO_NAMES;
50+
return RuleIdentifier::DUPLICATED_SCENARIO_TITLES;
5151
}
5252
}

tests/Analyzer/DuplicatedScenarioNamesAnalyzer/DuplicatedScenarioNamesAnalyzerTest.php

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Behastan\Tests\Analyzer\DuplicatedScenarioNamesAnalyzer;
6+
7+
use Rector\Behastan\Analyzer\DuplicatedScenarioTitlesAnalyzer;
8+
use Rector\Behastan\Finder\BehatMetafilesFinder;
9+
use Rector\Behastan\Tests\AbstractTestCase;
10+
11+
final class DuplicatedScenarioTitlesAnalyzerTest extends AbstractTestCase
12+
{
13+
private DuplicatedScenarioTitlesAnalyzer $duplicatedScenarioNamesAnalyzer;
14+
15+
protected function setUp(): void
16+
{
17+
parent::setUp();
18+
19+
$this->duplicatedScenarioNamesAnalyzer = $this->make(DuplicatedScenarioTitlesAnalyzer::class);
20+
}
21+
22+
public function testSpot(): void
23+
{
24+
$featureFiles = BehatMetafilesFinder::findFeatureFiles([__DIR__ . '/Fixture/simple']);
25+
$this->assertCount(2, $featureFiles);
26+
27+
$duplicatedScenarioNamesToFiles = $this->duplicatedScenarioNamesAnalyzer->analyze($featureFiles);
28+
29+
$this->assertCount(1, $duplicatedScenarioNamesToFiles);
30+
$this->assertArrayHasKey('Same scenario name', $duplicatedScenarioNamesToFiles);
31+
32+
$givenFiles = $duplicatedScenarioNamesToFiles['Same scenario name'];
33+
34+
$this->assertSame(
35+
[__DIR__ . '/Fixture/simple/some.feature', __DIR__ . '/Fixture/simple/another.feature'],
36+
$givenFiles
37+
);
38+
}
39+
40+
public function testSkipSecondLineDifferent(): void
41+
{
42+
$featureFiles = BehatMetafilesFinder::findFeatureFiles([__DIR__ . '/Fixture/no-multi-line']);
43+
$this->assertCount(2, $featureFiles);
44+
45+
$duplicatedScenarioNamesToFiles = $this->duplicatedScenarioNamesAnalyzer->analyze($featureFiles);
46+
47+
$this->assertCount(0, $duplicatedScenarioNamesToFiles);
48+
}
49+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Feature: Some feature
2+
3+
Scenario: Same scenario name But second line is different
4+
Given that
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Feature: Another feature
2+
3+
Scenario: Same scenario name But second line is not the same
4+
Given This

0 commit comments

Comments
 (0)