From eeffd3b4b4212ed6c7c6588797cf407bf5ff186a Mon Sep 17 00:00:00 2001 From: dantleech Date: Tue, 6 Dec 2016 09:07:57 +0000 Subject: [PATCH] Added scope binding --- src/Reflection/ReflectionVariable.php | 33 +++++- .../Visitor/VariableCollectionVisitor.php | 32 +++++- .../Visitor/VariableCollectionVisitorTest.php | 106 ++++++++++++++---- 3 files changed, 145 insertions(+), 26 deletions(-) diff --git a/src/Reflection/ReflectionVariable.php b/src/Reflection/ReflectionVariable.php index 1fb496402..b55170a83 100644 --- a/src/Reflection/ReflectionVariable.php +++ b/src/Reflection/ReflectionVariable.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\Variable; use BetterReflection\Reflection\ReflectionType; use BetterReflection\Reflection\ReflectionVariable; +use BetterReflection\Reflection\ReflectionFunctionAbstract; class ReflectionVariable { @@ -32,14 +33,27 @@ class ReflectionVariable */ private $type; - public static function createFromParamAndType(Param $param, ReflectionType $type): ReflectionVariable + /** + * @var ReflectionFunctionAbstract + */ + private $scopeReflection; + + public static function createFromParamAndType( + Param $param, + ReflectionType $type, + ReflectionFunctionAbstract $scopeReflection = null + ): ReflectionVariable { return self::createFromNodeAndType($param, $type); } - public static function createFromVariableAndType(Variable $variable, ReflectionType $type): ReflectionVariable + public static function createFromVariableAndType( + Variable $variable, + ReflectionType $type, + ReflectionFunctionAbstract $scopeReflection = null + ): ReflectionVariable { - return self::createFromNodeAndType($variable, $type); + return self::createFromNodeAndType($variable, $type, $scopeReflection); } public function getName(): string @@ -71,6 +85,12 @@ public function getEndPos(): int return $this->endPos; } + public function getScopeReflection(): ReflectionFunctionAbstract + { + return $this->scopeReflection; + } + + /** * Create a new reflection variable, * @@ -81,13 +101,18 @@ public function getEndPos(): int * the node, which means that the Lexer should be configured to provide * them. */ - private static function createFromNodeAndType(NodeAbstract $node, ReflectionType $type): ReflectionVariable + private static function createFromNodeAndType( + NodeAbstract $node, + ReflectionType $type, + ReflectionFunctionAbstract $scopeReflection = null + ): ReflectionVariable { $reflectionVariable = new self(); $reflectionVariable->name = $node->name; $reflectionVariable->type = $type; $reflectionVariable->startPos = $node->getAttribute('startFilePos'); $reflectionVariable->endPos = $node->getAttribute('endFilePos'); + $reflectionVariable->scopeReflection = $scopeReflection; return $reflectionVariable; } diff --git a/src/Util/Visitor/VariableCollectionVisitor.php b/src/Util/Visitor/VariableCollectionVisitor.php index b96f8a43e..76be3dd01 100644 --- a/src/Util/Visitor/VariableCollectionVisitor.php +++ b/src/Util/Visitor/VariableCollectionVisitor.php @@ -47,6 +47,11 @@ class VariableCollectionVisitor extends NodeVisitorAbstract */ private $methodParamTypes = []; + /** + * @var array + */ + private $scopes = []; + public function __construct(CompilerContext $context, TypeResolver $typeResolver = null) { $this->context = $context; @@ -79,7 +84,7 @@ public function beforeTraverse(array $nodes) public function enterNode(Node $node) { if ($node instanceof FunctionLike) { - $this->processFunctionLike($node); + $this->scopes[] = $this->processFunctionLike($node); return; } @@ -91,6 +96,15 @@ public function enterNode(Node $node) } } + public function leaveNode(Node $node) + { + if ($node instanceof FunctionLike) { + array_pop($this->scopes); + + return; + } + } + private function processFunctionLike(FunctionLike $node) { // reset when we enter the class method scope @@ -106,8 +120,11 @@ private function processFunctionLike(FunctionLike $node) $this->methodParamTypes[$param->name] = $reflectionType; // parameters count as available variables. - $this->variables[] = ReflectionVariable::createFromParamAndType($param, $reflectionType); + // TODO: Test + $this->variables[] = ReflectionVariable::createFromParamAndType($param, $reflectionType, $methodReflection); } + + return $methodReflection; } private function processAssign(Expr\Assign $node) @@ -120,7 +137,7 @@ private function processAssign(Expr\Assign $node) $type = $this->typeFromNode($node->expr); - $this->variables[] = ReflectionVariable::createFromVariableAndType($node->var, $type); + $this->variables[] = ReflectionVariable::createFromVariableAndType($node->var, $type, $this->getCurrentScope()); } private function typeFromNode(Node $expr): ReflectionType @@ -305,4 +322,13 @@ private function reflectionTypeForUnknown() { return $this->reflectionTypeFromString('mixed', false); } + + private function getCurrentScope() + { + if (empty($this->scopes)) { + return null; + } + + return end($this->scopes); + } } diff --git a/test/unit/Util/Visitor/VariableCollectionVisitorTest.php b/test/unit/Util/Visitor/VariableCollectionVisitorTest.php index 9ba8d8d13..2dd48b84c 100644 --- a/test/unit/Util/Visitor/VariableCollectionVisitorTest.php +++ b/test/unit/Util/Visitor/VariableCollectionVisitorTest.php @@ -18,6 +18,8 @@ use BetterReflection\SourceLocator\Type\AutoloadSourceLocator; use BetterReflection\SourceLocator\Type\EvaledCodeSourceLocator; use BetterReflection\NodeCompiler\CompilerContext; +use BetterReflection\Reflection\ReflectionFunction; +use BetterReflection\Reflection\ReflectionMethod; /** * @covers \BetterReflection\Util\Visitor\VariableCollectionVisitor @@ -289,6 +291,63 @@ public function getClassThree() ]; } + public function reflectionScopeBindingProvider() + { + return [ + [ + <<<'EOT' +EOT + , + ] + ]; + } + + public function testMethodReflectionScopeBinding() + { + $source = <<<'EOT' +function methodOne() +{ + $foobar = 'string'; + $barfoo = 'string'; +} + +function methodTwo() +{ + $booboo = 'string'; +} +EOT + ; + + $expectedVariableScopeNames = [ + 'foobar' => 'methodOne', + 'barfoo' => 'methodOne', + 'booboo' => 'methodTwo', + ]; + + $variables = $this->getVariablesForSource($source); + + foreach ($expectedVariableScopeNames as $expectedVariableName => $variableScopeName) { + + foreach ($variables as $variable) { + + if ($expectedVariableName === $variable->getName()) { + $this->assertInstanceOf(ReflectionMethod::class, $variable->getScopeReflection()); + $this->assertEquals($variableScopeName, $variable->getScopeReflection()->getName()); + + continue 2; + } + } + + $this->fail(sprintf('Could not find expected variable "%s"', $expectedVariableName)); + } + } + + public function testFunctionReflectionScopeBinding() + { + $this->markTestIncomplete(); + } + + /** * @param Node[] $statements * @param int $expectedReturns @@ -296,6 +355,32 @@ public function getClassThree() * @dataProvider variableCollectionProvider */ public function testVariableCollectionProvider($source, $expectedVariables) + { + $variables = $this->getVariablesForSource($source); + + foreach ($expectedVariables as $index => $expectedVariable) { + list($expectedName, $expectedType) = $expectedVariable; + $this->assertArrayHasKey($index, $variables); + $actual = $variables[$index]; + $this->assertInstanceOf(ReflectionVariable::class, $actual); + $this->assertEquals($expectedName, $actual->getName()); + $this->assertEquals($expectedType, (string) $actual->getType()); + } + } + + private function getVariablesForSource(string $source) + { + $context = $this->createCompilerContextFromSource($source); + $visitor = new VariableCollectionVisitor($context); + + $traverser = new NodeTraverser(); + $traverser->addVisitor($visitor); + $traverser->traverse([ $context->getSelf()->getAst() ]); + + return $visitor->getVariables(); + } + + private function createCompilerContextFromSource(string $source) { $source = str_replace(':content:', $source, <<<'EOT' reflect('BetterReflectionTest\Util\Visitor\Fixtures\Foobar'); - $context = new CompilerContext($reflector, $reflection); - - $visitor = new VariableCollectionVisitor($context); - - $traverser = new NodeTraverser(); - $traverser->addVisitor($visitor); - $traverser->traverse([ $reflection->getAst() ]); - - $variables = $visitor->getVariables(); - - foreach ($expectedVariables as $index => $expectedVariable) { - list($expectedName, $expectedType) = $expectedVariable; - $this->assertArrayHasKey($index, $variables); - $actual = $variables[$index]; - $this->assertInstanceOf(ReflectionVariable::class, $actual); - $this->assertEquals($expectedName, $actual->getName()); - $this->assertEquals($expectedType, (string) $actual->getType()); - } + return new CompilerContext($reflector, $reflection); } }