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
5 changes: 5 additions & 0 deletions src/Configuration/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ final class Option
*/
public const string SKIPPED_RECTOR_RULES = 'skipped_rector_rules';

/**
* @internal For reporting skipped classes that are not Rector rules
*/
public const string SKIPPED_NON_RECTOR_CLASSES = 'skipped_non_rector_classes';

/**
* @internal For reporting deprecated cache meta extensions
*/
Expand Down
1 change: 1 addition & 0 deletions src/Console/Command/ProcessCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->deprecatedRulesReporter->reportDeprecatedAttributesSetsArgs();

$this->missConfigurationReporter->reportSkippedNeverRegisteredRules();
$this->missConfigurationReporter->reportSkippedNonRectorClasses();
$this->missConfigurationReporter->reportUnusedSkips($processResult);

return $this->resolveReturnCode($processResult, $configuration);
Expand Down
21 changes: 21 additions & 0 deletions src/Reporting/MissConfigurationReporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Rector\Configuration\Option;
use Rector\Configuration\Parameter\SimpleParameterProvider;
use Rector\Configuration\VendorMissAnalyseGuard;
use Rector\Contract\Rector\RectorInterface;
use Rector\PostRector\Contract\Rector\PostRectorInterface;
use Rector\ValueObject\ProcessResult;
use Symfony\Component\Console\Style\SymfonyStyle;
Expand Down Expand Up @@ -72,6 +73,26 @@ public function reportSkippedNeverRegisteredRules(): void
$this->symfonyStyle->listing($neverRegisteredSkippedRules);
}

public function reportSkippedNonRectorClasses(): void
{
$skippedNonRectorClasses = SimpleParameterProvider::provideArrayParameter(
Option::SKIPPED_NON_RECTOR_CLASSES
);

if ($skippedNonRectorClasses === []) {
return;
}

$this->symfonyStyle->warning(sprintf(
'%s not a Rector rule, so %s never be skipped. Only classes that implement "%s" can be used in "->withSkip()"',
count($skippedNonRectorClasses) > 1 ? 'These skipped classes are' : 'This skipped class is',
count($skippedNonRectorClasses) > 1 ? 'they can' : 'it can',
RectorInterface::class
));

$this->symfonyStyle->listing($skippedNonRectorClasses);
}

/**
* @param string[] $filePaths
*/
Expand Down
26 changes: 26 additions & 0 deletions src/Validation/RectorConfigValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

use Rector\Configuration\Option;
use Rector\Configuration\Parameter\SimpleParameterProvider;
use Rector\Contract\Rector\RectorInterface;
use Rector\Exception\ShouldNotHappenException;
use Rector\PostRector\Contract\Rector\PostRectorInterface;

final class RectorConfigValidator
{
Expand All @@ -33,8 +35,14 @@ public static function ensureRectorRulesExist(array $skip): void
{
$nonExistingRules = [];
$skippedRectorRules = [];
$skippedNonRectorClasses = [];

foreach ($skip as $key => $value) {
if (is_string($key) && self::isNonRectorClass($key)) {
$skippedNonRectorClasses[] = $key;
continue;
}

if (self::isRectorClassValue($key)) {
if (class_exists($key)) {
$skippedRectorRules[] = $key;
Expand All @@ -58,6 +66,7 @@ public static function ensureRectorRulesExist(array $skip): void
}

SimpleParameterProvider::addParameter(Option::SKIPPED_RECTOR_RULES, $skippedRectorRules);
SimpleParameterProvider::addParameter(Option::SKIPPED_NON_RECTOR_CLASSES, $skippedNonRectorClasses);

if ($nonExistingRules === []) {
return;
Expand All @@ -73,6 +82,23 @@ public static function ensureRectorRulesExist(array $skip): void
);
}

/**
* Only Rector rules are matched against skipped classes, so any other class can never be skipped
*/
private static function isNonRectorClass(string $key): bool
{
// interfaces are allowed, as they can mark a group of Rector rules
if (! class_exists($key)) {
return false;
}

if (is_a($key, RectorInterface::class, true)) {
return false;
}

return ! is_a($key, PostRectorInterface::class, true);
}

private static function isRectorClassValue(mixed $value): bool
{
// only validate string
Expand Down
35 changes: 35 additions & 0 deletions tests/Reporting/MissConfigurationReporterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
use Rector\Configuration\Option;
use Rector\Configuration\Parameter\SimpleParameterProvider;
use Rector\Configuration\VendorMissAnalyseGuard;
use Rector\Php85\Rector\FuncCall\OrdSingleByteRector;
use Rector\PostRector\Rector\NameImportingPostRector;
use Rector\Reporting\MissConfigurationReporter;
use Rector\Reporting\UnusedSkipResolver;
use Rector\Testing\PHPUnit\AbstractLazyTestCase;
use Rector\Tests\Skipper\Skipper\Fixture\Element\FifthElement;
use Rector\Tests\Skipper\Skipper\Fixture\Element\ThreeMan;
use Rector\Tests\Skipper\Skipper\Source\AnotherClassToSkip;
use Rector\Validation\RectorConfigValidator;
use Rector\ValueObject\ProcessResult;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
Expand Down Expand Up @@ -59,6 +62,38 @@ protected function tearDown(): void
{
SimpleParameterProvider::setParameter(Option::SKIP, []);
SimpleParameterProvider::setParameter(Option::REPORT_UNUSED_SKIPS, false);
SimpleParameterProvider::setParameter(Option::SKIPPED_NON_RECTOR_CLASSES, []);
}

public function testReportsSkippedNonRectorClass(): void
{
RectorConfigValidator::ensureRectorRulesExist([
// not a Rector rule, can never be skipped
AnotherClassToSkip::class => ['some/path'],
// Rector rule, must not be reported
OrdSingleByteRector::class => ['some/path'],
// post rector rule, must not be reported
NameImportingPostRector::class => ['some/path'],
]);

$this->missConfigurationReporter->reportSkippedNonRectorClasses();

$output = $this->bufferedOutput->fetch();

$this->assertStringContainsString('AnotherClassToSkip', $output);
$this->assertStringNotContainsString('OrdSingleByteRector', $output);
$this->assertStringNotContainsString('NameImportingPostRector', $output);
}

public function testReportsNothingOnRectorClassesOnly(): void
{
RectorConfigValidator::ensureRectorRulesExist([
OrdSingleByteRector::class => ['some/path'],
]);

$this->missConfigurationReporter->reportSkippedNonRectorClasses();

$this->assertSame('', $this->bufferedOutput->fetch());
}

public function testReportsOnlyTrackableUnusedSkips(): void
Expand Down
Loading