rectorphp / rector

Instant Upgrades and Automated Refactoring of any PHP 5.3+ code
https://getrector.com
MIT License
8.59k stars 680 forks source link

Node Variable with parent of ArrayItem is missing scope required for scope refresh #7611

Closed shivampaw closed 1 year ago

shivampaw commented 1 year ago

Bug Report

Subject Details
Rector version v0.14.8
PHP version 8.0.25

If I create a basic rule (MVP is just returning the $node back) and the variable seems to be a destructured array, it is failing.

Minimal PHP Code Causing Issue

Custom Rule:

<?php

use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

class TestRule extends AbstractRector
{
    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition('Hello!', [
            new CodeSample(
                <<<'PHP'
return;
PHP,
                <<<'PHP'
return;
PHP,
            ),
        ]);
    }

    public function getNodeTypes(): array
    {
        return [Variable::class];
    }

    public function refactor(Node $node): ?Node
    {
        return $node;
    }
}

PHP Code:

    protected function setUp(): void
    {
        parent::setUp();

        $value = [
            [3, 1, false],
            [1, 2, false],
            [0, 0, true],
        ];

        foreach ($value as [$a_b, $b_c, $d_e]) {

        }
    }

I'm getting an error when it gets to the $a_b variable

 [ERROR] Could not process
         "x.php" file, due to:

         "System error: "Node "PhpParser\Node\Expr\Variable" with parent of "PhpParser\Node\Expr\ArrayItem" is missing scope
         required for scope refresh."

         Stack trace:
         #0 vendor/rector/rector/src/Rector/AbstractRector.php(347):
         Rector\Core\Application\ChangedNodeScopeRefresher->refresh(Object(PhpParser\Node\Expr\Variable), NULL,
         'UTests/Function...')
         #1 vendor/rector/rector/src/Rector/AbstractRector.php(254):
         Rector\Core\Rector\AbstractRector->refreshScopeNodes(Object(PhpParser\Node\Expr\Variable), 'UTests/Function...', NULL)
         #2 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(113):
         Rector\Core\Rector\AbstractRector->enterNode(Object(PhpParser\Node\Expr\Variable))
         #3 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(196):
         PhpParser\NodeTraverser->traverseNode(Object(PhpParser\Node\Expr\ArrayItem))
         #4 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(105):
         PhpParser\NodeTraverser->traverseArray(Array)
         #5 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(133):
         PhpParser\NodeTraverser->traverseNode(Object(PhpParser\Node\Expr\Array_))
         #6 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(196):
         PhpParser\NodeTraverser->traverseNode(Object(PhpParser\Node\Stmt\Foreach_))
         #7 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(105):
         PhpParser\NodeTraverser->traverseArray(Array)
         #8 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(196):
         PhpParser\NodeTraverser->traverseNode(Object(PhpParser\Node\Stmt\ClassMethod))
         #9 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(105):
         PhpParser\NodeTraverser->traverseArray(Array)
         #10 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(196):
         PhpParser\NodeTraverser->traverseNode(Object(PhpParser\Node\Stmt\Class_))
         #11 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(105):
         PhpParser\NodeTraverser->traverseArray(Array)
         #12 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(196):
         PhpParser\NodeTraverser->traverseNode(Object(PhpParser\Node\Stmt\Namespace_))
         #13 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(85):
         PhpParser\NodeTraverser->traverseArray(Array)
         #14 vendor/rector/rector/src/PhpParser/NodeTraverser/RectorNodeTraverser.php(42):
         PhpParser\NodeTraverser->traverse(Array)
         #15 vendor/rector/rector/src/Application/FileProcessor.php(60):
         Rector\Core\PhpParser\NodeTraverser\RectorNodeTraverser->traverse(Array)
         #16 vendor/rector/rector/src/Application/FileProcessor/PhpFileProcessor.php(133):
         Rector\Core\Application\FileProcessor->refactor(Object(Rector\Core\ValueObject\Application\File),
         Object(Rector\Core\ValueObject\Configuration))
         #17 vendor/rector/rector/src/Application/FileProcessor/PhpFileProcessor.php(101):
         Rector\Core\Application\FileProcessor\PhpFileProcessor->refactorNodesWithRectors(Object(Rector\Core\ValueObject\Applica
         tion\File), Object(Rector\Core\ValueObject\Configuration))
         #18 vendor/rector/rector/packages/Parallel/WorkerRunner.php(98):
         Rector\Core\Application\FileProcessor\PhpFileProcessor->process(Object(Rector\Core\ValueObject\Application\File),
         Object(Rector\Core\ValueObject\Configuration))
         #19 vendor/rector/rector/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php(97):
         Rector\Parallel\WorkerRunner->Rector\Parallel\{closure}(Array)
         #20 vendor/rector/rector/vendor/clue/ndjson-react/src/Decoder.php(110):
         RectorPrefix202211\Evenement\EventEmitter->emit('data', Array)
         #21 vendor/rector/rector/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php(97):
         RectorPrefix202211\Clue\React\NDJson\Decoder->handleData(Array)
         #22 vendor/rector/rector/vendor/react/stream/src/Util.php(62): RectorPrefix202211\Evenement\EventEmitter->emit('data',
         Array)
         #23 vendor/rector/rector/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php(97):
         RectorPrefix202211\React\Stream\Util::RectorPrefix202211\React\Stream\{closure}('{"action":"main...')
         #24 vendor/rector/rector/vendor/react/stream/src/DuplexResourceStream.php(154):
         RectorPrefix202211\Evenement\EventEmitter->emit('data', Array)
         #25 vendor/rector/rector/vendor/react/event-loop/src/StreamSelectLoop.php(201):
         RectorPrefix202211\React\Stream\DuplexResourceStream->handleData(Resource id #2836)
         #26 vendor/rector/rector/vendor/react/event-loop/src/StreamSelectLoop.php(173):
         RectorPrefix202211\React\EventLoop\StreamSelectLoop->waitForStreamActivity(NULL)
         #27 vendor/rector/rector/src/Console/Command/WorkerCommand.php(63):
         RectorPrefix202211\React\EventLoop\StreamSelectLoop->run()
         #28 vendor/rector/rector/vendor/symfony/console/Command/Command.php(307):
         Rector\Core\Console\Command\WorkerCommand->execute(Object(RectorPrefix202211\Symfony\Component\Console\Input\ArgvInput)
         , Object(RectorPrefix202211\Symfony\Component\Console\Output\ConsoleOutput))
         #29 vendor/rector/rector/vendor/symfony/console/Application.php(896):
         RectorPrefix202211\Symfony\Component\Console\Command\Command->run(Object(RectorPrefix202211\Symfony\Component\Console\I
         nput\ArgvInput), Object(RectorPrefix202211\Symfony\Component\Console\Output\ConsoleOutput))
         #30 vendor/rector/rector/vendor/symfony/console/Application.php(312):
         RectorPrefix202211\Symfony\Component\Console\Application->doRunCommand(Object(Rector\Core\Console\Command\WorkerCommand
         ), Object(RectorPrefix202211\Symfony\Component\Console\Input\ArgvInput),
         Object(RectorPrefix202211\Symfony\Component\Console\Output\ConsoleOutput))
         #31 vendor/rector/rector/src/Console/ConsoleApplication.php(49):
         RectorPrefix202211\Symfony\Component\Console\Application->doRun(Object(RectorPrefix202211\Symfony\Component\Console\Inp
         ut\ArgvInput), Object(RectorPrefix202211\Symfony\Component\Console\Output\ConsoleOutput))
         #32 vendor/rector/rector/vendor/symfony/console/Application.php(208):
         Rector\Core\Console\ConsoleApplication->doRun(Object(RectorPrefix202211\Symfony\Component\Console\Input\ArgvInput),
         Object(RectorPrefix202211\Symfony\Component\Console\Output\ConsoleOutput))
         #33 vendor/rector/rector/bin/rector.php(128): RectorPrefix202211\Symfony\Component\Console\Application->run()
         #34 vendor/rector/rector/bin/rector(5): require_once('...')
         #35 vendor/bin/rector(117): include('...')
         #36 {main}". On line: 85

Expected Behaviour

No error in this case

samsonasik commented 1 year ago

You need to extends AbstractScopeAwareRector and implements public function refactorWithScope(Node $node, Scope $scope) for that:

use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use PHPStan\Analyser\Scope;
use Rector\Core\Rector\AbstractScopeAwareRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

class TestRector extends AbstractScopeAwareRector
{
    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition('Hello!', [
            new CodeSample(
                <<<'PHP'
return;
PHP,
                <<<'PHP'
return;
PHP,
            ),
        ]);
    }

    public function getNodeTypes(): array
    {
        return [Variable::class];
    }

    public function refactorWithScope(Node $node, Scope $scope)
    {
        return $node;
    }
}
shivampaw commented 1 year ago

Thanks @samsonasik, I've tried that but get a similar error:

 [ERROR] Could not process
         "x.php"
         file, due to:
         "System error: "Scope not available on "PhpParser\Node\Expr\Variable" node with parent node of
         "PhpParser\Node\Expr\ArrayItem", but is required by a refactorWithScope() method of
         "CodeRector\CustomRules\CamelCaseLocalVariableNames" rule. Fix scope refresh on changed nodes first"
         Run Rector with "--debug" option and post the report here: https://github.com/rectorphp/rector/issues/new". On
         line: 53
samsonasik commented 1 year ago

I see, that's bug on PHPStanNodeScopeResolver on foreach value with ArrayItem, I will try to provide a fix :)