From 48aca0367fb8c8154682e9dcb433aab5e520956a Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 17 Nov 2025 23:03:00 +0000 Subject: [PATCH 1/2] add skip --- README.md | 21 +++++++++++------ rector.php | 3 ++- src/Analyzer/UnusedDefinitionsAnalyzer.php | 3 --- src/Command/AnalyzeCommand.php | 19 +++++++++++---- src/Contract/RuleInterface.php | 6 +++++ src/Enum/RuleIdentifier.php | 23 +++++++++++++++++++ ...uplicatedContextDefinitionContentsRule.php | 6 +++++ src/Rule/DuplicatedMaskRule.php | 6 +++++ src/Rule/UnusedContextDefinitionsRule.php | 6 +++++ src/ValueObject/Mask/AbstractMask.php | 2 ++ 10 files changed, 79 insertions(+), 16 deletions(-) create mode 100644 src/Enum/RuleIdentifier.php diff --git a/README.md b/README.md index 1d978a0c8..bac87634a 100644 --- a/README.md +++ b/README.md @@ -14,25 +14,32 @@ composer require behastan/behastan --dev ## Features -## 1. Find duplicated definitions +### 1. Find duplicated definitions contents -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: +* identifier: `duplicated-contents` -```bash -vendor/bin/behastan analyze -``` +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: + +
+### 2. Find duplicate masks + +* identifier: `duplicated-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.
-## 2. Find unused Behat definitions with static analysis +### 3. Find unused Behat definitions with static analysis + +* identifier: `unused-definitions` 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? 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: ```bash -vendor/bin/behastan unused-definitions tests +vendor/bin/behastan analyze tests ``` ↓ diff --git a/rector.php b/rector.php index 0489ab6d2..ec52a77e3 100644 --- a/rector.php +++ b/rector.php @@ -16,7 +16,8 @@ codingStyle: true, instanceOf: true, phpunitCodeQuality: true, - naming: true + naming: true, + rectorPreset: true, ) ->withImportNames() ->withSkip(['*/scoper.php', '*/Source/*', '*/Fixture/*']); diff --git a/src/Analyzer/UnusedDefinitionsAnalyzer.php b/src/Analyzer/UnusedDefinitionsAnalyzer.php index 5cdfaf5ba..43ad773ef 100644 --- a/src/Analyzer/UnusedDefinitionsAnalyzer.php +++ b/src/Analyzer/UnusedDefinitionsAnalyzer.php @@ -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; @@ -32,7 +31,6 @@ public function __construct( private SymfonyStyle $symfonyStyle, private UsedInstructionResolver $usedInstructionResolver, private DefinitionMasksExtractor $definitionMasksExtractor, - private MaskCollectionStatsPrinter $maskCollectionStatsPrinter, ) { } @@ -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()); diff --git a/src/Command/AnalyzeCommand.php b/src/Command/AnalyzeCommand.php index c22839187..53eeaf54b 100644 --- a/src/Command/AnalyzeCommand.php +++ b/src/Command/AnalyzeCommand.php @@ -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( @@ -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; @@ -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( 'Found %d Context and %d feature files', count($contextFileInfos), @@ -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('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('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; diff --git a/src/Contract/RuleInterface.php b/src/Contract/RuleInterface.php index 8164205e4..8c318b20b 100644 --- a/src/Contract/RuleInterface.php +++ b/src/Contract/RuleInterface.php @@ -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; @@ -22,4 +23,9 @@ public function process( MaskCollection $maskCollection, string $projectDirectory ): array; + + /** + * @return RuleIdentifier::* + */ + public function getIdentifier(): string; } diff --git a/src/Enum/RuleIdentifier.php b/src/Enum/RuleIdentifier.php new file mode 100644 index 000000000..0d8348cac --- /dev/null +++ b/src/Enum/RuleIdentifier.php @@ -0,0 +1,23 @@ + Date: Mon, 17 Nov 2025 23:34:00 +0000 Subject: [PATCH 2/2] readme --- README.md | 55 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index bac87634a..b06f9d6d8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,23 @@ Find unused and duplicated definitions easily – without running Behat test composer require behastan/behastan --dev ``` -## Features +## Usage + +```bash +vendor/bin/behastan analyse tests +``` + +
+ +Do you want to skip some rule? You can: + +```bash +vendor/bin/behastan analyse tests --skip= +``` + +
+ +Here are the available rules: ### 1. Find duplicated definitions contents @@ -34,40 +50,27 @@ Same as services, there should be no 2 same definition masks. Make them unique w * identifier: `unused-definitions` -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? +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? -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: +This rule spots definitions that are no longer needed. -```bash -vendor/bin/behastan analyze tests -``` +
-↓ +## Output example ```bash -Checking static, named and regex masks from 100 *Feature files -============================================================== - -Found 1036 masks: +Found 127 Context and 225 feature files +Extracting definitions masks... - * 747 exact - * 106 /regex/ - * 181 :named +Found 1367 masks: + * 863 exact + * 204 /regex/ + * 298 :named - 1036/1036 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - -the product price is :value -tests/Behat/ProductContext.php - -/^I submit order form and see payment page$/ -tests/Behat/OrderContext.php - - - [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!