diff --git a/.github/workflows/code_analysis.yaml b/.github/workflows/code_analysis.yaml index 1670042a6bc..84de1b31a50 100644 --- a/.github/workflows/code_analysis.yaml +++ b/.github/workflows/code_analysis.yaml @@ -36,6 +36,10 @@ jobs: name: 'Avoid duplicate short class names' run: php scripts/unique-rector-short-class-name.php + - + name: 'Validate rule definitions' + run: php scripts/validate-rule-definitions.php + - name: 'Help and Version' run: diff --git a/rules/DeadCode/Rector/Class_/RemoveRefactorDuplicatedNodeInstanceCheckRector.php b/rules/DeadCode/Rector/Class_/RemoveRefactorDuplicatedNodeInstanceCheckRector.php index 1aebff9b4a1..4308015a630 100644 --- a/rules/DeadCode/Rector/Class_/RemoveRefactorDuplicatedNodeInstanceCheckRector.php +++ b/rules/DeadCode/Rector/Class_/RemoveRefactorDuplicatedNodeInstanceCheckRector.php @@ -19,6 +19,7 @@ use Rector\PHPStan\ScopeFetcher; use Rector\Rector\AbstractRector; use Rector\StaticTypeMapper\ValueObject\Type\ShortenedObjectType; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; use Webmozart\Assert\Assert; @@ -37,7 +38,46 @@ public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( 'Remove refactor() method of Rector rule double check of $classMethod instance, if already defined in @param type', - [] + [ + new CodeSample( + <<<'CODE_SAMPLE' +final class SomeRector extends AbstractRector +{ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node) + { + if (! $node instanceof ClassMethod) { + return null; + } + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +final class SomeRector extends AbstractRector +{ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node) + { + } +} +CODE_SAMPLE + ), + ] ); } diff --git a/scripts/validate-rule-definitions.php b/scripts/validate-rule-definitions.php new file mode 100644 index 00000000000..6a1c4b1f42c --- /dev/null +++ b/scripts/validate-rule-definitions.php @@ -0,0 +1,68 @@ +find($ruleDirectories); + +$errorMessages = []; + +foreach ($rectorClasses as $rectorClass) { + $reflectionClass = new ReflectionClass($rectorClass); + + // rule definition does not depend on constructor dependencies, skip them + $rector = $reflectionClass->newInstanceWithoutConstructor(); + if (! $rector instanceof DocumentedRuleInterface) { + continue; + } + + $ruleDefinition = $rector->getRuleDefinition(); + + if (trim($ruleDefinition->getDescription()) === '') { + $errorMessages[] = sprintf('Rule "%s" is missing a clear description', $rectorClass); + } + + $codeSamples = $ruleDefinition->getCodeSamples(); + if ($codeSamples === []) { + $errorMessages[] = sprintf('Rule "%s" is missing at least one code sample', $rectorClass); + continue; + } + + foreach ($codeSamples as $codeSample) { + if (trim($codeSample->getBadCode()) === '' || trim($codeSample->getGoodCode()) === '') { + $errorMessages[] = sprintf('Rule "%s" has an empty code sample, fill before/after code', $rectorClass); + break; + } + } +} + +if ($errorMessages !== []) { + $symfonyStyle->listing($errorMessages); + $symfonyStyle->error(sprintf('Found %d rule definition error(s), see above', count($errorMessages))); + + exit(ExitCode::FAILURE); +} + +$symfonyStyle->writeln('Scanned paths:'); +$symfonyStyle->listing(array_map(static fn (string $directory): string => realpath($directory), $ruleDirectories)); + +$symfonyStyle->success(sprintf('All %d Rector rule definitions are valid!', count($rectorClasses))); + +exit(ExitCode::SUCCESS);