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
15 changes: 2 additions & 13 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"composer/semver": "^3.4",
"composer/xdebug-handler": "^3.0.5",
"doctrine/inflector": "^2.1",
"illuminate/container": "12.39.*",
"entropy/entropy": "^0.4.9",
"nette/utils": "^4.1.4",
"nikic/php-parser": "^5.8",
"ondram/ci-detector": "^4.2",
Expand Down Expand Up @@ -55,7 +55,6 @@
"symplify/easy-coding-standard": "^13.2.13",
"symplify/phpstan-extensions": "^12.0.2",
"symplify/phpstan-rules": "^14.12",
"symplify/vendor-patches": "^11.5",
"tomasvotruba/class-leak": "^2.1",
"tomasvotruba/fast-unit": "^0.1",
"tomasvotruba/type-coverage": "^2.3",
Expand Down Expand Up @@ -115,22 +114,12 @@
"preload": "php build/build-preload.php .",
"release": "vendor/bin/rng --from-commit X --to-commit Y --remote-repository rectorphp/rector-symfony --remote-repository rectorphp/rector-doctrine --remote-repository rectorphp/rector-phpunit"
},
"extra": {
"patches": {
"illuminate/container": [
"https://raw.githubusercontent.com/rectorphp/vendor-patches/main/patches/illuminate-container-container-php.patch"
]
},
"composer-exit-on-patch-failure": true,
"enable-patching": true
},
"config": {
"sort-packages": true,
"platform-check": false,
"allow-plugins": {
"phpstan/extension-installer": true,
"rector/extension-installer": true,
"cweagans/composer-patches": true
"rector/extension-installer": true
}
},
"minimum-stability": "dev",
Expand Down
17 changes: 4 additions & 13 deletions rules/CodingStyle/ClassNameImport/ClassNameImportSkipper.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,14 @@
* @param ClassNameImportSkipVoterInterface[] $classNameImportSkipVoters
*/
public function __construct(
private iterable $classNameImportSkipVoters,
private array $classNameImportSkipVoters,
private UseImportsResolver $useImportsResolver,
) {
}

public function shouldSkipNameForFullyQualifiedObjectType(
File $file,
Node $node,
FullyQualifiedObjectType $fullyQualifiedObjectType
): bool {
foreach ($this->classNameImportSkipVoters as $classNameImportSkipVoter) {
if ($classNameImportSkipVoter->shouldSkip($file, $fullyQualifiedObjectType, $node)) {
return true;
}
}

return false;
public function shouldSkipNameForFullyQualifiedObjectType(File $file, Node $node, FullyQualifiedObjectType $fullyQualifiedObjectType): bool
{
return array_any($this->classNameImportSkipVoters, fn (ClassNameImportSkipVoterInterface $classNameImportSkipVoter): bool => $classNameImportSkipVoter->shouldSkip($file, $fullyQualifiedObjectType, $node));
}

/**
Expand Down
102 changes: 78 additions & 24 deletions src/Config/RectorConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@

use Composer\Semver\Semver;
use Deprecated;
use Illuminate\Container\Container;
use Entropy\Container\Container;
use Override;
use Rector\Caching\Contract\ValueObject\Storage\CacheStorageInterface;
use Rector\Composer\InstalledPackageResolver;
use Rector\Configuration\Option;
use Rector\Configuration\Parameter\SimpleParameterProvider;
use Rector\Configuration\RectorConfigBuilder;
use Rector\Contract\DependencyInjection\RelatedConfigInterface;
use Rector\Contract\DependencyInjection\ResettableInterface;
use Rector\Contract\Rector\ConfigurableRectorInterface;
use Rector\Contract\Rector\RectorInterface;
use Rector\DependencyInjection\Laravel\ContainerMemento;
use Rector\Enum\Config\Defaults;
use Rector\Exception\ShouldNotHappenException;
use Rector\Skipper\SkipCriteriaResolver\SkippedClassResolver;
Expand Down Expand Up @@ -51,14 +49,23 @@ final class RectorConfig extends Container
private array $registeredComposerBoundRuleConfigurations = [];

/**
* @var string[]
* Optional override, e.g. injected by a test to read the versions from a standalone "composer.json"
*/
private array $autotagInterfaces = [Command::class, ResettableInterface::class];
private ?InstalledPackageResolver $installedPackageResolver = null;

/**
* Optional override, e.g. injected by a test to read the versions from a standalone "composer.json"
* Explicitly registered service ids, used for bound() and to drive forgetting on skip()/reset.
*
* @var array<class-string, true>
*/
private ?InstalledPackageResolver $installedPackageResolver = null;
private array $boundAbstracts = [];

/**
* Service ids that got a factory closure registered on the entropy container.
*
* @var array<class-string, true>
*/
private array $factoryBound = [];

private static ?bool $recreated = null;

Expand Down Expand Up @@ -215,8 +222,13 @@ public function ruleWithConfiguration(string $rectorClass, array $configuration)
$this->afterResolving($rectorClass, function (ConfigurableRectorInterface $configurableRector) use (
$rectorClass
): void {
$ruleConfiguration = $this->ruleConfigurations[$rectorClass];
$configurableRector->configure($ruleConfiguration);
// the rule may have been re-registered without configuration since this callback was
// queued (e.g. a later test reusing the rule via a set), so skip when it has no config
if (! isset($this->ruleConfigurations[$rectorClass])) {
return;
}

$configurableRector->configure($this->ruleConfigurations[$rectorClass]);
});
}

