Skip to content

Commit be60d74

Browse files
authored
[ci] add rule validation to check code samples have at least one diff (#8067)
* add code sample * [ci] add rule validation to check code samples have at least one diff * perfect
1 parent 07451c9 commit be60d74

3 files changed

Lines changed: 113 additions & 1 deletion

File tree

‎.github/workflows/code_analysis.yaml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ jobs:
3636
name: 'Avoid duplicate short class names'
3737
run: php scripts/unique-rector-short-class-name.php
3838

39+
-
40+
name: 'Validate rule definitions'
41+
run: php scripts/validate-rule-definitions.php
42+
3943
-
4044
name: 'Help and Version'
4145
run:

‎rules/DeadCode/Rector/Class_/RemoveRefactorDuplicatedNodeInstanceCheckRector.php‎

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Rector\PHPStan\ScopeFetcher;
2020
use Rector\Rector\AbstractRector;
2121
use Rector\StaticTypeMapper\ValueObject\Type\ShortenedObjectType;
22+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
2223
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
2324
use Webmozart\Assert\Assert;
2425

@@ -37,7 +38,46 @@ public function getRuleDefinition(): RuleDefinition
3738
{
3839
return new RuleDefinition(
3940
'Remove refactor() method of Rector rule double check of $classMethod instance, if already defined in @param type',
40-
[]
41+
[
42+
new CodeSample(
43+
<<<'CODE_SAMPLE'
44+
final class SomeRector extends AbstractRector
45+
{
46+
public function getNodeTypes(): array
47+
{
48+
return [ClassMethod::class];
49+
}
50+
51+
/**
52+
* @param ClassMethod $node
53+
*/
54+
public function refactor(Node $node)
55+
{
56+
if (! $node instanceof ClassMethod) {
57+
return null;
58+
}
59+
}
60+
}
61+
CODE_SAMPLE
62+
,
63+
<<<'CODE_SAMPLE'
64+
final class SomeRector extends AbstractRector
65+
{
66+
public function getNodeTypes(): array
67+
{
68+
return [ClassMethod::class];
69+
}
70+
71+
/**
72+
* @param ClassMethod $node
73+
*/
74+
public function refactor(Node $node)
75+
{
76+
}
77+
}
78+
CODE_SAMPLE
79+
),
80+
]
4181
);
4282
}
4383

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Console\ExitCode;
6+
use Rector\Scripts\Finder\RectorClassFinder;
7+
use Symfony\Component\Console\Input\ArrayInput;
8+
use Symfony\Component\Console\Output\ConsoleOutput;
9+
use Symfony\Component\Console\Style\SymfonyStyle;
10+
use Symplify\RuleDocGenerator\Contract\DocumentedRuleInterface;
11+
12+
require __DIR__ . '/../vendor/autoload.php';
13+
14+
$symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput());
15+
16+
// core rules + all installed rector extensions (rector-doctrine, rector-symfony, rector-phpunit, ...)
17+
$ruleDirectories = array_merge(
18+
[__DIR__ . '/../rules'],
19+
glob(__DIR__ . '/../vendor/rector/rector-*', GLOB_ONLYDIR) ?: []
20+
);
21+
22+
$rectorClassFinder = new RectorClassFinder();
23+
$rectorClasses = $rectorClassFinder->find($ruleDirectories);
24+
25+
$errorMessages = [];
26+
27+
foreach ($rectorClasses as $rectorClass) {
28+
$reflectionClass = new ReflectionClass($rectorClass);
29+
30+
// rule definition does not depend on constructor dependencies, skip them
31+
$rector = $reflectionClass->newInstanceWithoutConstructor();
32+
if (! $rector instanceof DocumentedRuleInterface) {
33+
continue;
34+
}
35+
36+
$ruleDefinition = $rector->getRuleDefinition();
37+
38+
if (trim($ruleDefinition->getDescription()) === '') {
39+
$errorMessages[] = sprintf('Rule "%s" is missing a clear description', $rectorClass);
40+
}
41+
42+
$codeSamples = $ruleDefinition->getCodeSamples();
43+
if ($codeSamples === []) {
44+
$errorMessages[] = sprintf('Rule "%s" is missing at least one code sample', $rectorClass);
45+
continue;
46+
}
47+
48+
foreach ($codeSamples as $codeSample) {
49+
if (trim($codeSample->getBadCode()) === '' || trim($codeSample->getGoodCode()) === '') {
50+
$errorMessages[] = sprintf('Rule "%s" has an empty code sample, fill before/after code', $rectorClass);
51+
break;
52+
}
53+
}
54+
}
55+
56+
if ($errorMessages !== []) {
57+
$symfonyStyle->listing($errorMessages);
58+
$symfonyStyle->error(sprintf('Found %d rule definition error(s), see above', count($errorMessages)));
59+
60+
exit(ExitCode::FAILURE);
61+
}
62+
63+
$symfonyStyle->writeln('Scanned paths:');
64+
$symfonyStyle->listing(array_map(static fn (string $directory): string => realpath($directory), $ruleDirectories));
65+
66+
$symfonyStyle->success(sprintf('All %d Rector rule definitions are valid!', count($rectorClasses)));
67+
68+
exit(ExitCode::SUCCESS);

0 commit comments

Comments
 (0)