diff --git a/packages-tests/Skipper/Skipper/SkipperRectorRuleTest.php b/packages-tests/Skipper/Skipper/SkipperRectorRuleTest.php new file mode 100644 index 00000000000..07ba05e6969 --- /dev/null +++ b/packages-tests/Skipper/Skipper/SkipperRectorRuleTest.php @@ -0,0 +1,41 @@ +bootFromConfigFiles([__DIR__ . '/config/single_skipped_rule_config.php']); + + $container = self::getContainer(); + + // to invoke before resolving + $container->make(PhpFilesFinder::class); + + // here 1 rule should be removed and 1 should remain + /** @var RewindableGenerator $rectorsIterator */ + $rectorsIterator = $container->tagged(RectorInterface::class); + $this->assertCount(1, $rectorsIterator); + + $rectors = iterator_to_array($rectorsIterator->getIterator()); + $this->assertInstanceOf(RemoveUnusedPromotedPropertyRector::class, $rectors[0]); + } +} diff --git a/packages-tests/Skipper/Skipper/config/single_skipped_rule_config.php b/packages-tests/Skipper/Skipper/config/single_skipped_rule_config.php new file mode 100644 index 00000000000..1426cfd83dc --- /dev/null +++ b/packages-tests/Skipper/Skipper/config/single_skipped_rule_config.php @@ -0,0 +1,16 @@ +rules([ + InlineConstructorDefaultToPropertyRector::class, + RemoveUnusedPromotedPropertyRector::class, + ]); + + $rectorConfig->skip([InlineConstructorDefaultToPropertyRector::class]); +}; diff --git a/packages/Skipper/SkipCriteriaResolver/SkippedClassResolver.php b/packages/Skipper/SkipCriteriaResolver/SkippedClassResolver.php index f9cd0272bff..2e2fcc6305e 100644 --- a/packages/Skipper/SkipCriteriaResolver/SkippedClassResolver.php +++ b/packages/Skipper/SkipCriteriaResolver/SkippedClassResolver.php @@ -4,7 +4,6 @@ namespace Rector\Skipper\SkipCriteriaResolver; -use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Configuration\Option; use Rector\Core\Configuration\Parameter\SimpleParameterProvider; use Rector\Testing\PHPUnit\StaticPHPUnitEnvironment; @@ -16,11 +15,6 @@ final class SkippedClassResolver */ private array $skippedClasses = []; - public function __construct( - private readonly ReflectionProvider $reflectionProvider - ) { - } - /** * @return array */ @@ -49,7 +43,8 @@ public function resolve(): array continue; } - if (! $this->reflectionProvider->hasClass($key)) { + // this only checks for Rector rules, that are always autoloaded + if (! class_exists($key) && ! interface_exists($key)) { continue; } diff --git a/packages/Testing/PHPUnit/AbstractLazyTestCase.php b/packages/Testing/PHPUnit/AbstractLazyTestCase.php index c628267a07b..4e84db10323 100644 --- a/packages/Testing/PHPUnit/AbstractLazyTestCase.php +++ b/packages/Testing/PHPUnit/AbstractLazyTestCase.php @@ -11,7 +11,6 @@ use Rector\Core\DependencyInjection\LazyContainerFactory; use Rector\Core\Rector\AbstractRector; use Rector\Core\Util\Reflection\PrivatesAccessor; -use Webmozart\Assert\Assert; abstract class AbstractLazyTestCase extends TestCase { @@ -26,10 +25,7 @@ protected function bootFromConfigFiles(array $configFiles): void $rectorConfig = self::getContainer(); foreach ($configFiles as $configFile) { - $configClosure = require $configFile; - Assert::isCallable($configClosure); - - $configClosure($rectorConfig); + $rectorConfig->import($configFile); } } diff --git a/phpstan.neon b/phpstan.neon index fe2bb81a7de..3cbe6878998 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -645,3 +645,8 @@ parameters: - message: '#Parameters should use "PhpParser\\Node\\Stmt\\ClassMethod" types as the only types passed to this method#' path: packages/VendorLocker/ParentClassMethodTypeOverrideGuard.php + + # checks for rector always autoloaded rules only + - + message: '#Function "(class_exists|interface_exists)\(\)" cannot be used/left in the code\: use ReflectionProvider\->has\*\(\) instead#' + path: packages/Skipper/SkipCriteriaResolver/SkippedClassResolver.php diff --git a/src/DependencyInjection/Laravel/ContainerMemento.php b/src/DependencyInjection/Laravel/ContainerMemento.php new file mode 100644 index 00000000000..50b6f1ad9fc --- /dev/null +++ b/src/DependencyInjection/Laravel/ContainerMemento.php @@ -0,0 +1,36 @@ +offsetUnset($typeToForget); + + // 2. remove all tagged rules + $privatesAccessor = new PrivatesAccessor(); + $privatesAccessor->propertyClosure($container, 'tags', static function (array $tags) use ( + $typeToForget + ): array { + foreach ($tags as $tagName => $taggedClasses) { + foreach ($taggedClasses as $key => $taggedClass) { + if (is_a($taggedClass, $typeToForget, true)) { + unset($tags[$tagName][$key]); + } + } + } + + return $tags; + }); + } +} diff --git a/src/DependencyInjection/LazyContainerFactory.php b/src/DependencyInjection/LazyContainerFactory.php index 9c01011d583..8444fbcabe5 100644 --- a/src/DependencyInjection/LazyContainerFactory.php +++ b/src/DependencyInjection/LazyContainerFactory.php @@ -63,6 +63,7 @@ use Rector\Core\Contract\Processor\FileProcessorInterface; use Rector\Core\Contract\Rector\PhpRectorInterface; use Rector\Core\Contract\Rector\RectorInterface; +use Rector\Core\DependencyInjection\Laravel\ContainerMemento; use Rector\Core\Logging\CurrentRectorProvider; use Rector\Core\Logging\RectorOutput; use Rector\Core\NodeDecorator\CreatedByRuleDecorator; @@ -165,6 +166,7 @@ use Rector\PostRector\Application\PostFileProcessor; use Rector\RectorGenerator\Command\GenerateCommand; use Rector\RectorGenerator\Command\InitRecipeCommand; +use Rector\Skipper\SkipCriteriaResolver\SkippedClassResolver; use Rector\Skipper\Skipper\Skipper; use Rector\StaticTypeMapper\Contract\PhpDocParser\PhpDocTypeMapperInterface; use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface; @@ -401,11 +403,10 @@ public function create(): RectorConfig $rectorConfig->containerCacheDirectory(sys_get_temp_dir()); // make use of https://github.com/symplify/easy-parallel + $rectorConfig->singleton(Application::class, static function (): Application { $application = new Application(); - // @todo inject commands - $privatesAccessor = new PrivatesAccessor(); $privatesAccessor->propertyClosure($application, 'commands', static function (array $commands): array { unset($commands['completion']); @@ -416,17 +417,17 @@ public function create(): RectorConfig return $application; }); - $rectorConfig->singleton(Inflector::class, static function (): Inflector { - $inflectorFactory = new InflectorFactory(); - return $inflectorFactory->build(); - }); - $rectorConfig->singleton(ConsoleApplication::class, ConsoleApplication::class); $rectorConfig->when(ConsoleApplication::class) ->needs('$commands') ->giveTagged(Command::class); + $rectorConfig->singleton(Inflector::class, static function (): Inflector { + $inflectorFactory = new InflectorFactory(); + return $inflectorFactory->build(); + }); + $rectorConfig->tag(ProcessCommand::class, Command::class); $rectorConfig->tag(WorkerCommand::class, Command::class); $rectorConfig->tag(SetupCICommand::class, Command::class); @@ -731,6 +732,25 @@ static function ( ->needs('$phpDocNodeVisitors') ->giveTagged(BasePhpDocNodeVisitorInterface::class); + /** @param mixed $parameters */ + $hasForgotten = false; + $rectorConfig->beforeResolving(static function (string $abstract, array $parameters, Container $container) use ( + &$hasForgotten + ): void { + // run only once + if ($hasForgotten && ! defined('PHPUNIT_COMPOSER_INSTALL')) { + return; + } + + $skippedClassResolver = new SkippedClassResolver(); + $skippedClasses = array_keys($skippedClassResolver->resolve()); + foreach ($skippedClasses as $skippedClass) { + ContainerMemento::forgetService($container, $skippedClass); + } + + $hasForgotten = true; + }); + return $rectorConfig; }