From 070ca62256bd217d2b96cd860d148dcd8cea3d59 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 28 Jul 2026 14:43:01 +0200 Subject: [PATCH] Wire RichParser node visitors via DI factory instead of private property hack Replace PHPStanContainerMemento reflection with a RichParserFactory service, registered in config/phpstan/parser.neon. The factory builds RichParser with a DirectExtensionsCollection of the two visitors Rector needs, so no private property of PHPStan internals is touched. A plain "nodeVisitors" argument in the config is not enough: PHPStan's AutowiredExtensionsExtension::beforeCompile() overwrites that argument for every service definition that instantiates the class directly. Factory-created definitions are skipped, so the explicit visitor list survives. --- .github/workflows/code_analysis.yaml | 2 +- config/phpstan/parser.neon | 16 +++++++ .../PHPStan/PHPStanContainerMemento.php | 42 ----------------- .../PHPStan/RichParserFactory.php | 46 +++++++++++++++++++ src/PhpParser/Parser/RectorParser.php | 3 -- .../PhpParser/Printer/PHPStanPrinterTest.php | 3 -- 6 files changed, 63 insertions(+), 49 deletions(-) delete mode 100644 src/DependencyInjection/PHPStan/PHPStanContainerMemento.php create mode 100644 src/DependencyInjection/PHPStan/RichParserFactory.php diff --git a/.github/workflows/code_analysis.yaml b/.github/workflows/code_analysis.yaml index d374be5c847..217dfb74d99 100644 --- a/.github/workflows/code_analysis.yaml +++ b/.github/workflows/code_analysis.yaml @@ -53,7 +53,7 @@ jobs: - name: 'Active Classes' run: | - vendor/bin/class-leak check bin config src rules utils --skip-suffix "Rector" --skip-type="Rector\\Utils\\Compiler\\Unprefixer" --skip-type="Rector\\NodeCollector\\BinaryOpConditionsCollector" --skip-type="Rector\\Set\\Contract\\SetListInterface" + vendor/bin/class-leak check bin config src rules utils --skip-suffix "Rector" --skip-type="Rector\\Utils\\Compiler\\Unprefixer" --skip-type="Rector\\NodeCollector\\BinaryOpConditionsCollector" --skip-type="Rector\\Set\\Contract\\SetListInterface" --skip-type="Rector\\DependencyInjection\\PHPStan\\RichParserFactory" - name: 'Compatible PHPStan versions' diff --git a/config/phpstan/parser.neon b/config/phpstan/parser.neon index 3b0f385dce2..1815cf28573 100644 --- a/config/phpstan/parser.neon +++ b/config/phpstan/parser.neon @@ -22,6 +22,22 @@ services: rectorParser: class: PHPStan\Parser\RichParser + factory: @rectorRichParserFactory::create() + arguments!: [] + autowired: no + + # RichParser used directly by Rector, see PHPStanServicesFactory::createPHPStanParser() + currentPhpVersionRichParser: + class: PHPStan\Parser\RichParser + factory: @rectorRichParserFactory::create() + arguments!: [] + autowired: no + + rectorRichParserFactory: + class: Rector\DependencyInjection\PHPStan\RichParserFactory arguments: parser: @currentPhpVersionPhpParser + nodeVisitors: + - @PHPStan\Parser\AnonymousClassVisitor + - @PHPStan\Parser\ArrayMapArgVisitor autowired: no diff --git a/src/DependencyInjection/PHPStan/PHPStanContainerMemento.php b/src/DependencyInjection/PHPStan/PHPStanContainerMemento.php deleted file mode 100644 index 81a43726d97..00000000000 --- a/src/DependencyInjection/PHPStan/PHPStanContainerMemento.php +++ /dev/null @@ -1,42 +0,0 @@ - $nodeVisitorsCollection */ - $nodeVisitorsCollection = $privatesAccessor->getPrivateProperty($richParser, 'nodeVisitors'); - - // keep visitors that are useful - // remove all the rest, https://github.com/phpstan/phpstan-src/tree/2.2.x/src/Parser - $nodeVisitorsToKeep = array_filter( - $nodeVisitorsCollection->getAll(), - static fn (NodeVisitor $nodeVisitor): bool => $nodeVisitor instanceof AnonymousClassVisitor - || $nodeVisitor instanceof ArrayMapArgVisitor - ); - - $privatesAccessor->setPrivateProperty( - $richParser, - 'nodeVisitors', - new DirectExtensionsCollection($nodeVisitorsToKeep) - ); - } -} diff --git a/src/DependencyInjection/PHPStan/RichParserFactory.php b/src/DependencyInjection/PHPStan/RichParserFactory.php new file mode 100644 index 00000000000..8d32a5e65a3 --- /dev/null +++ b/src/DependencyInjection/PHPStan/RichParserFactory.php @@ -0,0 +1,46 @@ +parser, + $this->nameResolver, + new DirectExtensionsCollection($this->nodeVisitors), + $this->ignoreLexer + ); + } +} diff --git a/src/PhpParser/Parser/RectorParser.php b/src/PhpParser/Parser/RectorParser.php index 81f72aeb206..e9479e590fd 100644 --- a/src/PhpParser/Parser/RectorParser.php +++ b/src/PhpParser/Parser/RectorParser.php @@ -9,7 +9,6 @@ use PhpParser\PhpVersion; use PHPStan\Parser\Parser; use PHPStan\Parser\RichParser; -use Rector\DependencyInjection\PHPStan\PHPStanContainerMemento; use Rector\PhpParser\ValueObject\StmtsAndTokens; use Rector\Util\Reflection\PrivatesAccessor; @@ -22,8 +21,6 @@ public function __construct( private Parser $parser, private PrivatesAccessor $privatesAccessor ) { - - PHPStanContainerMemento::removeRichVisitors($parser); } /** diff --git a/tests/PhpParser/Printer/PHPStanPrinterTest.php b/tests/PhpParser/Printer/PHPStanPrinterTest.php index d95bdafaf0d..8b68e6832f7 100644 --- a/tests/PhpParser/Printer/PHPStanPrinterTest.php +++ b/tests/PhpParser/Printer/PHPStanPrinterTest.php @@ -7,7 +7,6 @@ use PhpParser\PrettyPrinter\Standard; use PHPStan\Parser\Parser; use PHPStan\Parser\RichParser; -use Rector\DependencyInjection\PHPStan\PHPStanContainerMemento; use Rector\Testing\PHPUnit\AbstractLazyTestCase; use ReflectionProperty; @@ -24,8 +23,6 @@ public function testAddingCommentOnSomeNodesFail(): void /** @var RichParser $phpstanParser */ $phpstanParser = $this->make(Parser::class); - PHPStanContainerMemento::removeRichVisitors($phpstanParser); - $stmts = $phpstanParser->parseFile(__DIR__ . '/Fixture/some_array_map.php'); // get private property "parser"