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
62 changes: 36 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,55 +12,65 @@ Find unused and duplicated definitions easily – without running Behat test
composer require behastan/behastan --dev
```

## Features
## Usage

## 1. Find duplicated definitions
```bash
vendor/bin/behastan analyse tests
```

<br>

Some definitions have very similar masks, but even identical contents. Better use a one definitions with exact mask, to make your tests more precise and easier to maintain:
Do you want to skip some rule? You can:

```bash
vendor/bin/behastan analyze
vendor/bin/behastan analyse tests --skip=<rule-identifier>
```

<br>

Here are the available rules:

### 1. Find duplicated definitions contents

* identifier: `duplicated-contents`

Some definitions have similar masks, even identical contents. Better use a one definitions with exact mask, to make your tests more precise and easier to maintain:

<br>

## 2. Find unused Behat definitions with static analysis
### 2. Find duplicate masks

Behat uses `@When()`, `@Then()` and `@Given()` annotations and their PHP 8 attribute alternatives to define method to be called in `*.feature` files. Sometimes test change and lines from `*.feature` files are deleted. But what about definitions?
* identifier: `duplicated-masks`

This command helps you to spot definitions that are no longer needed. Just provide test directory (1 or more) and let it statically compare defined and used masks:
Same as services, there should be no 2 same definition masks. Make them unique with different behavior, or merge them and use one definition instead.

```bash
vendor/bin/behastan unused-definitions tests
```
<br>

### 3. Find unused Behat definitions with static analysis

```bash
Checking static, named and regex masks from 100 *Feature files
==============================================================
* identifier: `unused-definitions`

Found 1036 masks:
Behat uses `@When()`, `@Then()` and `@Given()` annotations or attributes to define a class method that is called in `*.feature` files. Sometimes test change and lines from `*.feature` files are deleted. But what about definitions?

* 747 exact
* 106 /regex/
* 181 :named
This rule spots definitions that are no longer needed.

1036/1036 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
<br>

the product price is :value
tests/Behat/ProductContext.php
## Output example

/^I submit order form and see payment page$/
tests/Behat/OrderContext.php
```bash
Found 127 Context and 225 feature files
Extracting definitions masks...

Found 1367 masks:
* 863 exact
* 204 /regex/
* 298 :named

[ERROR] Found 2 unused definitions
Running analysis...
```

You can also add this command to CI, to get instant feedback about unused definitions.

Add this command to CI, to get instant feedback of any changes.

That's it!

Expand Down
3 changes: 2 additions & 1 deletion rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
codingStyle: true,
instanceOf: true,
phpunitCodeQuality: true,
naming: true
naming: true,
rectorPreset: true,
)
->withImportNames()
->withSkip(['*/scoper.php', '*/Source/*', '*/Fixture/*']);
3 changes: 0 additions & 3 deletions src/Analyzer/UnusedDefinitionsAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use Nette\Utils\Strings;
use Rector\Behastan\DefinitionMasksExtractor;
use Rector\Behastan\Reporting\MaskCollectionStatsPrinter;
use Rector\Behastan\UsedInstructionResolver;
use Rector\Behastan\ValueObject\Mask\AbstractMask;
use Rector\Behastan\ValueObject\Mask\ExactMask;
Expand All @@ -32,7 +31,6 @@ public function __construct(
private SymfonyStyle $symfonyStyle,
private UsedInstructionResolver $usedInstructionResolver,
private DefinitionMasksExtractor $definitionMasksExtractor,
private MaskCollectionStatsPrinter $maskCollectionStatsPrinter,
) {
}

Expand All @@ -55,7 +53,6 @@ public function analyse(array $contextFiles, array $featureFiles, MaskCollection
}

$maskCollection = $this->definitionMasksExtractor->extract($contextFiles);
$this->maskCollectionStatsPrinter->print($maskCollection);

