Skip to content

Commit

Permalink
Merge pull request #206 from Roave/return-nodes-getter
Browse files Browse the repository at this point in the history
Added ReflectionFunctionAbstract::getReturnStatementsAst() method
  • Loading branch information
Ocramius authored Jul 25, 2016
2 parents 035c3c3 + b79e30d commit be5fd00
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 0 deletions.
22 changes: 22 additions & 0 deletions src/Reflection/ReflectionFunctionAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
use BetterReflection\SourceLocator\Located\LocatedSource;
use BetterReflection\TypesFinder\FindReturnType;
use BetterReflection\TypesFinder\FindTypeFromAst;
use BetterReflection\Util\Visitor\ReturnNodeVisitor;
use Closure;
use PhpParser\Node;
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
use PhpParser\Node\Expr\Yield_ as YieldNode;
use PhpParser\Node\Expr\Closure as ClosureNode;
use PhpParser\Node\Param as ParamNode;
use phpDocumentor\Reflection\Type;
use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter;
use PhpParser\PrettyPrinterAbstract;
Expand Down Expand Up @@ -597,4 +599,24 @@ public function removeParameter($parameterName)
}
}
}

/**
* Fetch an array of all return statements found within this function.
*
* Note that return statements within smaller scopes contained (e.g. anonymous classes, closures) are not returned
* here as they are not within the immediate scope.
*
* @return Node\Stmt\Return_[]
*/
public function getReturnStatementsAst()
{
$visitor = new ReturnNodeVisitor();

$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);

$traverser->traverse($this->node->getStmts());

return $visitor->getReturnNodes();
}
}
39 changes: 39 additions & 0 deletions src/Util/Visitor/ReturnNodeVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace BetterReflection\Util\Visitor;

use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node;

class ReturnNodeVisitor extends NodeVisitorAbstract
{
/**
* @var Node\Stmt\Return_[]
*/
private $returnNodes = [];

public function enterNode(Node $node)
{
if ($this->isScopeChangingNode($node)) {
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}

if ($node instanceof Node\Stmt\Return_) {
array_push($this->returnNodes, $node);
}
}

private function isScopeChangingNode(Node $node)
{
return $node instanceof Node\FunctionLike || $node instanceof Node\Stmt\Class_;
}

/**
* @return Node\Stmt\Return_[]
*/
public function getReturnNodes()
{
return $this->returnNodes;
}
}
67 changes: 67 additions & 0 deletions test/unit/Reflection/ReflectionFunctionAbstractTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@
use BetterReflection\SourceLocator\Type\StringSourceLocator;
use phpDocumentor\Reflection\Types\Boolean;
use phpDocumentor\Reflection\Types\Integer;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Break_;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter;

/**
Expand Down Expand Up @@ -562,4 +566,67 @@ public function testRemoveParameter()

$this->assertStringStartsWith('function foo($b)', (new \PhpParser\PrettyPrinter\Standard())->prettyPrint([$function->getAst()]));
}

public function testGetReturnStatementAstReturnsStatements()
{
$php = <<<'PHP'
<?php
function foo($a) {
if ($a) {
return 0;
}
return ($a + 3);
}
PHP;

$reflector = new FunctionReflector(new StringSourceLocator($php));
$function = $reflector->reflect('foo');

$nodes = $function->getReturnStatementsAst();

$this->assertCount(2, $nodes);
$this->assertContainsOnlyInstancesOf(Return_::class, $nodes);

reset($nodes);
/** @var Return_ $first */
$first = current($nodes);
/** @var Return_ $second */
$second = next($nodes);

$this->assertInstanceOf(LNumber::class, $first->expr);
$this->assertInstanceOf(BinaryOp\Plus::class, $second->expr);
}

public function testGetReturnStatementAstDoesNotGiveInnerScopeReturnStatements()
{
$php = <<<'PHP'
<?php
function foo($a) {
$x = new class {
public function __invoke() {
return 5;
}
};
return function () use ($x) {
return $x();
};
}
PHP;

$reflector = new FunctionReflector(new StringSourceLocator($php));
$function = $reflector->reflect('foo');

$nodes = $function->getReturnStatementsAst();

$this->assertCount(1, $nodes);
$this->assertContainsOnlyInstancesOf(Return_::class, $nodes);

reset($nodes);
/** @var Return_ $first */
$first = current($nodes);

$this->assertInstanceOf(Closure::class, $first->expr);
$this->assertSame(8, $first->getAttribute('startLine'));
$this->assertSame(10, $first->getAttribute('endLine'));
}
}
63 changes: 63 additions & 0 deletions test/unit/Util/Visitor/ReturnNodeVisitorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace BetterReflectionTest\Util\Visitor;

use BetterReflection\Util\Visitor\ReturnNodeVisitor;
use PhpParser\Node;
use PhpParser\NodeTraverser;

/**
* @covers \BetterReflection\Util\Visitor\ReturnNodeVisitor
*/
class ReturnNodeVisitorTest extends \PHPUnit_Framework_TestCase
{
public function outOfScopeNodeTypeProvider()
{
return [
'onlyExpectedNodesAdded' => [
[
new Node\Scalar\MagicConst\File(),
new Node\Stmt\Return_(),
],
1
],
'returnWithinClosureShouldNotBeReturned' => [
[
new Node\Expr\Closure([
new Node\Stmt\Return_(),
]),
new Node\Stmt\Return_(),
],
1
],
'returnWithinAnonymousClassShouldNotBeReturned' => [
[
new Node\Stmt\Class_('', [
new Node\Stmt\Return_(),
]),
new Node\Stmt\Return_(),
],
1
],
];
}

/**
* @param Node[] $statements
* @param int $expectedReturns
* @dataProvider outOfScopeNodeTypeProvider
*/
public function testOnlyExpectedReturnNodesAreReturned(array $statements, $expectedReturns)
{
$visitor = new ReturnNodeVisitor();

$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);

$traverser->traverse($statements);

$foundNodes = $visitor->getReturnNodes();
$this->assertCount($expectedReturns, $foundNodes);
$this->assertContainsOnlyInstancesOf(Node\Stmt\Return_::class, $foundNodes);
}
}

0 comments on commit be5fd00

Please sign in to comment.