Expand Down Expand Up @@ -274,13 +286,11 @@ public function rule(string $rectorClass): void

$this->singleton($rectorClass);

// the same rule can be registered by multiple sets, tag it only once,
// the same rule can be registered by multiple sets, record it only once,
// otherwise it is run twice on every node and listed twice in the reports
if (! isset($this->registeredRectorClasses[$rectorClass])) {
$this->registeredRectorClasses[$rectorClass] = true;

$this->tag($rectorClass, RectorInterface::class);

// for cache invalidation in case of change
SimpleParameterProvider::addParameter(Option::REGISTERED_RECTOR_RULES, $rectorClass);
}
Expand All @@ -304,7 +314,6 @@ public function rule(string $rectorClass): void
public function command(string $commandClass): void
{
$this->singleton($commandClass);
$this->tag($commandClass, Command::class);
}

public function import(string $filePath): void
Expand Down Expand Up @@ -500,32 +509,77 @@ public function boot(): void
}

// completely forget the Rector rule only when no path specified
ContainerMemento::forgetService($this, $skippedClass);
$this->forgetByContract($skippedClass);
}
}

/**
* @internal Use to add tag on service registrations
* Register a shared service. Without a $concrete factory the entropy container autowires the
* class on demand via reflection; register() makes it discoverable by the interfaces it
* implements, so findByContract() can find it without any explicit tagging.
*
* @param class-string $abstract
* @param (callable(self): object)|null $concrete
*/
public function autotagInterface(string $interface): void
public function singleton(string $abstract, ?callable $concrete = null): void
{
$this->autotagInterfaces[] = $interface;
$this->boundAbstracts[$abstract] = true;

if ($concrete === null) {
// no factory: let the entropy container discover it by contract
$this->register($abstract);
return;
}

if (! isset($this->factoryBound[$abstract])) {
$this->factoryBound[$abstract] = true;
// entropy calls the factory with the container instance, which is always this RectorConfig
parent::service($abstract, fn (): object => $concrete($this));
}
}

/**
* PSR-11 style accessor, kept for call sites that read services eagerly.
*
* @template TObject of object
* @param class-string<TObject> $id
* @return TObject
*/
public function get(string $id): object
{
return $this->make($id);
}

/**
* @param string $abstract
* @param class-string $abstract
*/
public function bound(string $abstract): bool
{
return isset($this->boundAbstracts[$abstract]);
}