$featureInstructions = $this->usedInstructionResolver->resolveInstructionsFromFeatureFiles($featureFiles);
$maskProgressBar = $this->symfonyStyle->createProgressBar($maskCollection->count());
Expand Down
19 changes: 14 additions & 5 deletions src/Command/AnalyzeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public function __construct(
protected function configure(): void
{
$this->setName('analyze');
$this->setAliases(['analyse']);

$this->setDescription('Run complete static analysis on Behat definitions and features');

$this->addArgument(
Expand All @@ -65,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$contextFileInfos = BehatMetafilesFinder::findContextFiles([$testDirectory]);
if ($contextFileInfos === []) {
$this->symfonyStyle->error(sprintf(
'No *.Context files found in "%s". Please provide correct test directory',
'No *.Context files found in "%s". Please provide correct directory',
$testDirectory
));
return self::FAILURE;
Expand All @@ -74,12 +76,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$featureFileInfos = BehatMetafilesFinder::findFeatureFiles([$testDirectory]);
if ($featureFileInfos === []) {
$this->symfonyStyle->error(sprintf(
'No *.feature files found in "%s". Please provide correct test directory',
'No *.feature files found in "%s". Please provide correct directory',
$testDirectory
));
return self::FAILURE;
}

$skips = $input->getOption('skip');

$this->symfonyStyle->writeln(sprintf(
'<fg=green>Found %d Context and %d feature files</>',
count($contextFileInfos),
Expand All @@ -91,21 +95,26 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->symfonyStyle->newLine();

$this->maskCollectionStatsPrinter->print($maskCollection);

$this->symfonyStyle->newLine();

// @todo skip by "--skip" option

$this->symfonyStyle->writeln('<fg=yellow>Running analysis...</>');
$this->symfonyStyle->newLine();

/** @var RuleError[] $allRuleErrors */
$allRuleErrors = [];
foreach ($this->rules as $rule) {
if ($skips !== [] && in_array($rule->getIdentifier(), $skips, true)) {
$this->symfonyStyle->writeln(sprintf('<fg=cyan>Skipping "%s" rule</>', $rule->getIdentifier()));
$this->symfonyStyle->newLine();
continue;
}

$ruleErrors = $rule->process($contextFileInfos, $featureFileInfos, $maskCollection, $testDirectory);
$allRuleErrors = array_merge($allRuleErrors, $ruleErrors);
}

if ($allRuleErrors === []) {
$this->symfonyStyle->newLine(2);
$this->symfonyStyle->success('No errors found. Good job!');

return self::SUCCESS;
Expand Down
6 changes: 6 additions & 0 deletions src/Contract/RuleInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Rector\Behastan\Contract;

use Rector\Behastan\Enum\RuleIdentifier;
use Rector\Behastan\ValueObject\MaskCollection;
use Rector\Behastan\ValueObject\RuleError;
use Symfony\Component\Finder\SplFileInfo;
Expand All @@ -22,4 +23,9 @@ public function process(
MaskCollection $maskCollection,
string $projectDirectory
): array;

/**
* @return RuleIdentifier::*
*/
public function getIdentifier(): string;
}
23 changes: 23 additions & 0 deletions src/Enum/RuleIdentifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Rector\Behastan\Enum;

final class RuleIdentifier
{
/**
* @var string
*/
public const DUPLICATED_CONTENTS = 'duplicated-contents';

/**
* @var string
*/
public const DUPLICATED_MASKS = 'duplicated-masks';

/**
* @var string
*/
public const UNUSED_DEFINITIONS = 'unused-definitions';
}
6 changes: 6 additions & 0 deletions src/Rule/DuplicatedContextDefinitionContentsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Rector\Behastan\Analyzer\ContextDefinitionsAnalyzer;
use Rector\Behastan\Contract\RuleInterface;
use Rector\Behastan\Enum\RuleIdentifier;
use Rector\Behastan\ValueObject\MaskCollection;
use Rector\Behastan\ValueObject\RuleError;
use Symfony\Component\Finder\SplFileInfo;
Expand Down Expand Up @@ -59,6 +60,11 @@ public function process(
return $ruleErrors;
}

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

/**
* @template TItem as object
*
Expand Down
6 changes: 6 additions & 0 deletions src/Rule/DuplicatedMaskRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Rector\Behastan\Analyzer\ContextDefinitionsAnalyzer;
use Rector\Behastan\Contract\RuleInterface;
use Rector\Behastan\Enum\RuleIdentifier;
use Rector\Behastan\ValueObject\ContextDefinition;
use Rector\Behastan\ValueObject\MaskCollection;
use Rector\Behastan\ValueObject\RuleError;
Expand Down Expand Up @@ -59,4 +60,9 @@ public function process(

return $ruleErrors;
}

public function getIdentifier(): string
{
return RuleIdentifier::DUPLICATED_MASKS;
}
}
6 changes: 6 additions & 0 deletions src/Rule/UnusedContextDefinitionsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Rector\Behastan\Analyzer\UnusedDefinitionsAnalyzer;
use Rector\Behastan\Contract\RuleInterface;
use Rector\Behastan\Enum\RuleIdentifier;
use Rector\Behastan\ValueObject\MaskCollection;
use Rector\Behastan\ValueObject\RuleError;
use Symfony\Component\Finder\SplFileInfo;
Expand Down Expand Up @@ -43,4 +44,9 @@ public function process(

return $ruleErrors;
}

public function getIdentifier(): string
{
return RuleIdentifier::UNUSED_DEFINITIONS;
}
}
2 changes: 2 additions & 0 deletions src/ValueObject/Mask/AbstractMask.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Rector\Behastan\ValueObject\Mask;

use Rector\Behastan\Contract\MaskInterface;
Expand Down