Skip to content
Closed
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
32 changes: 32 additions & 0 deletions src/Config/RectorConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,38 @@ public function forgetByContract(string $contract): void
}
}

/**
* The container caches every instance it builds, so a rule that another service takes as a
* constructor dependency is cached like any other object. Returned unfiltered it would count
* as an active rule although no config ever registered it — and this is where the active set
* comes from on both paths: the node traverser is autowired from an "@param RectorInterface[]"
* parameter, and AbstractRectorTestCase reads the same list.
*
* The narrowing keys off the requested contract, so a wider one still sees every instance -
* findByContract(NodeVisitor::class) includes unregistered rules, since RectorInterface
* extends NodeVisitor.
*
* @template TType as object
*
* @param class-string<TType> $contractClass
* @return list<TType>
*/
public function findByContract(string $contractClass): array
{
$instances = parent::findByContract($contractClass);

if (! is_a($contractClass, RectorInterface::class, true)) {
return $instances;
}

return array_values(
array_filter(
$instances,
fn (object $instance): bool => isset($this->registeredRectorClasses[$instance::class])
)
);
}

public function reportingRealPath(bool $absolute = true): void
{
SimpleParameterProvider::setParameter(Option::ABSOLUTE_FILE_PATH, $absolute);
Expand Down
12 changes: 10 additions & 2 deletions src/Testing/PHPUnit/AbstractRectorTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Iterator;
use Nette\Utils\FileSystem;
use Nette\Utils\Strings;
use PhpParser\NodeVisitor;
use PHPUnit\Framework\ExpectationFailedException;
use Rector\Application\ApplicationFileProcessor;
use Rector\Autoloading\AdditionalAutoloader;
Expand Down Expand Up @@ -100,8 +101,15 @@ protected function setUp(): void
$this->forgetRectorsRules();
$rectorConfig->resetRuleConfigurations();

// this has to be always empty, so we can add new rules with their configuration
$this->assertEmpty($rectorConfig->findByContract(RectorInterface::class));
// this has to be always empty, so we can add new rules with their configuration.
// asked through the wider contract on purpose: the rule contract now reports only
// registered rules, and the registered list was just cleared, so it would answer
// "empty" without looking at what the forget above actually left behind
$leftOverRectors = array_filter(
$rectorConfig->findByContract(NodeVisitor::class),
static fn (NodeVisitor $nodeVisitor): bool => $nodeVisitor instanceof RectorInterface
);
$this->assertSame([], $leftOverRectors);

$this->bootFromConfigFiles([$configFile]);

Expand Down
20 changes: 20 additions & 0 deletions tests/Config/RectorConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
use Rector\Config\RectorConfig;
use Rector\Configuration\Option;
use Rector\Configuration\Parameter\SimpleParameterProvider;
use Rector\Contract\Rector\RectorInterface;
use Rector\Renaming\Rector\MethodCall\RenameMethodRector;
use Rector\Renaming\Rector\Name\RenameClassRector;
use Rector\Renaming\Rector\PropertyFetch\RenamePropertyRector;
use Rector\Renaming\ValueObject\MethodCallRename;
use Rector\Renaming\ValueObject\RenameProperty;
use Rector\Symfony\Set\SymfonySetList;
use Rector\Testing\PHPUnit\AbstractLazyTestCase;
use Rector\Tests\Config\Source\DependencyOnlyRector;
use Rector\Tests\Config\Source\RegisteringRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromReturnNewRector;

final class RectorConfigTest extends AbstractLazyTestCase
Expand Down Expand Up @@ -89,4 +92,21 @@ public function testRuleWithConfigurationComposerVersionBoundOnMissingPackage():
$registeredRectorRules = SimpleParameterProvider::provideArrayParameter(Option::REGISTERED_RECTOR_RULES);
$this->assertNotContains(RenamePropertyRector::class, $registeredRectorRules);
}

public function testRuleTakenOnlyAsDependencyIsNotActive(): void
{
$rectorConfig = $this->getContainer();

$rectorConfig->rule(RegisteringRector::class);

// building the registered rule also builds and caches its constructor dependency,
// which is a rule too - but one no config asked for
$activeRectorClasses = array_map(
static fn (object $rector): string => $rector::class,
$rectorConfig->findByContract(RectorInterface::class)
);

$this->assertContains(RegisteringRector::class, $activeRectorClasses);
$this->assertNotContains(DependencyOnlyRector::class, $activeRectorClasses);
}
}
35 changes: 35 additions & 0 deletions tests/Config/Source/DependencyOnlyRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Config\Source;

use PhpParser\Node;
use PhpParser\Node\Scalar\String_;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* Never registered. It exists only as a constructor dependency of @see RegisteringRector,
* which is enough for the container to build and cache it.
*/
final class DependencyOnlyRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('', []);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [String_::class];
}

public function refactor(Node $node): ?Node
{
return null;
}
}
36 changes: 36 additions & 0 deletions tests/Config/Source/RegisteringRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Config\Source;

use PhpParser\Node;
use PhpParser\Node\Scalar\String_;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

final class RegisteringRector extends AbstractRector
{
public function __construct(
private readonly DependencyOnlyRector $dependencyOnlyRector
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('', []);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [String_::class];
}

public function refactor(Node $node): ?Node
{
return $this->dependencyOnlyRector->refactor($node);
}
}