/**
* Forget every service of the contract, both from the entropy container and from the local
* bookkeeping, so a skipped or reset service is not seen as bound and cannot be resurrected
* through discovery.
*
* @param class-string $contract
*/
#[Override]
public function singleton($abstract, mixed $concrete = null): void
public function forgetByContract(string $contract): void
{
parent::singleton($abstract, $concrete);
parent::forgetByContract($contract);

foreach ($this->autotagInterfaces as $autotagInterface) {
if (! is_a($abstract, $autotagInterface, true)) {
foreach (array_keys($this->boundAbstracts) as $abstract) {
if (! is_a($abstract, $contract, true)) {
continue;
}

$this->tag($abstract, $autotagInterface);
unset(
$this->boundAbstracts[$abstract],
$this->factoryBound[$abstract],
$this->registeredRectorClasses[$abstract],
);
}
}

Expand Down Expand Up @@ -559,7 +613,7 @@ public function getRuleConfigurations(): array
*/
public function getMainRectorClasses(): array
{
return $this->tags[RectorInterface::class] ?? [];
return array_keys($this->registeredRectorClasses);
}

/**
Expand Down
30 changes: 0 additions & 30 deletions src/Config/RegisteredService.php

This file was deleted.

27 changes: 8 additions & 19 deletions src/Configuration/RectorConfigBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@
use Rector\Config\Level\TypeDeclarationDocblocksLevel;
use Rector\Config\Level\TypeDeclarationLevel;
use Rector\Config\RectorConfig;
use Rector\Config\RegisteredService;
use Rector\Configuration\Levels\LevelRulesResolver;
use Rector\Configuration\Parameter\SimpleParameterProvider;
use Rector\Console\Notifier;
use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface;
use Rector\Contract\Rector\ConfigurableRectorInterface;
use Rector\Contract\Rector\RectorInterface;
use Rector\Doctrine\Set\DoctrineSetList;
Expand Down Expand Up @@ -173,7 +171,7 @@ final class RectorConfigBuilder
private array $typeGuardedClasses = [];

/**
* @var RegisteredService[]
* @var array<class-string>
*/
private array $registerServices = [];

Expand Down Expand Up @@ -280,15 +278,7 @@ public function __invoke(RectorConfig $rectorConfig): void

// must be in upper part, as these services might be used by rule registered bellow
foreach ($this->registerServices as $registerService) {
$rectorConfig->singleton($registerService->getClassName());

if ($registerService->getAlias()) {
$rectorConfig->alias($registerService->getClassName(), $registerService->getAlias());
}

if ($registerService->getTag()) {
$rectorConfig->tag($registerService->getClassName(), $registerService->getTag());
}
$rectorConfig->singleton($registerService);
}

if ($this->skip !== []) {
Expand Down Expand Up @@ -1114,9 +1104,12 @@ public function withTypeGuardedClasses(array $typeGuardedClasses): self
return $this;
}

public function registerService(string $className, ?string $alias = null, ?string $tag = null): self
/**
* @param class-string $className
*/
public function registerService(string $className): self
{
$this->registerServices[] = new RegisteredService($className, $alias, $tag);
$this->registerServices[] = $className;

return $this;
}
Expand All @@ -1130,11 +1123,7 @@ public function registerDecoratingNodeVisitor(string $decoratingNodeVisitorClass
{
Assert::isAOf($decoratingNodeVisitorClass, NodeVisitor::class);

$this->registerServices[] = new RegisteredService(
$decoratingNodeVisitorClass,
null,
DecoratingNodeVisitorInterface::class
);
$this->registerServices[] = $decoratingNodeVisitorClass;

return $this;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Console/Output/OutputFormatterCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ final class OutputFormatterCollector
/**
* @param OutputFormatterInterface[] $outputFormatters
*/
public function __construct(iterable $outputFormatters)
public function __construct(array $outputFormatters)
{
foreach ($outputFormatters as $outputFormatter) {
$this->outputFormatters[$outputFormatter->getName()] = $outputFormatter;
Expand Down
Loading
Loading