Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ This rule spots definitions that are no longer needed, so you can remove them.

<br>

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

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:

Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
],
"require": {
"php": "^8.3",
"nette/utils": "^4.0",
"behat/gherkin": "^4.16",
"entropy/entropy": "dev-main",
"nette/utils": "^4.0",
"nikic/php-parser": "^5.6",
"symfony/finder": "^7.0",
"webmozart/assert": "^1.12"
Expand All @@ -19,6 +20,7 @@
"phpunit/phpunit": "^12.5",
"rector/jack": "^0.5.1",
"rector/rector": "^2.3.2",
"shipmonk/composer-dependency-analyser": "^1.8",
"symplify/easy-coding-standard": "^13.0",
"tomasvotruba/class-leak": "^2.1",
"tracy/tracy": "^2.10"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
namespace Rector\Behastan\Analyzer;

use Entropy\Attributes\RelatedTest;
use Entropy\Utils\Regex;
use Rector\Behastan\Tests\Analyzer\DuplicatedScenarioNamesAnalyzer\DuplicatedScenarioNamesAnalyzerTest;
use Rector\Behastan\Gherkin\GherkinParser;
use Rector\Behastan\Tests\Analyzer\DuplicatedScenarioNamesAnalyzer\DuplicatedScenarioTitlesAnalyzerTest;
use Symfony\Component\Finder\SplFileInfo;

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

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

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

foreach ($matches as $match) {
$scenarioName = $match['name'];
$scenarioNamesToFiles[$scenarioName][] = $featureFile->getRealPath();
foreach ($featureGherkin->getScenarios() as $scenario) {
$scenarioNamesToFiles[$scenario->getTitle()][] = $featureFile->getRealPath();
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Enum/RuleIdentifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ final class RuleIdentifier
{
public const string DUPLICATED_CONTENTS = 'duplicated-contents';

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

public const string DUPLICATED_PATTERNS = 'duplicated-patterns';

Expand Down
44 changes: 44 additions & 0 deletions src/Gherkin/GherkinParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Rector\Behastan\Gherkin;

use Behat\Gherkin\Keywords\ArrayKeywords;
use Behat\Gherkin\Lexer;
use Behat\Gherkin\Node\FeatureNode;
use Behat\Gherkin\Parser;
use Webmozart\Assert\Assert;

final class GherkinParser
{
private Parser $parser;

public function __construct()
{
$arrayKeywords = new ArrayKeywords([
'en' => [
'feature' => 'Feature',
'background' => 'Background',
'scenario' => 'Scenario',
'scenario_outline' => 'Scenario Outline|Scenario Template',
'examples' => 'Examples|Scenarios',
'given' => 'Given',
'when' => 'When',
'then' => 'Then',
'and' => 'And',
'but' => 'But',
],
]);

$this->parser = new Parser(new Lexer($arrayKeywords));
}

public function parseFile(string $filePath): FeatureNode
{
$featureNode = $this->parser->parseFile($filePath);
Assert::isInstanceOf($featureNode, FeatureNode::class);

return $featureNode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@

namespace Rector\Behastan\Rule;

use Rector\Behastan\Analyzer\DuplicatedScenarioNamesAnalyzer;
use Rector\Behastan\Analyzer\DuplicatedScenarioTitlesAnalyzer;
use Rector\Behastan\Contract\RuleInterface;
use Rector\Behastan\Enum\RuleIdentifier;
use Rector\Behastan\ValueObject\PatternCollection;
use Rector\Behastan\ValueObject\RuleError;
use Symfony\Component\Finder\SplFileInfo;

final readonly class DuplicatedScenarioNamesRule implements RuleInterface
final readonly class DuplicatedScenarioTitleRule implements RuleInterface
{
public function __construct(
private DuplicatedScenarioNamesAnalyzer $duplicatedScenarioNamesAnalyzer
private DuplicatedScenarioTitlesAnalyzer $duplicatedScenarioNamesAnalyzer
) {
}

Expand Down Expand Up @@ -47,6 +47,6 @@ public function process(

public function getIdentifier(): string
{
return RuleIdentifier::DUPLICATED_SCENARIO_NAMES;
return RuleIdentifier::DUPLICATED_SCENARIO_TITLES;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Rector\Behastan\Tests\Analyzer\DuplicatedScenarioNamesAnalyzer;

use Rector\Behastan\Analyzer\DuplicatedScenarioTitlesAnalyzer;
use Rector\Behastan\Finder\BehatMetafilesFinder;
use Rector\Behastan\Tests\AbstractTestCase;

final class DuplicatedScenarioTitlesAnalyzerTest extends AbstractTestCase
{
private DuplicatedScenarioTitlesAnalyzer $duplicatedScenarioNamesAnalyzer;

protected function setUp(): void
{
parent::setUp();

$this->duplicatedScenarioNamesAnalyzer = $this->make(DuplicatedScenarioTitlesAnalyzer::class);
}

public function testSpot(): void
{
$featureFiles = BehatMetafilesFinder::findFeatureFiles([__DIR__ . '/Fixture/simple']);
$this->assertCount(2, $featureFiles);

$duplicatedScenarioNamesToFiles = $this->duplicatedScenarioNamesAnalyzer->analyze($featureFiles);

$this->assertCount(1, $duplicatedScenarioNamesToFiles);
$this->assertArrayHasKey('Same scenario name', $duplicatedScenarioNamesToFiles);

$givenFiles = $duplicatedScenarioNamesToFiles['Same scenario name'];

$this->assertSame(
[__DIR__ . '/Fixture/simple/some.feature', __DIR__ . '/Fixture/simple/another.feature'],
$givenFiles
);
}

public function testSkipSecondLineDifferent(): void
{
$featureFiles = BehatMetafilesFinder::findFeatureFiles([__DIR__ . '/Fixture/no-multi-line']);
$this->assertCount(2, $featureFiles);

$duplicatedScenarioNamesToFiles = $this->duplicatedScenarioNamesAnalyzer->analyze($featureFiles);

$this->assertCount(0, $duplicatedScenarioNamesToFiles);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Feature: Some feature

Scenario: Same scenario name But second line is different
Given that
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Feature: Another feature

Scenario: Same scenario name But second line is not the same
Given This