diff --git a/README.md b/README.md index 1d978a0c8..b06f9d6d8 100644 --- a/README.md +++ b/README.md @@ -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 +``` + +
-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= ``` +
+ +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:
-## 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 -``` +
-↓ +### 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% +
-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! 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 @@ +