From f1a7e3076f01c24bafd72bd3700dc60603aac1f1 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 14 Sep 2014 14:16:01 +0200 Subject: [PATCH 01/36] start cor --- src/Visitor/VisitorGateway.php | 86 ++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/Visitor/VisitorGateway.php diff --git a/src/Visitor/VisitorGateway.php b/src/Visitor/VisitorGateway.php new file mode 100644 index 0000000..7ca901f --- /dev/null +++ b/src/Visitor/VisitorGateway.php @@ -0,0 +1,86 @@ + VisitorState + */ + public function __construct(array $visitor) + { + foreach ($visitor as $k => $v) { + if (!($v instanceof State\State)) { + throw new \InvalidArgumentException("Invalid visitor for index $k"); + } + $v->setContext($this); + $this->stateList[$v->getName()] = $v; + } + + $this->stateStack = new \SplObjectStorage(); + } + + /** + * @inheritdoc + */ + public function enterNode(Node $node) + { + foreach ($this->stateStack as $keyNode) { + $v = $this->stateStack->getInfo(); + $ret = $v->enter($node); + if (!is_null($ret)) { + return $ret; + } + } + } + + /** + * @inheritdoc + */ + public function leaveNode(\PhpParser\Node $node) + { + foreach ($this->stateStack as $keyNode) { + $v = $this->stateStack->getInfo(); + $ret = $v->leave($node); + if (!is_null($ret)) { + $this->stateStack->detach($node); + return $ret; + } + } + $this->stateStack->detach($node); + } + + public function pushState($stateKey, Node $node) + { + if (!array_key_exists($stateKey, $this->stateList)) { + throw new \InvalidArgumentException("$stateKey is not registered state"); + } + $v = $this->stateList[$stateKey]; + + $this->stateStack[$node] = $v; + } + +} \ No newline at end of file From 3c1797c98fdbd9017f07eb3dccc7e6499ddd447f Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 14 Sep 2014 14:16:55 +0200 Subject: [PATCH 02/36] adding context --- src/Visitor/VisitorGateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Visitor/VisitorGateway.php b/src/Visitor/VisitorGateway.php index 7ca901f..c9ea4ae 100644 --- a/src/Visitor/VisitorGateway.php +++ b/src/Visitor/VisitorGateway.php @@ -12,7 +12,7 @@ /** * VisitorGateway is a state pattern for multiple visitors */ -class VisitorGateway extends NodeVisitorAbstract +class VisitorGateway extends NodeVisitorAbstract implements State\VisitorContext { /** From 3877c662e8bd8082d446bce1e63ba2558ec85842 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 14 Sep 2014 14:50:17 +0200 Subject: [PATCH 03/36] with cor --- src/Visitor/State/AbstractState.php | 23 +++++++++++++ src/Visitor/State/ClassLevel.php | 38 +++++++++++++++++++++ src/Visitor/State/ClassMethodLevel.php | 39 +++++++++++++++++++++ src/Visitor/State/FileLevel.php | 47 ++++++++++++++++++++++++++ src/Visitor/State/PackageLevel.php | 36 ++++++++++++++++++++ src/Visitor/State/State.php | 43 +++++++++++++++++++++++ src/Visitor/State/VisitorContext.php | 27 +++++++++++++++ src/Visitor/VisitorGateway.php | 20 ++++++++--- 8 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 src/Visitor/State/AbstractState.php create mode 100644 src/Visitor/State/ClassLevel.php create mode 100644 src/Visitor/State/ClassMethodLevel.php create mode 100644 src/Visitor/State/FileLevel.php create mode 100644 src/Visitor/State/PackageLevel.php create mode 100644 src/Visitor/State/State.php create mode 100644 src/Visitor/State/VisitorContext.php diff --git a/src/Visitor/State/AbstractState.php b/src/Visitor/State/AbstractState.php new file mode 100644 index 0000000..d4caa61 --- /dev/null +++ b/src/Visitor/State/AbstractState.php @@ -0,0 +1,23 @@ +context = $ctx; + } + +} \ No newline at end of file diff --git a/src/Visitor/State/ClassLevel.php b/src/Visitor/State/ClassLevel.php new file mode 100644 index 0000000..5f5f3c2 --- /dev/null +++ b/src/Visitor/State/ClassLevel.php @@ -0,0 +1,38 @@ +getType()) { + case 'Stmt_ClassMethod': + if ($node->type === Node\Stmt\Class_::MODIFIER_PUBLIC) { + $this->context->pushState('class-method', $node); + } + break; + } + } + + public function getName() + { + return 'class'; + } + + public function leave(Node $node) + { + + } + +} \ No newline at end of file diff --git a/src/Visitor/State/ClassMethodLevel.php b/src/Visitor/State/ClassMethodLevel.php new file mode 100644 index 0000000..fb9defa --- /dev/null +++ b/src/Visitor/State/ClassMethodLevel.php @@ -0,0 +1,39 @@ +context->getNodeFor('class'); + $currentMethod = $this->context->getNodeFor('class-method'); + + switch ($node->getType()) { + case 'Expr_MethodCall': + // ... + break; + } + } + + public function getName() + { + return 'class-method'; + } + + public function leave(Node $node) + { + + } + +} \ No newline at end of file diff --git a/src/Visitor/State/FileLevel.php b/src/Visitor/State/FileLevel.php new file mode 100644 index 0000000..8c51ad9 --- /dev/null +++ b/src/Visitor/State/FileLevel.php @@ -0,0 +1,47 @@ +getType()) { + case 'Stmt_Namespace': + // ... + break; + case 'Stmt_UseUse': + break; + case 'Stmt_Class': + $this->context->pushState('class', $node); + break; + case 'Stmt_Trait': + $this->context->pushState('trait', $node); + break; + case 'Stmt_Interface': + $this->context->pushState('interface', $node); + break; + } + } + + public function getName() + { + return 'file'; + } + + public function leave(Node $node) + { + + } + +} \ No newline at end of file diff --git a/src/Visitor/State/PackageLevel.php b/src/Visitor/State/PackageLevel.php new file mode 100644 index 0000000..52e914a --- /dev/null +++ b/src/Visitor/State/PackageLevel.php @@ -0,0 +1,36 @@ +getType()) { + case 'PhpFile': + $this->context->pushState('file', $node); + break; + } + } + + public function getName() + { + return 'package'; + } + + public function leave(Node $node) + { + + } + +} \ No newline at end of file diff --git a/src/Visitor/State/State.php b/src/Visitor/State/State.php new file mode 100644 index 0000000..bc321ab --- /dev/null +++ b/src/Visitor/State/State.php @@ -0,0 +1,43 @@ +stateStack as $keyNode) { $v = $this->stateStack->getInfo(); @@ -76,11 +78,19 @@ public function leaveNode(\PhpParser\Node $node) public function pushState($stateKey, Node $node) { if (!array_key_exists($stateKey, $this->stateList)) { - throw new \InvalidArgumentException("$stateKey is not registered state"); + throw new \InvalidArgumentException("$stateKey is not a registered state"); } - $v = $this->stateList[$stateKey]; + $this->stateStack[$node] = $this->stateList[$stateKey]; + } - $this->stateStack[$node] = $v; + public function getNodeFor($stateKey) + { + foreach ($this->stateStack as $node) { + $v = $this->stateStack->getInfo(); + if ($stateKey === $v->getName()) { + return $node; + } + } } } \ No newline at end of file From 6a2f2ce2dc2161230d667b0a48ceebf6501f646f Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 14 Sep 2014 18:16:46 +0200 Subject: [PATCH 04/36] skeleton for visitor with cor --- src/Visitor/State/AbstractState.php | 5 ++ src/Visitor/State/ClassLevel.php | 10 ++-- src/Visitor/State/ClassMethodLevel.php | 5 -- src/Visitor/State/FileLevel.php | 77 ++++++++++++++++++++++++-- src/Visitor/State/PackageLevel.php | 5 -- src/Visitor/State/VisitorContext.php | 8 +++ src/Visitor/VisitorGateway.php | 45 +++++++++++++-- 7 files changed, 129 insertions(+), 26 deletions(-) diff --git a/src/Visitor/State/AbstractState.php b/src/Visitor/State/AbstractState.php index d4caa61..76e940b 100644 --- a/src/Visitor/State/AbstractState.php +++ b/src/Visitor/State/AbstractState.php @@ -20,4 +20,9 @@ public function setContext(VisitorContext $ctx) $this->context = $ctx; } + public function leave(Node $node) + { + + } + } \ No newline at end of file diff --git a/src/Visitor/State/ClassLevel.php b/src/Visitor/State/ClassLevel.php index 5f5f3c2..393c7fd 100644 --- a/src/Visitor/State/ClassLevel.php +++ b/src/Visitor/State/ClassLevel.php @@ -17,6 +17,11 @@ class ClassLevel extends AbstractState public function enter(Node $node) { switch ($node->getType()) { + + case 'Stmt_TraitUse' : + $this->importSignatureTrait($node); + break; + case 'Stmt_ClassMethod': if ($node->type === Node\Stmt\Class_::MODIFIER_PUBLIC) { $this->context->pushState('class-method', $node); @@ -30,9 +35,4 @@ public function getName() return 'class'; } - public function leave(Node $node) - { - - } - } \ No newline at end of file diff --git a/src/Visitor/State/ClassMethodLevel.php b/src/Visitor/State/ClassMethodLevel.php index fb9defa..bd8195f 100644 --- a/src/Visitor/State/ClassMethodLevel.php +++ b/src/Visitor/State/ClassMethodLevel.php @@ -31,9 +31,4 @@ public function getName() return 'class-method'; } - public function leave(Node $node) - { - - } - } \ No newline at end of file diff --git a/src/Visitor/State/FileLevel.php b/src/Visitor/State/FileLevel.php index 8c51ad9..99e4f35 100644 --- a/src/Visitor/State/FileLevel.php +++ b/src/Visitor/State/FileLevel.php @@ -14,20 +14,44 @@ class FileLevel extends AbstractState { + /** + * @var null|PHPParser_Node_Name Current namespace + */ + protected $namespace; + + /** + * @var array Currently defined namespace and class aliases + */ + protected $aliases; + public function enter(Node $node) { switch ($node->getType()) { - case 'Stmt_Namespace': - // ... + + case 'Stmt_Namespace' : + $this->namespace = $node->name; + $this->aliases = array(); break; - case 'Stmt_UseUse': + + case 'Stmt_UseUse' : + if (isset($this->aliases[$node->alias])) { + throw new \PhpParser\Error( + sprintf( + 'Cannot use "%s" as "%s" because the name is already in use', $node->name, $node->alias + ), $node->getLine() + ); + } + $this->aliases[$node->alias] = $node->name; break; + case 'Stmt_Class': $this->context->pushState('class', $node); break; + case 'Stmt_Trait': $this->context->pushState('trait', $node); break; + case 'Stmt_Interface': $this->context->pushState('interface', $node); break; @@ -39,9 +63,52 @@ public function getName() return 'file'; } - public function leave(Node $node) + /** + * resolve the Name with current namespace and alias + * + * @param \PHPParser_Node_Name $src + * @return \PHPParser_Node_Name|\PHPParser_Node_Name_FullyQualified + */ + public function resolveClassName(Node\Name $src) + { + $name = clone $src; + // don't resolve special class names + if (in_array((string) $name, array('self', 'parent', 'static'))) { + return $name; + } + + // fully qualified names are already resolved + if ($name->isFullyQualified()) { + return $name; + } + + // resolve aliases (for non-relative names) + if (!$name->isRelative() && isset($this->aliases[$name->getFirst()])) { + $name->setFirst($this->aliases[$name->getFirst()]); + // if no alias exists prepend current namespace + } elseif (null !== $this->namespace) { + $name->prepend($this->namespace); + } + + return new Node\Name\FullyQualified($name->parts, $name->getAttributes()); + } + + /** + * Helper : get the FQCN of the given $node->name + * + * @param PHPParser_Node $node + * @return string + */ + public function getNamespacedName(Node $node) { - + if (null !== $this->namespace) { + $namespacedName = clone $this->namespace; + $namespacedName->append($node->name); + } else { + $namespacedName = $node->name; + } + + return (string) $namespacedName; } } \ No newline at end of file diff --git a/src/Visitor/State/PackageLevel.php b/src/Visitor/State/PackageLevel.php index 52e914a..bb80b24 100644 --- a/src/Visitor/State/PackageLevel.php +++ b/src/Visitor/State/PackageLevel.php @@ -28,9 +28,4 @@ public function getName() return 'package'; } - public function leave(Node $node) - { - - } - } \ No newline at end of file diff --git a/src/Visitor/State/VisitorContext.php b/src/Visitor/State/VisitorContext.php index 04b35e9..ab82e5f 100644 --- a/src/Visitor/State/VisitorContext.php +++ b/src/Visitor/State/VisitorContext.php @@ -24,4 +24,12 @@ interface VisitorContext public function pushState($stateKey, Node $node); public function getNodeFor($stateKey); + + public function getState($stateKey); + + public function getReflectionContext(); + + public function getGraphContext(); + + public function getGraph(); } \ No newline at end of file diff --git a/src/Visitor/VisitorGateway.php b/src/Visitor/VisitorGateway.php index 503dd9c..1e1042f 100644 --- a/src/Visitor/VisitorGateway.php +++ b/src/Visitor/VisitorGateway.php @@ -8,6 +8,10 @@ use PhpParser\NodeVisitorAbstract; use PhpParser\Node; +use Trismegiste\Mondrian\Transform\ReflectionContext; +use Trismegiste\Mondrian\Transform\GraphContext; +use Trismegiste\Mondrian\Graph\Vertex; +use Trismegiste\Mondrian\Graph\Graph; /** * VisitorGateway is a multiple patterns for chaining visitors @@ -26,14 +30,21 @@ class VisitorGateway extends NodeVisitorAbstract implements State\VisitorContext * @var array $stateStack Stack of previous state */ protected $stateStack; + protected $reflectionCtx; + protected $graphCtx; + protected $graph; /** * Ctor * - * @param array $visitor a typelist PHPParser_Node => VisitorState + * @param array $visitor a list of State */ - public function __construct(array $visitor) + public function __construct(array $visitor, ReflectionContext $ref, GraphContext $grf, Graph $g) { + $this->graphCtx = $grf; + $this->graph = $g; + $this->reflectionCtx = $ref; + foreach ($visitor as $k => $v) { if (!($v instanceof State\State)) { throw new \InvalidArgumentException("Invalid visitor for index $k"); @@ -77,10 +88,8 @@ public function leaveNode(Node $node) public function pushState($stateKey, Node $node) { - if (!array_key_exists($stateKey, $this->stateList)) { - throw new \InvalidArgumentException("$stateKey is not a registered state"); - } - $this->stateStack[$node] = $this->stateList[$stateKey]; + $state = $this->getState($stateKey); + $this->stateStack[$node] = $state; } public function getNodeFor($stateKey) @@ -93,4 +102,28 @@ public function getNodeFor($stateKey) } } + public function getState($stateKey) + { + if (!array_key_exists($stateKey, $this->stateList)) { + throw new \InvalidArgumentException("$stateKey is not a registered state"); + } + + return $this->stateList[$stateKey]; + } + + public function getGraph() + { + return $this->graph; + } + + public function getGraphContext() + { + return $this->graphCtx; + } + + public function getReflectionContext() + { + return $this->reflectionCtx; + } + } \ No newline at end of file From 9de1fe065879421ff8882b86eb3106e452f81509 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 14 Sep 2014 18:51:05 +0200 Subject: [PATCH 05/36] trying refactor for symbolmap visitor --- src/Visitor/State/ClassLevel.php | 18 ++++++++++++- src/Visitor/State/FileLevel.php | 40 +++++++++++++++++++++++++++++ src/Visitor/SymbolMap/Collector.php | 36 ++++++++++++++++++++++++++ src/Visitor/VisitorGateway.php | 1 + 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/Visitor/SymbolMap/Collector.php diff --git a/src/Visitor/State/ClassLevel.php b/src/Visitor/State/ClassLevel.php index 393c7fd..2ba096a 100644 --- a/src/Visitor/State/ClassLevel.php +++ b/src/Visitor/State/ClassLevel.php @@ -24,12 +24,28 @@ public function enter(Node $node) case 'Stmt_ClassMethod': if ($node->type === Node\Stmt\Class_::MODIFIER_PUBLIC) { - $this->context->pushState('class-method', $node); + $classNode = $this->context->getNodeFor('class'); + $fileState = $this->context->getState('file'); + $fqcn = $fileState->getNamespacedName($classNode); + $this->context->getReflectionContext()->addMethodToClass($fqcn, $node->name); } break; } } + protected function importSignatureTrait(Node\Stmt\TraitUse $node) + { + $classNode = $this->context->getNodeFor('class'); + $fileState = $this->context->getState('file'); + $fqcn = $fileState->getNamespacedName($classNode); + // @todo do not forget aliases + foreach ($node->traits as $import) { + $name = (string) $stateFile->resolveClassName($import); + $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_TRAIT); + $this->context->getReflectionContext()->pushUseTrait($fqcn, $name); + } + } + public function getName() { return 'class'; diff --git a/src/Visitor/State/FileLevel.php b/src/Visitor/State/FileLevel.php index 99e4f35..e9091bd 100644 --- a/src/Visitor/State/FileLevel.php +++ b/src/Visitor/State/FileLevel.php @@ -7,6 +7,7 @@ namespace Trismegiste\Mondrian\Visitor\State; use PhpParser\Node; +use Trismegiste\Mondrian\Transform\ReflectionContext; /** * FileLevel is ... @@ -45,19 +46,58 @@ public function enter(Node $node) break; case 'Stmt_Class': + $this->enterClassNode($node); $this->context->pushState('class', $node); break; case 'Stmt_Trait': + $this->enterTraitNode($node); $this->context->pushState('trait', $node); break; case 'Stmt_Interface': + $this->enterInterfaceNode($node); $this->context->pushState('interface', $node); break; } } + protected function enterClassNode(Node\Stmt\Class_ $node) + { + $fqcn = $this->getNamespacedName($node); + $this->context->getReflectionContext()->initSymbol($fqcn, ReflectionContext::SYMBOL_CLASS); + // extends + if (!is_null($node->extends)) { + $name = (string) $this->resolveClassName($node->extends); + $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_CLASS); + $this->context->getReflectionContext()->pushParentClass($fqcn, $name); + } + // implements + foreach ($node->implements as $parent) { + $name = (string) $this->resolveClassName($parent); + $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_INTERFACE); + $this->context->getReflectionContext()->pushParentClass($fqcn, $name); + } + } + + protected function enterInterfaceNode(\PHPParser_Node_Stmt_Interface $node) + { + $fqcn = $this->getNamespacedName($node); + $this->context->getReflectionContext()->initSymbol($fqcn, ReflectionContext::SYMBOL_INTERFACE); + // extends + foreach ($node->extends as $interf) { + $name = (string) $this->resolveClassName($interf); + $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_INTERFACE); + $this->context->getReflectionContext()->pushParentClass($fqcn, $name); + } + } + + protected function enterTraitNode(\PHPParser_Node_Stmt_Trait $node) + { + $fqcn = $this->getNamespacedName($node); + $this->context->getReflectionContext()->initSymbol($fqcn, ReflectionContext::SYMBOL_TRAIT); + } + public function getName() { return 'file'; diff --git a/src/Visitor/SymbolMap/Collector.php b/src/Visitor/SymbolMap/Collector.php new file mode 100644 index 0000000..ef32db2 --- /dev/null +++ b/src/Visitor/SymbolMap/Collector.php @@ -0,0 +1,36 @@ +context->resolveSymbol(); + } + +} \ No newline at end of file diff --git a/src/Visitor/VisitorGateway.php b/src/Visitor/VisitorGateway.php index 1e1042f..8349227 100644 --- a/src/Visitor/VisitorGateway.php +++ b/src/Visitor/VisitorGateway.php @@ -54,6 +54,7 @@ public function __construct(array $visitor, ReflectionContext $ref, GraphContext } $this->stateStack = new \SplObjectStorage(); + $this->stateStack->attach('start', $visitor[0]); } /** From 0b1beeb7981902e1179a38cb43da5a8fb927f078 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 14 Sep 2014 19:22:17 +0200 Subject: [PATCH 06/36] proof of concept ok --- src/Visitor/State/AbstractState.php | 2 + src/Visitor/SymbolMap/Collector.php | 2 +- src/Visitor/VisitorGateway.php | 2 +- tests/Visitor/SymbolMap/CollectorTest.php | 56 +++++++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 tests/Visitor/SymbolMap/CollectorTest.php diff --git a/src/Visitor/State/AbstractState.php b/src/Visitor/State/AbstractState.php index 76e940b..54bb7ed 100644 --- a/src/Visitor/State/AbstractState.php +++ b/src/Visitor/State/AbstractState.php @@ -6,6 +6,8 @@ namespace Trismegiste\Mondrian\Visitor\State; +use PhpParser\Node; + /** * AbstractState is a abstract state */ diff --git a/src/Visitor/SymbolMap/Collector.php b/src/Visitor/SymbolMap/Collector.php index ef32db2..df536f3 100644 --- a/src/Visitor/SymbolMap/Collector.php +++ b/src/Visitor/SymbolMap/Collector.php @@ -30,7 +30,7 @@ public function __construct(ReflectionContext $ref, GraphContext $grf, Graph $g) public function afterTraverse(array $dummy) { - $this->context->resolveSymbol(); + $this->reflectionCtx->resolveSymbol(); } } \ No newline at end of file diff --git a/src/Visitor/VisitorGateway.php b/src/Visitor/VisitorGateway.php index 8349227..1b7edd1 100644 --- a/src/Visitor/VisitorGateway.php +++ b/src/Visitor/VisitorGateway.php @@ -54,7 +54,7 @@ public function __construct(array $visitor, ReflectionContext $ref, GraphContext } $this->stateStack = new \SplObjectStorage(); - $this->stateStack->attach('start', $visitor[0]); + $this->stateStack->attach(new \stdClass(), $visitor[0]); } /** diff --git a/tests/Visitor/SymbolMap/CollectorTest.php b/tests/Visitor/SymbolMap/CollectorTest.php new file mode 100644 index 0000000..50eff8c --- /dev/null +++ b/tests/Visitor/SymbolMap/CollectorTest.php @@ -0,0 +1,56 @@ +context = new ReflectionContext(); + $mockGraphCtx = $this->getMockBuilder('Trismegiste\Mondrian\Transform\GraphContext') + ->disableOriginalConstructor() + ->getMock(); + $mockGraph = $this->getMock('Trismegiste\Mondrian\Graph\Graph'); + $this->visitor = new Collector($this->context, $mockGraphCtx, $mockGraph); + $this->parser = new PackageParser(new \PHPParser_Parser(new \PHPParser_Lexer())); + $this->traverser = new \PHPParser_NodeTraverser(); + $this->traverser->addVisitor($this->visitor); + } + + public function testSimpleCase() + { + $iter = array(new SplFileInfo(__DIR__ . '/../../Fixtures/Project/Concrete.php', '/../../Fixtures/Project/', 'Concrete.php')); + $stmts = $this->parser->parse(new \ArrayIterator($iter)); + $this->traverser->traverse($stmts); + $this->visitor->afterTraverse(array()); + + $this->assertAttributeEquals(array( + 'Project\\Concrete' => array( + 'type' => 'c', + 'parent' => array(), + 'method' => array('simple' => 'Project\\Concrete'), + 'use' => [] + ), + ), 'inheritanceMap', $this->context); + } + +} From d0dd906c49e097579e14a9230d954b4196648dab Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 14 Sep 2014 19:37:21 +0200 Subject: [PATCH 07/36] ready to rox --- tests/Visitor/SymbolMap/CollectorTest.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/Visitor/SymbolMap/CollectorTest.php b/tests/Visitor/SymbolMap/CollectorTest.php index 50eff8c..5b893ee 100644 --- a/tests/Visitor/SymbolMap/CollectorTest.php +++ b/tests/Visitor/SymbolMap/CollectorTest.php @@ -10,6 +10,7 @@ use Trismegiste\Mondrian\Transform\ReflectionContext; use Trismegiste\Mondrian\Parser\PackageParser; use Symfony\Component\Finder\SplFileInfo; +use Trismegiste\Mondrian\Tests\Fixtures\MockSplFileInfo; /** * CollectorTest is a test for the visitor SymbolMap\Collector @@ -36,12 +37,24 @@ public function setUp() $this->traverser->addVisitor($this->visitor); } - public function testSimpleCase() + protected function scanFile($fixtures) { - $iter = array(new SplFileInfo(__DIR__ . '/../../Fixtures/Project/Concrete.php', '/../../Fixtures/Project/', 'Concrete.php')); + $iter = []; + + foreach ($fixtures as $fch) { + $path = __DIR__ . '/../../Fixtures/Project/' . $fch; + $code = file_get_contents($path); + $iter[] = new MockSplFileInfo($path, $code); + } + $stmts = $this->parser->parse(new \ArrayIterator($iter)); $this->traverser->traverse($stmts); $this->visitor->afterTraverse(array()); + } + + public function testSimpleCase() + { + $this->scanFile(['Concrete.php']); $this->assertAttributeEquals(array( 'Project\\Concrete' => array( From 4eb5a63663905feff35c9c650f980ba720727b33 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 00:07:07 +0200 Subject: [PATCH 08/36] full test for new visitor symbol map --- src/Visitor/State/ClassLevel.php | 3 +- src/Visitor/State/ClassMethodLevel.php | 34 ------- src/Visitor/State/InterfaceLevel.php | 37 ++++++++ src/Visitor/State/TraitLevel.php | 55 +++++++++++ src/Visitor/SymbolMap/Collector.php | 4 +- tests/Visitor/SymbolMap/CollectorTest.php | 111 ++++++++++++++++++++++ 6 files changed, 208 insertions(+), 36 deletions(-) delete mode 100644 src/Visitor/State/ClassMethodLevel.php create mode 100644 src/Visitor/State/InterfaceLevel.php create mode 100644 src/Visitor/State/TraitLevel.php diff --git a/src/Visitor/State/ClassLevel.php b/src/Visitor/State/ClassLevel.php index 2ba096a..4a80d00 100644 --- a/src/Visitor/State/ClassLevel.php +++ b/src/Visitor/State/ClassLevel.php @@ -7,6 +7,7 @@ namespace Trismegiste\Mondrian\Visitor\State; use PhpParser\Node; +use Trismegiste\Mondrian\Transform\ReflectionContext; /** * ClassLevel is ... @@ -40,7 +41,7 @@ protected function importSignatureTrait(Node\Stmt\TraitUse $node) $fqcn = $fileState->getNamespacedName($classNode); // @todo do not forget aliases foreach ($node->traits as $import) { - $name = (string) $stateFile->resolveClassName($import); + $name = (string) $fileState->resolveClassName($import); $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_TRAIT); $this->context->getReflectionContext()->pushUseTrait($fqcn, $name); } diff --git a/src/Visitor/State/ClassMethodLevel.php b/src/Visitor/State/ClassMethodLevel.php deleted file mode 100644 index bd8195f..0000000 --- a/src/Visitor/State/ClassMethodLevel.php +++ /dev/null @@ -1,34 +0,0 @@ -context->getNodeFor('class'); - $currentMethod = $this->context->getNodeFor('class-method'); - - switch ($node->getType()) { - case 'Expr_MethodCall': - // ... - break; - } - } - - public function getName() - { - return 'class-method'; - } - -} \ No newline at end of file diff --git a/src/Visitor/State/InterfaceLevel.php b/src/Visitor/State/InterfaceLevel.php new file mode 100644 index 0000000..5546e34 --- /dev/null +++ b/src/Visitor/State/InterfaceLevel.php @@ -0,0 +1,37 @@ +getType()) { + + case 'Stmt_ClassMethod': + if ($node->type === Node\Stmt\Class_::MODIFIER_PUBLIC) { + $classNode = $this->context->getNodeFor('interface'); + $fileState = $this->context->getState('file'); + $fqcn = $fileState->getNamespacedName($classNode); + $this->context->getReflectionContext()->addMethodToClass($fqcn, $node->name); + } + break; + } + } + + public function getName() + { + return 'interface'; + } + +} \ No newline at end of file diff --git a/src/Visitor/State/TraitLevel.php b/src/Visitor/State/TraitLevel.php new file mode 100644 index 0000000..a23684f --- /dev/null +++ b/src/Visitor/State/TraitLevel.php @@ -0,0 +1,55 @@ +getType()) { + + case 'Stmt_TraitUse' : + $this->importSignatureTrait($node); + break; + + case 'Stmt_ClassMethod': + if ($node->type === Node\Stmt\Class_::MODIFIER_PUBLIC) { + $classNode = $this->context->getNodeFor('trait'); + $fileState = $this->context->getState('file'); + $fqcn = $fileState->getNamespacedName($classNode); + $this->context->getReflectionContext()->addMethodToClass($fqcn, $node->name); + } + break; + } + } + + protected function importSignatureTrait(Node\Stmt\TraitUse $node) + { + $classNode = $this->context->getNodeFor('trait'); + $fileState = $this->context->getState('file'); + $fqcn = $fileState->getNamespacedName($classNode); + // @todo do not forget aliases + foreach ($node->traits as $import) { + $name = (string) $fileState->resolveClassName($import); + $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_TRAIT); + $this->context->getReflectionContext()->pushUseTrait($fqcn, $name); + } + } + + public function getName() + { + return 'trait'; + } + +} \ No newline at end of file diff --git a/src/Visitor/SymbolMap/Collector.php b/src/Visitor/SymbolMap/Collector.php index df536f3..340b155 100644 --- a/src/Visitor/SymbolMap/Collector.php +++ b/src/Visitor/SymbolMap/Collector.php @@ -22,7 +22,9 @@ public function __construct(ReflectionContext $ref, GraphContext $grf, Graph $g) $visitor = [ new \Trismegiste\Mondrian\Visitor\State\PackageLevel(), new \Trismegiste\Mondrian\Visitor\State\FileLevel(), - new \Trismegiste\Mondrian\Visitor\State\ClassLevel() + new \Trismegiste\Mondrian\Visitor\State\ClassLevel(), + new \Trismegiste\Mondrian\Visitor\State\InterfaceLevel(), + new \Trismegiste\Mondrian\Visitor\State\TraitLevel() ]; parent::__construct($visitor, $ref, $grf, $g); diff --git a/tests/Visitor/SymbolMap/CollectorTest.php b/tests/Visitor/SymbolMap/CollectorTest.php index 5b893ee..7f94c90 100644 --- a/tests/Visitor/SymbolMap/CollectorTest.php +++ b/tests/Visitor/SymbolMap/CollectorTest.php @@ -66,4 +66,115 @@ public function testSimpleCase() ), 'inheritanceMap', $this->context); } + public function testExternalInterfaceInheritance() + { + $this->scanFile(['InheritExtra.php']); + + $this->assertAttributeEquals(array( + 'Project\\InheritExtra' => array( + 'type' => 'c', + 'parent' => array(0 => 'IteratorAggregate'), + 'method' => array('getIterator' => 'IteratorAggregate'), + 'use' => [] + ), + 'IteratorAggregate' => array( + 'type' => 'i', + 'parent' => array(), + 'method' => array(), + 'use' => [] + ), + ), 'inheritanceMap', $this->context); + } + + public function testAliasing() + { + $this->scanFile(['Alias1.php', 'Alias2.php']); + + $this->assertAttributeEquals(array( + 'Project\\Aliasing' => array( + 'type' => 'c', + 'parent' => array('Project\Maid', 'Project\Peril'), + 'method' => array('spokes' => 'Project\\Aliasing'), + 'use' => [] + ), + 'Project\Maid' => array( + 'type' => 'c', + 'parent' => array(), + 'method' => array(), + 'use' => [] + ), + 'Project\Peril' => array( + 'type' => 'i', + 'parent' => array(), + 'method' => array(), + 'use' => [] + ) + ), 'inheritanceMap', $this->context); + } + + public function testSimpleTrait() + { + $this->scanFile(['SimpleTrait.php']); + + $this->assertAttributeEquals(array( + 'Project\\SimpleTrait' => array( + 'type' => 't', + 'parent' => [], + 'method' => ['someService' => 'Project\\SimpleTrait'], + 'use' => [] + ) + ), 'inheritanceMap', $this->context); + } + + public function testImportingMethodFromTrait() + { + $this->scanFile([ + 'ServiceWrong.php', + 'ServiceTrait.php' + ]); + + $this->assertAttributeEquals(array( + 'Project\\ServiceWrong' => array( + 'type' => 'c', + 'parent' => [], + 'method' => array('someService' => 'Project\\ServiceWrong'), + 'use' => ['Project\\ServiceTrait'] + ), + 'Project\\ServiceTrait' => array( + 'type' => 't', + 'parent' => [], + 'method' => array('someService' => 'Project\\ServiceTrait'), + 'use' => [] + )), 'inheritanceMap', $this->context); + } + + public function testImportingMethodFromTraitWithInterfaceCollision() + { + $this->scanFile([ + 'ServiceRight.php', + 'ServiceTrait.php', + 'ServiceInterface.php' + ]); + + $this->assertAttributeEquals(array( + 'Project\\ServiceRight' => array( + 'type' => 'c', + 'parent' => ['Project\\ServiceInterface'], + 'method' => array('someService' => 'Project\\ServiceInterface'), + 'use' => ['Project\\ServiceTrait'] + ), + 'Project\\ServiceInterface' => array( + 'type' => 'i', + 'parent' => [], + 'method' => array('someService' => 'Project\\ServiceInterface'), + 'use' => [] + ), + 'Project\\ServiceTrait' => array( + 'type' => 't', + 'parent' => [], + 'method' => array('someService' => 'Project\\ServiceTrait'), + 'use' => [] + )), 'inheritanceMap', $this->context); + } + } From aa4a612c7bb634c623a31011eb8fe401b82ce6df Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 08:46:44 +0200 Subject: [PATCH 09/36] now new collector symbol map ok --- src/Visitor/State/FileLevel.php | 6 +-- src/Visitor/VisitorGateway.php | 49 +++++++++--------- tests/Visitor/SymbolMap/CollectorTest.php | 60 ++++++++++++++++++++++- 3 files changed, 88 insertions(+), 27 deletions(-) diff --git a/src/Visitor/State/FileLevel.php b/src/Visitor/State/FileLevel.php index e9091bd..5e2099b 100644 --- a/src/Visitor/State/FileLevel.php +++ b/src/Visitor/State/FileLevel.php @@ -46,18 +46,18 @@ public function enter(Node $node) break; case 'Stmt_Class': - $this->enterClassNode($node); $this->context->pushState('class', $node); + $this->enterClassNode($node); break; case 'Stmt_Trait': - $this->enterTraitNode($node); $this->context->pushState('trait', $node); + $this->enterTraitNode($node); break; case 'Stmt_Interface': - $this->enterInterfaceNode($node); $this->context->pushState('interface', $node); + $this->enterInterfaceNode($node); break; } } diff --git a/src/Visitor/VisitorGateway.php b/src/Visitor/VisitorGateway.php index 1b7edd1..32dc08b 100644 --- a/src/Visitor/VisitorGateway.php +++ b/src/Visitor/VisitorGateway.php @@ -29,7 +29,7 @@ class VisitorGateway extends NodeVisitorAbstract implements State\VisitorContext /** * @var array $stateStack Stack of previous state */ - protected $stateStack; + protected $stateStack = []; protected $reflectionCtx; protected $graphCtx; protected $graph; @@ -53,8 +53,11 @@ public function __construct(array $visitor, ReflectionContext $ref, GraphContext $this->stateList[$v->getName()] = $v; } - $this->stateStack = new \SplObjectStorage(); - $this->stateStack->attach(new \stdClass(), $visitor[0]); + $this->stateStack[0] = [ + 'node' => null, + 'state' => $visitor[0], + 'key' => $visitor[0]->getName() + ]; } /** @@ -62,13 +65,8 @@ public function __construct(array $visitor, ReflectionContext $ref, GraphContext */ public function enterNode(Node $node) { - foreach ($this->stateStack as $keyNode) { - $v = $this->stateStack->getInfo(); - $ret = $v->enter($node); - if (!is_null($ret)) { - return $ret; - } - } + printf("Entering %s %s %s %d\n", $this->stateStack[0]['key'], $node->getType(), $node->name, count($this->stateStack)); + return $this->stateStack[0]['state']->enter($node); } /** @@ -76,29 +74,34 @@ public function enterNode(Node $node) */ public function leaveNode(Node $node) { - foreach ($this->stateStack as $keyNode) { - $v = $this->stateStack->getInfo(); - $ret = $v->leave($node); - if (!is_null($ret)) { - $this->stateStack->detach($node); - return $ret; - } + printf("Leaving %s %s %s %d\n", $this->stateStack[0]['key'], $node->getType(), $node->name, count($this->stateStack)); + $ret = $this->stateStack[0]['state']->leave($node); + + if ($this->stateStack[0]['nodeType'] === $node->getType()) { + array_shift($this->stateStack); } - $this->stateStack->detach($node); + + return $ret; } public function pushState($stateKey, Node $node) { + printf("Stacking %s %s %s %d\n", $stateKey, $node->getType(), $node->name, count($this->stateStack)); $state = $this->getState($stateKey); - $this->stateStack[$node] = $state; + + array_unshift($this->stateStack, [ + 'node' => $node, + 'state' => $state, + 'key' => $state->getName(), + 'nodeType' => $node->getType() + ]); } public function getNodeFor($stateKey) { - foreach ($this->stateStack as $node) { - $v = $this->stateStack->getInfo(); - if ($stateKey === $v->getName()) { - return $node; + foreach ($this->stateStack as $assoc) { + if ($assoc['key'] === $stateKey) { + return $assoc['node']; } } } diff --git a/tests/Visitor/SymbolMap/CollectorTest.php b/tests/Visitor/SymbolMap/CollectorTest.php index 7f94c90..68b8df8 100644 --- a/tests/Visitor/SymbolMap/CollectorTest.php +++ b/tests/Visitor/SymbolMap/CollectorTest.php @@ -9,7 +9,6 @@ use Trismegiste\Mondrian\Visitor\SymbolMap\Collector; use Trismegiste\Mondrian\Transform\ReflectionContext; use Trismegiste\Mondrian\Parser\PackageParser; -use Symfony\Component\Finder\SplFileInfo; use Trismegiste\Mondrian\Tests\Fixtures\MockSplFileInfo; /** @@ -177,4 +176,63 @@ public function testImportingMethodFromTraitWithInterfaceCollision() )), 'inheritanceMap', $this->context); } + public function testInterfaceExtends() + { + $this->scanFile(['Interface.php']); + + $this->assertAttributeEquals(array( + 'Project\\IOne' => array( + 'type' => 'i', + 'parent' => [], + 'method' => [], + 'use' => [] + ), + 'Project\\ITwo' => array( + 'type' => 'i', + 'parent' => [], + 'method' => [], + 'use' => [] + ), + 'Project\\IThree' => array( + 'type' => 'i', + 'parent' => ['Project\ITwo'], + 'method' => [], + 'use' => [] + ), + 'Project\\Multiple' => array( + 'type' => 'i', + 'parent' => ['Project\IOne', 'Project\ITwo'], + 'method' => [], + 'use' => [] + ), + ), 'inheritanceMap', $this->context); + } + + public function testTraitUsingTrait() + { + $this->scanFile([ + 'ServiceUsingTrait.php', + 'ServiceTrait.php' + ]); + + $this->assertAttributeEquals(array( + 'Project\\ServiceUsingTrait' => array( + 'type' => 't', + 'parent' => [], + // 'method' => array('someService' => 'Project\\ServiceTrait'), + 'method' => [], + 'use' => ['Project\\ServiceTrait'] + ), + 'Project\\ServiceTrait' => array( + 'type' => 't', + 'parent' => [], + 'method' => array('someService' => 'Project\\ServiceTrait'), + 'use' => [] + )), 'inheritanceMap', $this->context); + + $this->markTestIncomplete(); // @todo the commented line above must be incommented + // I will ot create vertex for imported implementation from trait but a class using ServiceUsingTrait + // must copy-paste all methods signature + } + } From f65369a88864fb4c954a48fb3b9b6b15f5a5e6b4 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 09:17:17 +0200 Subject: [PATCH 10/36] replacing new visitor seems ok --- phpunit.xml | 2 +- src/Builder/Compiler/AbstractTraverser.php | 2 +- src/Builder/Compiler/BuilderInterface.php | 3 ++- src/Transform/GraphBuilder.php | 2 +- src/Visitor/State/ClassLevel.php | 2 +- src/Visitor/VisitorGateway.php | 10 +++++++--- 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 56956a2..8c539b4 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,6 +1,6 @@ + colors="false"> diff --git a/src/Builder/Compiler/AbstractTraverser.php b/src/Builder/Compiler/AbstractTraverser.php index a0d1781..3e8690b 100644 --- a/src/Builder/Compiler/AbstractTraverser.php +++ b/src/Builder/Compiler/AbstractTraverser.php @@ -14,7 +14,7 @@ abstract class AbstractTraverser implements BuilderInterface { - public function buildTraverser(Visitor\FqcnHelper $collector) + public function buildTraverser(/*Visitor\FqcnHelper*/ $collector) { $traverser = new \PHPParser_NodeTraverser(); $traverser->addVisitor($collector); diff --git a/src/Builder/Compiler/BuilderInterface.php b/src/Builder/Compiler/BuilderInterface.php index fe50f28..e610115 100644 --- a/src/Builder/Compiler/BuilderInterface.php +++ b/src/Builder/Compiler/BuilderInterface.php @@ -14,5 +14,6 @@ public function buildContext(); public function buildCollectors(); - public function buildTraverser(FqcnHelper $collector); + // @todo add VisitorGateway type_hint + public function buildTraverser(/*FqcnHelper*/ $collector); } \ No newline at end of file diff --git a/src/Transform/GraphBuilder.php b/src/Transform/GraphBuilder.php index 3915dac..8fa929d 100644 --- a/src/Transform/GraphBuilder.php +++ b/src/Transform/GraphBuilder.php @@ -57,7 +57,7 @@ public function buildContext() public function buildCollectors() { return array( - new Visitor\SymbolMap($this->reflection), + new Visitor\SymbolMap\Collector($this->reflection, $this->vertexContext, $this->graphResult), new Visitor\VertexCollector($this->reflection, $this->vertexContext, $this->graphResult), new Visitor\EdgeCollector($this->reflection, $this->vertexContext, $this->graphResult) ); diff --git a/src/Visitor/State/ClassLevel.php b/src/Visitor/State/ClassLevel.php index 4a80d00..b0aa74a 100644 --- a/src/Visitor/State/ClassLevel.php +++ b/src/Visitor/State/ClassLevel.php @@ -24,7 +24,7 @@ public function enter(Node $node) break; case 'Stmt_ClassMethod': - if ($node->type === Node\Stmt\Class_::MODIFIER_PUBLIC) { + if ($node->isPublic()) { $classNode = $this->context->getNodeFor('class'); $fileState = $this->context->getState('file'); $fqcn = $fileState->getNamespacedName($classNode); diff --git a/src/Visitor/VisitorGateway.php b/src/Visitor/VisitorGateway.php index 32dc08b..e05ce69 100644 --- a/src/Visitor/VisitorGateway.php +++ b/src/Visitor/VisitorGateway.php @@ -33,6 +33,7 @@ class VisitorGateway extends NodeVisitorAbstract implements State\VisitorContext protected $reflectionCtx; protected $graphCtx; protected $graph; + private $debug = false; /** * Ctor @@ -65,7 +66,8 @@ public function __construct(array $visitor, ReflectionContext $ref, GraphContext */ public function enterNode(Node $node) { - printf("Entering %s %s %s %d\n", $this->stateStack[0]['key'], $node->getType(), $node->name, count($this->stateStack)); + if ($this->debug) + printf("Entering %s %s %s %d\n", $this->stateStack[0]['key'], $node->getType(), $node->name, count($this->stateStack)); return $this->stateStack[0]['state']->enter($node); } @@ -74,7 +76,8 @@ public function enterNode(Node $node) */ public function leaveNode(Node $node) { - printf("Leaving %s %s %s %d\n", $this->stateStack[0]['key'], $node->getType(), $node->name, count($this->stateStack)); + if ($this->debug) + printf("Leaving %s %s %s %d\n", $this->stateStack[0]['key'], $node->getType(), $node->name, count($this->stateStack)); $ret = $this->stateStack[0]['state']->leave($node); if ($this->stateStack[0]['nodeType'] === $node->getType()) { @@ -86,7 +89,8 @@ public function leaveNode(Node $node) public function pushState($stateKey, Node $node) { - printf("Stacking %s %s %s %d\n", $stateKey, $node->getType(), $node->name, count($this->stateStack)); + if ($this->debug) + printf("Stacking %s %s %s %d\n", $stateKey, $node->getType(), $node->name, count($this->stateStack)); $state = $this->getState($stateKey); array_unshift($this->stateStack, [ From ee005414f55d15d378bf2d57ef02f648826c0478 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 09:35:59 +0200 Subject: [PATCH 11/36] refactor file level --- src/Visitor/State/FileLevel.php | 102 +------------------ src/Visitor/State/FileLevelTemplate.php | 124 ++++++++++++++++++++++++ src/Visitor/State/InterfaceLevel.php | 2 +- src/Visitor/State/TraitLevel.php | 2 +- 4 files changed, 127 insertions(+), 103 deletions(-) create mode 100644 src/Visitor/State/FileLevelTemplate.php diff --git a/src/Visitor/State/FileLevel.php b/src/Visitor/State/FileLevel.php index 5e2099b..0ccb500 100644 --- a/src/Visitor/State/FileLevel.php +++ b/src/Visitor/State/FileLevel.php @@ -12,56 +12,9 @@ /** * FileLevel is ... */ -class FileLevel extends AbstractState +class FileLevel extends FileLevelTemplate { - /** - * @var null|PHPParser_Node_Name Current namespace - */ - protected $namespace; - - /** - * @var array Currently defined namespace and class aliases - */ - protected $aliases; - - public function enter(Node $node) - { - switch ($node->getType()) { - - case 'Stmt_Namespace' : - $this->namespace = $node->name; - $this->aliases = array(); - break; - - case 'Stmt_UseUse' : - if (isset($this->aliases[$node->alias])) { - throw new \PhpParser\Error( - sprintf( - 'Cannot use "%s" as "%s" because the name is already in use', $node->name, $node->alias - ), $node->getLine() - ); - } - $this->aliases[$node->alias] = $node->name; - break; - - case 'Stmt_Class': - $this->context->pushState('class', $node); - $this->enterClassNode($node); - break; - - case 'Stmt_Trait': - $this->context->pushState('trait', $node); - $this->enterTraitNode($node); - break; - - case 'Stmt_Interface': - $this->context->pushState('interface', $node); - $this->enterInterfaceNode($node); - break; - } - } - protected function enterClassNode(Node\Stmt\Class_ $node) { $fqcn = $this->getNamespacedName($node); @@ -98,57 +51,4 @@ protected function enterTraitNode(\PHPParser_Node_Stmt_Trait $node) $this->context->getReflectionContext()->initSymbol($fqcn, ReflectionContext::SYMBOL_TRAIT); } - public function getName() - { - return 'file'; - } - - /** - * resolve the Name with current namespace and alias - * - * @param \PHPParser_Node_Name $src - * @return \PHPParser_Node_Name|\PHPParser_Node_Name_FullyQualified - */ - public function resolveClassName(Node\Name $src) - { - $name = clone $src; - // don't resolve special class names - if (in_array((string) $name, array('self', 'parent', 'static'))) { - return $name; - } - - // fully qualified names are already resolved - if ($name->isFullyQualified()) { - return $name; - } - - // resolve aliases (for non-relative names) - if (!$name->isRelative() && isset($this->aliases[$name->getFirst()])) { - $name->setFirst($this->aliases[$name->getFirst()]); - // if no alias exists prepend current namespace - } elseif (null !== $this->namespace) { - $name->prepend($this->namespace); - } - - return new Node\Name\FullyQualified($name->parts, $name->getAttributes()); - } - - /** - * Helper : get the FQCN of the given $node->name - * - * @param PHPParser_Node $node - * @return string - */ - public function getNamespacedName(Node $node) - { - if (null !== $this->namespace) { - $namespacedName = clone $this->namespace; - $namespacedName->append($node->name); - } else { - $namespacedName = $node->name; - } - - return (string) $namespacedName; - } - } \ No newline at end of file diff --git a/src/Visitor/State/FileLevelTemplate.php b/src/Visitor/State/FileLevelTemplate.php new file mode 100644 index 0000000..499cbee --- /dev/null +++ b/src/Visitor/State/FileLevelTemplate.php @@ -0,0 +1,124 @@ +getType()) { + + case 'Stmt_Namespace' : + $this->namespace = $node->name; + $this->aliases = array(); + break; + + case 'Stmt_UseUse' : + if (isset($this->aliases[$node->alias])) { + throw new \PhpParser\Error( + sprintf( + 'Cannot use "%s" as "%s" because the name is already in use', $node->name, $node->alias + ), $node->getLine() + ); + } + $this->aliases[$node->alias] = $node->name; + break; + + case 'Stmt_Class': + $this->context->pushState('class', $node); + $this->enterClassNode($node); + break; + + case 'Stmt_Trait': + $this->context->pushState('trait', $node); + $this->enterTraitNode($node); + break; + + case 'Stmt_Interface': + $this->context->pushState('interface', $node); + $this->enterInterfaceNode($node); + break; + } + } + + abstract protected function enterClassNode(Node\Stmt\Class_ $node); + + abstract protected function enterInterfaceNode(\PHPParser_Node_Stmt_Interface $node); + + abstract protected function enterTraitNode(\PHPParser_Node_Stmt_Trait $node); + + public function getName() + { + return 'file'; + } + + /** + * resolve the Name with current namespace and alias + * + * @param \PHPParser_Node_Name $src + * @return \PHPParser_Node_Name|\PHPParser_Node_Name_FullyQualified + */ + public function resolveClassName(Node\Name $src) + { + $name = clone $src; + // don't resolve special class names + if (in_array((string) $name, array('self', 'parent', 'static'))) { + return $name; + } + + // fully qualified names are already resolved + if ($name->isFullyQualified()) { + return $name; + } + + // resolve aliases (for non-relative names) + if (!$name->isRelative() && isset($this->aliases[$name->getFirst()])) { + $name->setFirst($this->aliases[$name->getFirst()]); + // if no alias exists prepend current namespace + } elseif (null !== $this->namespace) { + $name->prepend($this->namespace); + } + + return new Node\Name\FullyQualified($name->parts, $name->getAttributes()); + } + + /** + * Helper : get the FQCN of the given $node->name + * + * @param PHPParser_Node $node + * @return string + */ + public function getNamespacedName(Node $node) + { + if (null !== $this->namespace) { + $namespacedName = clone $this->namespace; + $namespacedName->append($node->name); + } else { + $namespacedName = $node->name; + } + + return (string) $namespacedName; + } + +} \ No newline at end of file diff --git a/src/Visitor/State/InterfaceLevel.php b/src/Visitor/State/InterfaceLevel.php index 5546e34..855ecad 100644 --- a/src/Visitor/State/InterfaceLevel.php +++ b/src/Visitor/State/InterfaceLevel.php @@ -19,7 +19,7 @@ public function enter(Node $node) switch ($node->getType()) { case 'Stmt_ClassMethod': - if ($node->type === Node\Stmt\Class_::MODIFIER_PUBLIC) { + if ($node->isPublic()) { $classNode = $this->context->getNodeFor('interface'); $fileState = $this->context->getState('file'); $fqcn = $fileState->getNamespacedName($classNode); diff --git a/src/Visitor/State/TraitLevel.php b/src/Visitor/State/TraitLevel.php index a23684f..9b63d8c 100644 --- a/src/Visitor/State/TraitLevel.php +++ b/src/Visitor/State/TraitLevel.php @@ -24,7 +24,7 @@ public function enter(Node $node) break; case 'Stmt_ClassMethod': - if ($node->type === Node\Stmt\Class_::MODIFIER_PUBLIC) { + if ($node->isPublic()) { $classNode = $this->context->getNodeFor('trait'); $fileState = $this->context->getState('file'); $fqcn = $fileState->getNamespacedName($classNode); From 23b94cf78b63346614e578819ccd703698bf9992 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 09:42:11 +0200 Subject: [PATCH 12/36] moving concrete file level to symbol map --- src/Visitor/SymbolMap/Collector.php | 2 +- src/Visitor/{State => SymbolMap}/FileLevel.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) rename src/Visitor/{State => SymbolMap}/FileLevel.php (94%) diff --git a/src/Visitor/SymbolMap/Collector.php b/src/Visitor/SymbolMap/Collector.php index 340b155..1ec187a 100644 --- a/src/Visitor/SymbolMap/Collector.php +++ b/src/Visitor/SymbolMap/Collector.php @@ -21,7 +21,7 @@ public function __construct(ReflectionContext $ref, GraphContext $grf, Graph $g) { $visitor = [ new \Trismegiste\Mondrian\Visitor\State\PackageLevel(), - new \Trismegiste\Mondrian\Visitor\State\FileLevel(), + new FileLevel(), new \Trismegiste\Mondrian\Visitor\State\ClassLevel(), new \Trismegiste\Mondrian\Visitor\State\InterfaceLevel(), new \Trismegiste\Mondrian\Visitor\State\TraitLevel() diff --git a/src/Visitor/State/FileLevel.php b/src/Visitor/SymbolMap/FileLevel.php similarity index 94% rename from src/Visitor/State/FileLevel.php rename to src/Visitor/SymbolMap/FileLevel.php index 0ccb500..0f534c2 100644 --- a/src/Visitor/State/FileLevel.php +++ b/src/Visitor/SymbolMap/FileLevel.php @@ -4,10 +4,11 @@ * Mondrian */ -namespace Trismegiste\Mondrian\Visitor\State; +namespace Trismegiste\Mondrian\Visitor\SymbolMap; use PhpParser\Node; use Trismegiste\Mondrian\Transform\ReflectionContext; +use Trismegiste\Mondrian\Visitor\State\FileLevelTemplate; /** * FileLevel is ... From 97efe0c42d520973fccaa45bbc310057fa807cec Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 10:01:16 +0200 Subject: [PATCH 13/36] refactor object level --- src/Visitor/State/ClassLevel.php | 20 ++--------------- src/Visitor/State/FileLevelTemplate.php | 2 ++ src/Visitor/State/InterfaceLevel.php | 6 ++--- src/Visitor/State/ObjectLevel.php | 24 ++++++++++++++++++++ src/Visitor/State/TraitLevel.php | 20 ++--------------- src/Visitor/State/TraitUserLevel.php | 30 +++++++++++++++++++++++++ 6 files changed, 62 insertions(+), 40 deletions(-) create mode 100644 src/Visitor/State/ObjectLevel.php create mode 100644 src/Visitor/State/TraitUserLevel.php diff --git a/src/Visitor/State/ClassLevel.php b/src/Visitor/State/ClassLevel.php index b0aa74a..9fff929 100644 --- a/src/Visitor/State/ClassLevel.php +++ b/src/Visitor/State/ClassLevel.php @@ -7,12 +7,11 @@ namespace Trismegiste\Mondrian\Visitor\State; use PhpParser\Node; -use Trismegiste\Mondrian\Transform\ReflectionContext; /** * ClassLevel is ... */ -class ClassLevel extends AbstractState +class ClassLevel extends TraitUserLevel { public function enter(Node $node) @@ -25,28 +24,13 @@ public function enter(Node $node) case 'Stmt_ClassMethod': if ($node->isPublic()) { - $classNode = $this->context->getNodeFor('class'); - $fileState = $this->context->getState('file'); - $fqcn = $fileState->getNamespacedName($classNode); + $fqcn = $this->getCurrentFqcn(); $this->context->getReflectionContext()->addMethodToClass($fqcn, $node->name); } break; } } - protected function importSignatureTrait(Node\Stmt\TraitUse $node) - { - $classNode = $this->context->getNodeFor('class'); - $fileState = $this->context->getState('file'); - $fqcn = $fileState->getNamespacedName($classNode); - // @todo do not forget aliases - foreach ($node->traits as $import) { - $name = (string) $fileState->resolveClassName($import); - $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_TRAIT); - $this->context->getReflectionContext()->pushUseTrait($fqcn, $name); - } - } - public function getName() { return 'class'; diff --git a/src/Visitor/State/FileLevelTemplate.php b/src/Visitor/State/FileLevelTemplate.php index 499cbee..edc1644 100644 --- a/src/Visitor/State/FileLevelTemplate.php +++ b/src/Visitor/State/FileLevelTemplate.php @@ -32,6 +32,8 @@ final public function enter(Node $node) case 'Stmt_Namespace' : $this->namespace = $node->name; $this->aliases = array(); + // @todo : with multiple namespaces in one file : does this bug ? + // leave() shouldn't reset these values ? break; case 'Stmt_UseUse' : diff --git a/src/Visitor/State/InterfaceLevel.php b/src/Visitor/State/InterfaceLevel.php index 855ecad..628bb3b 100644 --- a/src/Visitor/State/InterfaceLevel.php +++ b/src/Visitor/State/InterfaceLevel.php @@ -11,7 +11,7 @@ /** * InterfaceLevel is ... */ -class InterfaceLevel extends AbstractState +class InterfaceLevel extends ObjectLevel { public function enter(Node $node) @@ -20,9 +20,7 @@ public function enter(Node $node) case 'Stmt_ClassMethod': if ($node->isPublic()) { - $classNode = $this->context->getNodeFor('interface'); - $fileState = $this->context->getState('file'); - $fqcn = $fileState->getNamespacedName($classNode); + $fqcn = $this->getCurrentFqcn(); $this->context->getReflectionContext()->addMethodToClass($fqcn, $node->name); } break; diff --git a/src/Visitor/State/ObjectLevel.php b/src/Visitor/State/ObjectLevel.php new file mode 100644 index 0000000..d0e0cdc --- /dev/null +++ b/src/Visitor/State/ObjectLevel.php @@ -0,0 +1,24 @@ +context->getNodeFor($this->getName()); + $fileState = $this->context->getState('file'); + $fqcn = $fileState->getNamespacedName($objectNode); + + return $fqcn; + } + +} \ No newline at end of file diff --git a/src/Visitor/State/TraitLevel.php b/src/Visitor/State/TraitLevel.php index 9b63d8c..dd37bf7 100644 --- a/src/Visitor/State/TraitLevel.php +++ b/src/Visitor/State/TraitLevel.php @@ -7,12 +7,11 @@ namespace Trismegiste\Mondrian\Visitor\State; use PhpParser\Node; -use Trismegiste\Mondrian\Transform\ReflectionContext; /** * TraitLevel is ... */ -class TraitLevel extends AbstractState +class TraitLevel extends TraitUserLevel { public function enter(Node $node) @@ -25,28 +24,13 @@ public function enter(Node $node) case 'Stmt_ClassMethod': if ($node->isPublic()) { - $classNode = $this->context->getNodeFor('trait'); - $fileState = $this->context->getState('file'); - $fqcn = $fileState->getNamespacedName($classNode); + $fqcn = $this->getCurrentFqcn(); $this->context->getReflectionContext()->addMethodToClass($fqcn, $node->name); } break; } } - protected function importSignatureTrait(Node\Stmt\TraitUse $node) - { - $classNode = $this->context->getNodeFor('trait'); - $fileState = $this->context->getState('file'); - $fqcn = $fileState->getNamespacedName($classNode); - // @todo do not forget aliases - foreach ($node->traits as $import) { - $name = (string) $fileState->resolveClassName($import); - $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_TRAIT); - $this->context->getReflectionContext()->pushUseTrait($fqcn, $name); - } - } - public function getName() { return 'trait'; diff --git a/src/Visitor/State/TraitUserLevel.php b/src/Visitor/State/TraitUserLevel.php new file mode 100644 index 0000000..75b6d82 --- /dev/null +++ b/src/Visitor/State/TraitUserLevel.php @@ -0,0 +1,30 @@ +context->getState('file'); + $fqcn = $this->getCurrentFqcn(); + // @todo do not forget aliases + foreach ($node->traits as $import) { + $name = (string) $fileState->resolveClassName($import); + $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_TRAIT); + $this->context->getReflectionContext()->pushUseTrait($fqcn, $name); + } + } + +} \ No newline at end of file From 323fd981e7af95b5f5844ff83d9c9f7fe452903e Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 10:14:53 +0200 Subject: [PATCH 14/36] shortcut shortcut everywhere --- src/Transform/ReflectionContext.php | 17 +++++++++++++++++ src/Visitor/State/AbstractState.php | 5 +++++ src/Visitor/State/ClassLevel.php | 2 +- src/Visitor/State/InterfaceLevel.php | 2 +- src/Visitor/State/TraitLevel.php | 2 +- src/Visitor/State/TraitUserLevel.php | 5 ++--- src/Visitor/SymbolMap/FileLevel.php | 18 +++++++++--------- src/Visitor/VisitorGateway.php | 1 + 8 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/Transform/ReflectionContext.php b/src/Transform/ReflectionContext.php index 396614c..3d297a0 100644 --- a/src/Transform/ReflectionContext.php +++ b/src/Transform/ReflectionContext.php @@ -13,6 +13,8 @@ * * Responsible for maintaining a list of methods, traits, classes and interfaces used * for building inheritance links in a digraph + * + * @todo this class lacks an interface */ class ReflectionContext { @@ -164,6 +166,21 @@ public function initSymbol($name, $symbolType) } } + public function initClass($name) + { + $this->initSymbol($name, self::SYMBOL_CLASS); + } + + public function initInterface($name) + { + $this->initSymbol($name, self::SYMBOL_INTERFACE); + } + + public function initTrait($name) + { + $this->initSymbol($name, self::SYMBOL_TRAIT); + } + /** * Stacks a parent type for a type * diff --git a/src/Visitor/State/AbstractState.php b/src/Visitor/State/AbstractState.php index 54bb7ed..3ac8f40 100644 --- a/src/Visitor/State/AbstractState.php +++ b/src/Visitor/State/AbstractState.php @@ -27,4 +27,9 @@ public function leave(Node $node) } + protected function getReflectionContext() + { + return $this->context->getReflectionContext(); + } + } \ No newline at end of file diff --git a/src/Visitor/State/ClassLevel.php b/src/Visitor/State/ClassLevel.php index 9fff929..e009394 100644 --- a/src/Visitor/State/ClassLevel.php +++ b/src/Visitor/State/ClassLevel.php @@ -25,7 +25,7 @@ public function enter(Node $node) case 'Stmt_ClassMethod': if ($node->isPublic()) { $fqcn = $this->getCurrentFqcn(); - $this->context->getReflectionContext()->addMethodToClass($fqcn, $node->name); + $this->getReflectionContext()->addMethodToClass($fqcn, $node->name); } break; } diff --git a/src/Visitor/State/InterfaceLevel.php b/src/Visitor/State/InterfaceLevel.php index 628bb3b..d41efcb 100644 --- a/src/Visitor/State/InterfaceLevel.php +++ b/src/Visitor/State/InterfaceLevel.php @@ -21,7 +21,7 @@ public function enter(Node $node) case 'Stmt_ClassMethod': if ($node->isPublic()) { $fqcn = $this->getCurrentFqcn(); - $this->context->getReflectionContext()->addMethodToClass($fqcn, $node->name); + $this->getReflectionContext()->addMethodToClass($fqcn, $node->name); } break; } diff --git a/src/Visitor/State/TraitLevel.php b/src/Visitor/State/TraitLevel.php index dd37bf7..858c840 100644 --- a/src/Visitor/State/TraitLevel.php +++ b/src/Visitor/State/TraitLevel.php @@ -25,7 +25,7 @@ public function enter(Node $node) case 'Stmt_ClassMethod': if ($node->isPublic()) { $fqcn = $this->getCurrentFqcn(); - $this->context->getReflectionContext()->addMethodToClass($fqcn, $node->name); + $this->getReflectionContext()->addMethodToClass($fqcn, $node->name); } break; } diff --git a/src/Visitor/State/TraitUserLevel.php b/src/Visitor/State/TraitUserLevel.php index 75b6d82..1bd4894 100644 --- a/src/Visitor/State/TraitUserLevel.php +++ b/src/Visitor/State/TraitUserLevel.php @@ -7,7 +7,6 @@ namespace Trismegiste\Mondrian\Visitor\State; use PhpParser\Node; -use Trismegiste\Mondrian\Transform\ReflectionContext; /** * TraitUserLevel is a helper for traits users (class & trait) level state @@ -22,8 +21,8 @@ protected function importSignatureTrait(Node\Stmt\TraitUse $node) // @todo do not forget aliases foreach ($node->traits as $import) { $name = (string) $fileState->resolveClassName($import); - $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_TRAIT); - $this->context->getReflectionContext()->pushUseTrait($fqcn, $name); + $this->getReflectionContext()->initTrait($name); + $this->getReflectionContext()->pushUseTrait($fqcn, $name); } } diff --git a/src/Visitor/SymbolMap/FileLevel.php b/src/Visitor/SymbolMap/FileLevel.php index 0f534c2..9ace402 100644 --- a/src/Visitor/SymbolMap/FileLevel.php +++ b/src/Visitor/SymbolMap/FileLevel.php @@ -19,37 +19,37 @@ class FileLevel extends FileLevelTemplate protected function enterClassNode(Node\Stmt\Class_ $node) { $fqcn = $this->getNamespacedName($node); - $this->context->getReflectionContext()->initSymbol($fqcn, ReflectionContext::SYMBOL_CLASS); + $this->getReflectionContext()->initClass($fqcn); // extends if (!is_null($node->extends)) { $name = (string) $this->resolveClassName($node->extends); - $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_CLASS); - $this->context->getReflectionContext()->pushParentClass($fqcn, $name); + $this->getReflectionContext()->initClass($name); + $this->getReflectionContext()->pushParentClass($fqcn, $name); } // implements foreach ($node->implements as $parent) { $name = (string) $this->resolveClassName($parent); - $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_INTERFACE); - $this->context->getReflectionContext()->pushParentClass($fqcn, $name); + $this->getReflectionContext()->initInterface($name); + $this->getReflectionContext()->pushParentClass($fqcn, $name); } } protected function enterInterfaceNode(\PHPParser_Node_Stmt_Interface $node) { $fqcn = $this->getNamespacedName($node); - $this->context->getReflectionContext()->initSymbol($fqcn, ReflectionContext::SYMBOL_INTERFACE); + $this->getReflectionContext()->initInterface($fqcn); // extends foreach ($node->extends as $interf) { $name = (string) $this->resolveClassName($interf); - $this->context->getReflectionContext()->initSymbol($name, ReflectionContext::SYMBOL_INTERFACE); - $this->context->getReflectionContext()->pushParentClass($fqcn, $name); + $this->getReflectionContext()->initInterface($name); + $this->getReflectionContext()->pushParentClass($fqcn, $name); } } protected function enterTraitNode(\PHPParser_Node_Stmt_Trait $node) { $fqcn = $this->getNamespacedName($node); - $this->context->getReflectionContext()->initSymbol($fqcn, ReflectionContext::SYMBOL_TRAIT); + $this->getReflectionContext()->initTrait($fqcn); } } \ No newline at end of file diff --git a/src/Visitor/VisitorGateway.php b/src/Visitor/VisitorGateway.php index e05ce69..0219c64 100644 --- a/src/Visitor/VisitorGateway.php +++ b/src/Visitor/VisitorGateway.php @@ -108,6 +108,7 @@ public function getNodeFor($stateKey) return $assoc['node']; } } + throw new \InvalidArgumentException("$stateKey is not a current stacked state"); } public function getState($stateKey) From 1cdd88c10e1963c0f7adbdada3a47b4f83f3ac1d Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 10:23:00 +0200 Subject: [PATCH 15/36] moving class with symbol map --- .../State/{ObjectLevel.php => AbstractObjectLevel.php} | 4 ++-- src/Visitor/{State => SymbolMap}/ClassLevel.php | 2 +- src/Visitor/SymbolMap/Collector.php | 6 +++--- src/Visitor/{State => SymbolMap}/InterfaceLevel.php | 5 +++-- src/Visitor/{State => SymbolMap}/TraitLevel.php | 2 +- src/Visitor/{State => SymbolMap}/TraitUserLevel.php | 5 +++-- 6 files changed, 13 insertions(+), 11 deletions(-) rename src/Visitor/State/{ObjectLevel.php => AbstractObjectLevel.php} (72%) rename src/Visitor/{State => SymbolMap}/ClassLevel.php (92%) rename src/Visitor/{State => SymbolMap}/InterfaceLevel.php (76%) rename src/Visitor/{State => SymbolMap}/TraitLevel.php (92%) rename src/Visitor/{State => SymbolMap}/TraitUserLevel.php (78%) diff --git a/src/Visitor/State/ObjectLevel.php b/src/Visitor/State/AbstractObjectLevel.php similarity index 72% rename from src/Visitor/State/ObjectLevel.php rename to src/Visitor/State/AbstractObjectLevel.php index d0e0cdc..eb1151c 100644 --- a/src/Visitor/State/ObjectLevel.php +++ b/src/Visitor/State/AbstractObjectLevel.php @@ -7,9 +7,9 @@ namespace Trismegiste\Mondrian\Visitor\State; /** - * ObjectLevel is a helper for class/trait/interface level state + * AbstractObjectLevel is a helper for class/trait/interface level state */ -abstract class ObjectLevel extends AbstractState +abstract class AbstractObjectLevel extends AbstractState { protected function getCurrentFqcn() diff --git a/src/Visitor/State/ClassLevel.php b/src/Visitor/SymbolMap/ClassLevel.php similarity index 92% rename from src/Visitor/State/ClassLevel.php rename to src/Visitor/SymbolMap/ClassLevel.php index e009394..b6a62b8 100644 --- a/src/Visitor/State/ClassLevel.php +++ b/src/Visitor/SymbolMap/ClassLevel.php @@ -4,7 +4,7 @@ * Mondrian */ -namespace Trismegiste\Mondrian\Visitor\State; +namespace Trismegiste\Mondrian\Visitor\SymbolMap; use PhpParser\Node; diff --git a/src/Visitor/SymbolMap/Collector.php b/src/Visitor/SymbolMap/Collector.php index 1ec187a..6b8178a 100644 --- a/src/Visitor/SymbolMap/Collector.php +++ b/src/Visitor/SymbolMap/Collector.php @@ -22,9 +22,9 @@ public function __construct(ReflectionContext $ref, GraphContext $grf, Graph $g) $visitor = [ new \Trismegiste\Mondrian\Visitor\State\PackageLevel(), new FileLevel(), - new \Trismegiste\Mondrian\Visitor\State\ClassLevel(), - new \Trismegiste\Mondrian\Visitor\State\InterfaceLevel(), - new \Trismegiste\Mondrian\Visitor\State\TraitLevel() + new ClassLevel(), + new InterfaceLevel(), + new TraitLevel() ]; parent::__construct($visitor, $ref, $grf, $g); diff --git a/src/Visitor/State/InterfaceLevel.php b/src/Visitor/SymbolMap/InterfaceLevel.php similarity index 76% rename from src/Visitor/State/InterfaceLevel.php rename to src/Visitor/SymbolMap/InterfaceLevel.php index d41efcb..7c26862 100644 --- a/src/Visitor/State/InterfaceLevel.php +++ b/src/Visitor/SymbolMap/InterfaceLevel.php @@ -4,14 +4,15 @@ * Mondrian */ -namespace Trismegiste\Mondrian\Visitor\State; +namespace Trismegiste\Mondrian\Visitor\SymbolMap; use PhpParser\Node; +use Trismegiste\Mondrian\Visitor\State\AbstractObjectLevel; /** * InterfaceLevel is ... */ -class InterfaceLevel extends ObjectLevel +class InterfaceLevel extends AbstractObjectLevel { public function enter(Node $node) diff --git a/src/Visitor/State/TraitLevel.php b/src/Visitor/SymbolMap/TraitLevel.php similarity index 92% rename from src/Visitor/State/TraitLevel.php rename to src/Visitor/SymbolMap/TraitLevel.php index 858c840..1085f19 100644 --- a/src/Visitor/State/TraitLevel.php +++ b/src/Visitor/SymbolMap/TraitLevel.php @@ -4,7 +4,7 @@ * Mondrian */ -namespace Trismegiste\Mondrian\Visitor\State; +namespace Trismegiste\Mondrian\Visitor\SymbolMap; use PhpParser\Node; diff --git a/src/Visitor/State/TraitUserLevel.php b/src/Visitor/SymbolMap/TraitUserLevel.php similarity index 78% rename from src/Visitor/State/TraitUserLevel.php rename to src/Visitor/SymbolMap/TraitUserLevel.php index 1bd4894..acf46be 100644 --- a/src/Visitor/State/TraitUserLevel.php +++ b/src/Visitor/SymbolMap/TraitUserLevel.php @@ -4,14 +4,15 @@ * Mondrian */ -namespace Trismegiste\Mondrian\Visitor\State; +namespace Trismegiste\Mondrian\Visitor\SymbolMap; use PhpParser\Node; +use Trismegiste\Mondrian\Visitor\State\AbstractObjectLevel; /** * TraitUserLevel is a helper for traits users (class & trait) level state */ -abstract class TraitUserLevel extends ObjectLevel +abstract class TraitUserLevel extends AbstractObjectLevel { protected function importSignatureTrait(Node\Stmt\TraitUse $node) From 84bbd564f3abc43af4a938335664d7b5cf0fc87e Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 11:17:43 +0200 Subject: [PATCH 16/36] starting vertex collector --- src/Visitor/State/AbstractState.php | 19 ++++++++++ src/Visitor/State/FileLevelTemplate.php | 5 ++- src/Visitor/SymbolMap/FileLevel.php | 5 ++- src/Visitor/Vertex/ClassLevel.php | 40 +++++++++++++++++++++ src/Visitor/Vertex/Collector.php | 33 +++++++++++++++++ src/Visitor/Vertex/FileLevel.php | 45 ++++++++++++++++++++++++ src/Visitor/Vertex/ObjectLevelHelper.php | 17 +++++++++ src/Visitor/VisitorGateway.php | 3 +- 8 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 src/Visitor/Vertex/ClassLevel.php create mode 100644 src/Visitor/Vertex/Collector.php create mode 100644 src/Visitor/Vertex/FileLevel.php create mode 100644 src/Visitor/Vertex/ObjectLevelHelper.php diff --git a/src/Visitor/State/AbstractState.php b/src/Visitor/State/AbstractState.php index 3ac8f40..3a2dfaf 100644 --- a/src/Visitor/State/AbstractState.php +++ b/src/Visitor/State/AbstractState.php @@ -27,9 +27,28 @@ public function leave(Node $node) } + /** + * @return \Trismegiste\Mondrian\Transform\ReflectionContext + */ protected function getReflectionContext() { return $this->context->getReflectionContext(); } + /** + * @return \Trismegiste\Mondrian\Transform\GraphContext + */ + protected function getGraphContext() + { + return $this->context->getGraphContext(); + } + + /** + * @return \Trismegiste\Mondrian\Graph\Graph + */ + protected function getGraph() + { + return $this->context->getGraph(); + } + } \ No newline at end of file diff --git a/src/Visitor/State/FileLevelTemplate.php b/src/Visitor/State/FileLevelTemplate.php index edc1644..23a9f1f 100644 --- a/src/Visitor/State/FileLevelTemplate.php +++ b/src/Visitor/State/FileLevelTemplate.php @@ -7,7 +7,6 @@ namespace Trismegiste\Mondrian\Visitor\State; use PhpParser\Node; -use Trismegiste\Mondrian\Transform\ReflectionContext; /** * FileLevelTemplate is Template Method DP for a FileLevel state @@ -66,9 +65,9 @@ final public function enter(Node $node) abstract protected function enterClassNode(Node\Stmt\Class_ $node); - abstract protected function enterInterfaceNode(\PHPParser_Node_Stmt_Interface $node); + abstract protected function enterInterfaceNode(Node\Stmt\Interface_ $node); - abstract protected function enterTraitNode(\PHPParser_Node_Stmt_Trait $node); + abstract protected function enterTraitNode(Node\Stmt\Trait_ $node); public function getName() { diff --git a/src/Visitor/SymbolMap/FileLevel.php b/src/Visitor/SymbolMap/FileLevel.php index 9ace402..bc40a10 100644 --- a/src/Visitor/SymbolMap/FileLevel.php +++ b/src/Visitor/SymbolMap/FileLevel.php @@ -7,7 +7,6 @@ namespace Trismegiste\Mondrian\Visitor\SymbolMap; use PhpParser\Node; -use Trismegiste\Mondrian\Transform\ReflectionContext; use Trismegiste\Mondrian\Visitor\State\FileLevelTemplate; /** @@ -34,7 +33,7 @@ protected function enterClassNode(Node\Stmt\Class_ $node) } } - protected function enterInterfaceNode(\PHPParser_Node_Stmt_Interface $node) + protected function enterInterfaceNode(Node\Stmt\Interface_ $node) { $fqcn = $this->getNamespacedName($node); $this->getReflectionContext()->initInterface($fqcn); @@ -46,7 +45,7 @@ protected function enterInterfaceNode(\PHPParser_Node_Stmt_Interface $node) } } - protected function enterTraitNode(\PHPParser_Node_Stmt_Trait $node) + protected function enterTraitNode(Node\Stmt\Trait_ $node) { $fqcn = $this->getNamespacedName($node); $this->getReflectionContext()->initTrait($fqcn); diff --git a/src/Visitor/Vertex/ClassLevel.php b/src/Visitor/Vertex/ClassLevel.php new file mode 100644 index 0000000..954f0b0 --- /dev/null +++ b/src/Visitor/Vertex/ClassLevel.php @@ -0,0 +1,40 @@ +getType() == 'Stmt_ClassMethod') && + ($node->isPublic())) { + $fqcn = $this->getCurrentFqcn(); + // if this class is declaring the method, we create a vertex for this signature + $declaringClass = $this->getReflectionContext()->getDeclaringClass($fqcn, $node->name); + if ($this->currentClass == $declaringClass) { + $this->pushMethod($node); + } + + // if not abstract we add the vertex for the implementation + if (!$node->isAbstract()) { + $this->pushImplementation($node); + } + } + } + + public function getName() + { + return 'class'; + } + +} \ No newline at end of file diff --git a/src/Visitor/Vertex/Collector.php b/src/Visitor/Vertex/Collector.php new file mode 100644 index 0000000..1655af9 --- /dev/null +++ b/src/Visitor/Vertex/Collector.php @@ -0,0 +1,33 @@ +factoryPrototype($node, 'class', 'Trismegiste\Mondrian\Transform\Vertex\ClassVertex'); + } + + protected function enterInterfaceNode(Stmt\Interface_ $node) + { + $this->factoryPrototype($node, 'interface', 'Trismegiste\Mondrian\Transform\Vertex\InterfaceVertex'); + } + + protected function enterTraitNode(Stmt\Trait_ $node) + { + $this->factoryPrototype($node, 'trait', 'Trismegiste\Mondrian\Transform\Vertex\TraitVertex'); + } + + private function factoryPrototype(Stmt $node, $type, $vertexClass) + { + $index = $this->getNamespacedName($node); + + if (!$this->getGraphContext()->existsVertex($type, $index)) { + $factory = new \ReflectionClass($vertexClass); + $v = $factory->newInstance($index); + $this->getGraph()->addVertex($v); + $this->getGraphContext()->indicesVertex($type, $index, $v); + } + } + +} \ No newline at end of file diff --git a/src/Visitor/Vertex/ObjectLevelHelper.php b/src/Visitor/Vertex/ObjectLevelHelper.php new file mode 100644 index 0000000..5553969 --- /dev/null +++ b/src/Visitor/Vertex/ObjectLevelHelper.php @@ -0,0 +1,17 @@ + Date: Mon, 15 Sep 2014 12:04:09 +0200 Subject: [PATCH 17/36] visitor for class vertex --- src/Visitor/Vertex/ClassLevel.php | 11 +- src/Visitor/Vertex/Collector.php | 4 +- src/Visitor/Vertex/ObjectLevelHelper.php | 61 ++++++- src/Visitor/VertexCollector.php | 4 +- tests/Visitor/SymbolMap/CollectorTest.php | 2 +- tests/Visitor/Vertex/CollectorTest.php | 201 ++++++++++++++++++++++ 6 files changed, 273 insertions(+), 10 deletions(-) create mode 100644 tests/Visitor/Vertex/CollectorTest.php diff --git a/src/Visitor/Vertex/ClassLevel.php b/src/Visitor/Vertex/ClassLevel.php index 954f0b0..80c9bf8 100644 --- a/src/Visitor/Vertex/ClassLevel.php +++ b/src/Visitor/Vertex/ClassLevel.php @@ -17,17 +17,18 @@ class ClassLevel extends ObjectLevelHelper public function enter(Node $node) { if (($node->getType() == 'Stmt_ClassMethod') && - ($node->isPublic())) { + $node->isPublic()) { + $methodName = $node->name; $fqcn = $this->getCurrentFqcn(); // if this class is declaring the method, we create a vertex for this signature - $declaringClass = $this->getReflectionContext()->getDeclaringClass($fqcn, $node->name); - if ($this->currentClass == $declaringClass) { - $this->pushMethod($node); + $declaringClass = $this->getReflectionContext()->getDeclaringClass($fqcn, $methodName); + if ($fqcn == $declaringClass) { + $this->pushMethod($node, "$fqcn::$methodName"); } // if not abstract we add the vertex for the implementation if (!$node->isAbstract()) { - $this->pushImplementation($node); + $this->pushImplementation($node, "$fqcn::$methodName"); } } } diff --git a/src/Visitor/Vertex/Collector.php b/src/Visitor/Vertex/Collector.php index 1655af9..55140fd 100644 --- a/src/Visitor/Vertex/Collector.php +++ b/src/Visitor/Vertex/Collector.php @@ -23,8 +23,8 @@ public function __construct(ReflectionContext $ref, GraphContext $grf, Graph $g) new \Trismegiste\Mondrian\Visitor\State\PackageLevel(), new FileLevel(), new ClassLevel(), - new InterfaceLevel(), - new TraitLevel() +// new InterfaceLevel(), +// new TraitLevel() ]; parent::__construct($visitor, $ref, $grf, $g); diff --git a/src/Visitor/Vertex/ObjectLevelHelper.php b/src/Visitor/Vertex/ObjectLevelHelper.php index 5553969..e6c9211 100644 --- a/src/Visitor/Vertex/ObjectLevelHelper.php +++ b/src/Visitor/Vertex/ObjectLevelHelper.php @@ -7,11 +7,70 @@ namespace Trismegiste\Mondrian\Visitor\Vertex; use Trismegiste\Mondrian\Visitor\State\AbstractObjectLevel; +use PhpParser\Node\Stmt; +use Trismegiste\Mondrian\Transform\Vertex\MethodVertex; +use Trismegiste\Mondrian\Transform\Vertex\ImplVertex; +use Trismegiste\Mondrian\Transform\Vertex\ParamVertex; /** * ObjectLevelHelper is an helper for common behavior of interface/class/trait */ abstract class ObjectLevelHelper extends AbstractObjectLevel { - + + /** + * Adding a new vertex if the method is not already indexed + * Since it is a method, I'm also adding the parameters + * + * @param \PHPParser_Node_Stmt_ClassMethod $node + */ + protected function pushMethod(Stmt\ClassMethod $node, $index) + { + $dict = $this->getGraphContext(); + if (!$dict->existsVertex('method', $index)) { + $v = new MethodVertex($index); + $this->getGraph()->addVertex($v); + $dict->indicesVertex('method', $index, $v); + // now param + foreach ($node->params as $order => $aParam) { + $this->pushParameter($index, $order); + } + } + } + + /** + * Adding a new vertex if the implementation is not already indexed + * + * @param \PHPParser_Node_Stmt_ClassMethod $node + */ + protected function pushImplementation(\PHPParser_Node_Stmt_ClassMethod $node, $index) + { + $dict = $this->getGraphContext(); + if (!$dict->existsVertex('impl', $index)) { + $v = new ImplVertex($index); + $this->getGraph()->addVertex($v); + $dict->indicesVertex('impl', $index, $v); + } + } + + /** + * Add a parameter vertex. I must point out that I store the order + * of the parameter, not its name. Why ? Because, name can change accross + * inheritance tree. Therefore, it could fail the refactoring of the source + * from the digraph. + * + * @param string $methodName like 'FQCN::method' + * @param int $order + */ + protected function pushParameter($methodName, $order) + { + $dict = $this->getGraphContext(); + $index = $methodName . '/' . $order; + if (!$dict->existsVertex('param', $index)) { + $v = new ParamVertex($index); + $this->getGraph()->addVertex($v); + $dict->indicesVertex('param', $index, $v); + } + } + } \ No newline at end of file diff --git a/src/Visitor/VertexCollector.php b/src/Visitor/VertexCollector.php index 2513ed1..9d6b024 100644 --- a/src/Visitor/VertexCollector.php +++ b/src/Visitor/VertexCollector.php @@ -62,7 +62,8 @@ private function enterTraitMethod(\PHPParser_Node_Stmt_ClassMethod $node) $this->pushImplementation($node); } - // push param for implementation + // push param for implementation, these parameters will be connected + // to copy-pasted signature (see below) $index = $this->currentClass . '::' . $this->currentMethod; foreach ($node->params as $order => $aParam) { $this->pushParameter($index, $order); @@ -79,6 +80,7 @@ private function enterTraitMethod(\PHPParser_Node_Stmt_ClassMethod $node) $this->graph->addVertex($v); $this->indicesVertex('method', $index, $v); } + // we do not copy-paste the parameters, there will be connected to original parameters from trait (see above) } } diff --git a/tests/Visitor/SymbolMap/CollectorTest.php b/tests/Visitor/SymbolMap/CollectorTest.php index 68b8df8..b22bab7 100644 --- a/tests/Visitor/SymbolMap/CollectorTest.php +++ b/tests/Visitor/SymbolMap/CollectorTest.php @@ -4,7 +4,7 @@ * Mondrian */ -namespace Trismegiste\Mondrian\Tests\Visitor; +namespace Trismegiste\Mondrian\Tests\Visitor\SymbolMap; use Trismegiste\Mondrian\Visitor\SymbolMap\Collector; use Trismegiste\Mondrian\Transform\ReflectionContext; diff --git a/tests/Visitor/Vertex/CollectorTest.php b/tests/Visitor/Vertex/CollectorTest.php new file mode 100644 index 0000000..d1f2b0a --- /dev/null +++ b/tests/Visitor/Vertex/CollectorTest.php @@ -0,0 +1,201 @@ +reflection = $this->getMockBuilder('Trismegiste\Mondrian\Transform\ReflectionContext') + ->getMock(); + $this->vertex = $this->getMockBuilder('Trismegiste\Mondrian\Transform\GraphContext') + ->disableOriginalConstructor() + ->getMock(); + $this->graph = $this->getMockBuilder('Trismegiste\Mondrian\Graph\Graph') + ->getMock(); + $this->visitor = new Collector($this->reflection, $this->vertex, $this->graph); + } + + public function getTypeNodeSetting() + { + $vertexNS = 'Trismegiste\Mondrian\Transform\Vertex\\'; + $fileNode = new \Trismegiste\Mondrian\Parser\PhpFile('dummy', []); + $nsNode = new \PHPParser_Node_Stmt_Namespace(new \PHPParser_Node_Name('Tubular')); + $classNode = new \PHPParser_Node_Stmt_Class('Bells'); + $interfNode = new \PHPParser_Node_Stmt_Interface('Bells'); + $traitNode = new \PHPParser_Node_Stmt_Trait('Bells'); + return array( + array('class', 'Tubular\Bells', $vertexNS . 'ClassVertex', array($fileNode, $nsNode, $classNode)), + // array('interface', 'Tubular\Bells', $vertexNS . 'InterfaceVertex', array($fileNode, $nsNode, $interfNode)), + // array('trait', 'Tubular\Bells', $vertexNS . 'TraitVertex', array($fileNode, $nsNode, $traitNode)) + ); + } + + /** + * @dataProvider getTypeNodeSetting + */ + public function testNoNewClassVertex($type, $fqcn, $graphVertex, array $nodeList) + { + $this->vertex + ->expects($this->once()) + ->method('existsVertex') + ->with($type, $fqcn) + ->will($this->returnValue(true)); + + $this->vertex + ->expects($this->never()) + ->method('addVertex'); + + foreach ($nodeList as $node) { + $this->visitor->enterNode($node); + } + } + + /** + * @dataProvider getTypeNodeSetting + */ + public function testNewClassVertex($type, $fqcn, $graphVertex, array $nodeList) + { + $this->vertex + ->expects($this->once()) + ->method('existsVertex') + ->with($type, $fqcn) + ->will($this->returnValue(false)); + + $this->vertex + ->expects($this->once()) + ->method('indicesVertex') + ->with($type, $fqcn); + + $this->graph + ->expects($this->once()) + ->method('addVertex') + ->with($this->isInstanceOf($graphVertex)); + + foreach ($nodeList as $node) { + $this->visitor->enterNode($node); + } + } + + /** + * @dataProvider getTypeNodeSetting + */ + public function testNewMethodVertex($type, $fqcn, $graphVertex, array $nodeList) + { + $method = new \PHPParser_Node_Stmt_ClassMethod('crisis'); + $method->params[] = new \PHPParser_Node_Param('incantations'); + $nodeList[] = $method; + + $this->reflection + ->expects($this->once()) + ->method('getDeclaringClass') + ->with($fqcn, 'crisis') + ->will($this->returnValue($fqcn)); + + $this->graph + ->expects($this->exactly($type == 'interface' ? 3 : 4)) + ->method('addVertex'); + + $this->graph + ->expects($this->at(0)) + ->method('addVertex') + ->with($this->isInstanceOf($graphVertex)); + + $this->graph + ->expects($this->at(1)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); + + $this->graph + ->expects($this->at(2)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ParamVertex')); + + if ($type != 'interface') { + $this->graph + ->expects($this->at(3)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ImplVertex')); + } + + foreach ($nodeList as $node) { + $this->visitor->enterNode($node); + } + } + + /** + * @dataProvider getTypeNodeSetting + */ + public function testCopyPasteImportedMethodFromTrait($type, $fqcn, $graphVertex, array $nodeList) + { + if ($type === 'trait') { + $method = new \PHPParser_Node_Stmt_ClassMethod('crisis'); + $method->params[] = new \PHPParser_Node_Param('incantations'); + $nodeList[] = $method; + + $this->reflection + ->expects($this->once()) + ->method('isTrait') + ->with($fqcn) + ->will($this->returnValue(true)); + + $this->reflection + ->expects($this->once()) + ->method('getClassesUsingTraitForDeclaringMethod') + ->with($fqcn, 'crisis') + ->will($this->returnValue(['TraitUser1', 'TraitUser2'])); + + $this->graph + ->expects($this->exactly(5)) + ->method('addVertex'); + + // the trait vertex + $this->graph + ->expects($this->at(0)) + ->method('addVertex') + ->with($this->isInstanceOf($graphVertex)); + + // implementation + $this->graph + ->expects($this->at(1)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ImplVertex')); + $this->graph + ->expects($this->at(2)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ParamVertex')); + + // first copy-pasted method + $this->graph + ->expects($this->at(3)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); + + // second copy-pasted method + $this->graph + ->expects($this->at(4)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); + + foreach ($nodeList as $node) { + $this->visitor->enterNode($node); + } + } + } + +} From 5b35ea25c07b69cf88c66536d790b88f8f325b4e Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 12:09:07 +0200 Subject: [PATCH 18/36] visitor for interface vertex --- src/Visitor/Vertex/Collector.php | 2 +- src/Visitor/Vertex/InterfaceLevel.php | 36 ++++++++++++++++++++++++++ tests/Visitor/Vertex/CollectorTest.php | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/Visitor/Vertex/InterfaceLevel.php diff --git a/src/Visitor/Vertex/Collector.php b/src/Visitor/Vertex/Collector.php index 55140fd..dad8c14 100644 --- a/src/Visitor/Vertex/Collector.php +++ b/src/Visitor/Vertex/Collector.php @@ -23,7 +23,7 @@ public function __construct(ReflectionContext $ref, GraphContext $grf, Graph $g) new \Trismegiste\Mondrian\Visitor\State\PackageLevel(), new FileLevel(), new ClassLevel(), -// new InterfaceLevel(), + new InterfaceLevel(), // new TraitLevel() ]; diff --git a/src/Visitor/Vertex/InterfaceLevel.php b/src/Visitor/Vertex/InterfaceLevel.php new file mode 100644 index 0000000..35702dd --- /dev/null +++ b/src/Visitor/Vertex/InterfaceLevel.php @@ -0,0 +1,36 @@ +getType() == 'Stmt_ClassMethod') && + $node->isPublic()) { + $methodName = $node->name; + $fqcn = $this->getCurrentFqcn(); + // if this class is declaring the method, we create a vertex for this signature + $declaringClass = $this->getReflectionContext()->getDeclaringClass($fqcn, $methodName); + if ($fqcn == $declaringClass) { + $this->pushMethod($node, "$fqcn::$methodName"); + } + } + } + + public function getName() + { + return 'interface'; + } + +} \ No newline at end of file diff --git a/tests/Visitor/Vertex/CollectorTest.php b/tests/Visitor/Vertex/CollectorTest.php index d1f2b0a..876c098 100644 --- a/tests/Visitor/Vertex/CollectorTest.php +++ b/tests/Visitor/Vertex/CollectorTest.php @@ -41,7 +41,7 @@ public function getTypeNodeSetting() $traitNode = new \PHPParser_Node_Stmt_Trait('Bells'); return array( array('class', 'Tubular\Bells', $vertexNS . 'ClassVertex', array($fileNode, $nsNode, $classNode)), - // array('interface', 'Tubular\Bells', $vertexNS . 'InterfaceVertex', array($fileNode, $nsNode, $interfNode)), + array('interface', 'Tubular\Bells', $vertexNS . 'InterfaceVertex', array($fileNode, $nsNode, $interfNode)), // array('trait', 'Tubular\Bells', $vertexNS . 'TraitVertex', array($fileNode, $nsNode, $traitNode)) ); } From 68d7165ed58dc7c1aa30051f9ed0fa10aab77492 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 13:16:06 +0200 Subject: [PATCH 19/36] refactor for vertex collector almost ok --- src/Transform/GraphBuilder.php | 2 +- src/Visitor/Vertex/ClassLevel.php | 28 ++++++------ src/Visitor/Vertex/Collector.php | 2 +- src/Visitor/Vertex/InterfaceLevel.php | 18 +++----- src/Visitor/Vertex/ObjectLevelHelper.php | 15 ++++++- src/Visitor/Vertex/TraitLevel.php | 54 ++++++++++++++++++++++++ tests/Visitor/Vertex/CollectorTest.php | 31 ++++++-------- 7 files changed, 102 insertions(+), 48 deletions(-) create mode 100644 src/Visitor/Vertex/TraitLevel.php diff --git a/src/Transform/GraphBuilder.php b/src/Transform/GraphBuilder.php index 8fa929d..7d27101 100644 --- a/src/Transform/GraphBuilder.php +++ b/src/Transform/GraphBuilder.php @@ -58,7 +58,7 @@ public function buildCollectors() { return array( new Visitor\SymbolMap\Collector($this->reflection, $this->vertexContext, $this->graphResult), - new Visitor\VertexCollector($this->reflection, $this->vertexContext, $this->graphResult), + new Visitor\Vertex\Collector($this->reflection, $this->vertexContext, $this->graphResult), new Visitor\EdgeCollector($this->reflection, $this->vertexContext, $this->graphResult) ); } diff --git a/src/Visitor/Vertex/ClassLevel.php b/src/Visitor/Vertex/ClassLevel.php index 80c9bf8..90f4a98 100644 --- a/src/Visitor/Vertex/ClassLevel.php +++ b/src/Visitor/Vertex/ClassLevel.php @@ -6,7 +6,7 @@ namespace Trismegiste\Mondrian\Visitor\Vertex; -use PhpParser\Node; +use PhpParser\Node\Stmt; /** * ClassLevel is ... @@ -14,22 +14,18 @@ class ClassLevel extends ObjectLevelHelper { - public function enter(Node $node) + protected function enterPublicMethod($fqcn, Stmt\ClassMethod $node) { - if (($node->getType() == 'Stmt_ClassMethod') && - $node->isPublic()) { - $methodName = $node->name; - $fqcn = $this->getCurrentFqcn(); - // if this class is declaring the method, we create a vertex for this signature - $declaringClass = $this->getReflectionContext()->getDeclaringClass($fqcn, $methodName); - if ($fqcn == $declaringClass) { - $this->pushMethod($node, "$fqcn::$methodName"); - } - - // if not abstract we add the vertex for the implementation - if (!$node->isAbstract()) { - $this->pushImplementation($node, "$fqcn::$methodName"); - } + $methodName = $node->name; + // if this class is declaring the method, we create a vertex for this signature + $declaringClass = $this->getReflectionContext()->getDeclaringClass($fqcn, $methodName); + if ($fqcn == $declaringClass) { + $this->pushMethod($node, "$fqcn::$methodName"); + } + + // if not abstract we add the vertex for the implementation + if (!$node->isAbstract()) { + $this->pushImplementation($node, "$fqcn::$methodName"); } } diff --git a/src/Visitor/Vertex/Collector.php b/src/Visitor/Vertex/Collector.php index dad8c14..1655af9 100644 --- a/src/Visitor/Vertex/Collector.php +++ b/src/Visitor/Vertex/Collector.php @@ -24,7 +24,7 @@ public function __construct(ReflectionContext $ref, GraphContext $grf, Graph $g) new FileLevel(), new ClassLevel(), new InterfaceLevel(), -// new TraitLevel() + new TraitLevel() ]; parent::__construct($visitor, $ref, $grf, $g); diff --git a/src/Visitor/Vertex/InterfaceLevel.php b/src/Visitor/Vertex/InterfaceLevel.php index 35702dd..3b64031 100644 --- a/src/Visitor/Vertex/InterfaceLevel.php +++ b/src/Visitor/Vertex/InterfaceLevel.php @@ -6,7 +6,7 @@ namespace Trismegiste\Mondrian\Visitor\Vertex; -use PhpParser\Node; +use PhpParser\Node\Stmt; /** * InterfaceLevel is ... @@ -14,17 +14,13 @@ class InterfaceLevel extends ObjectLevelHelper { - public function enter(Node $node) + protected function enterPublicMethod($fqcn, Stmt\ClassMethod $node) { - if (($node->getType() == 'Stmt_ClassMethod') && - $node->isPublic()) { - $methodName = $node->name; - $fqcn = $this->getCurrentFqcn(); - // if this class is declaring the method, we create a vertex for this signature - $declaringClass = $this->getReflectionContext()->getDeclaringClass($fqcn, $methodName); - if ($fqcn == $declaringClass) { - $this->pushMethod($node, "$fqcn::$methodName"); - } + $methodName = $node->name; + // if this class is declaring the method, we create a vertex for this signature + $declaringClass = $this->getReflectionContext()->getDeclaringClass($fqcn, $methodName); + if ($fqcn == $declaringClass) { + $this->pushMethod($node, "$fqcn::$methodName"); } } diff --git a/src/Visitor/Vertex/ObjectLevelHelper.php b/src/Visitor/Vertex/ObjectLevelHelper.php index e6c9211..ee5977d 100644 --- a/src/Visitor/Vertex/ObjectLevelHelper.php +++ b/src/Visitor/Vertex/ObjectLevelHelper.php @@ -7,6 +7,7 @@ namespace Trismegiste\Mondrian\Visitor\Vertex; use Trismegiste\Mondrian\Visitor\State\AbstractObjectLevel; +use PhpParser\Node; use PhpParser\Node\Stmt; use Trismegiste\Mondrian\Transform\Vertex\MethodVertex; use Trismegiste\Mondrian\Transform\Vertex\ImplVertex; @@ -41,9 +42,9 @@ protected function pushMethod(Stmt\ClassMethod $node, $index) /** * Adding a new vertex if the implementation is not already indexed * - * @param \PHPParser_Node_Stmt_ClassMethod $node + * @param Stmt\ClassMethod $node */ - protected function pushImplementation(\PHPParser_Node_Stmt_ClassMethod $node, $index) + protected function pushImplementation(Stmt\ClassMethod $node, $index) { $dict = $this->getGraphContext(); if (!$dict->existsVertex('impl', $index)) { @@ -73,4 +74,14 @@ protected function pushParameter($methodName, $order) } } + final public function enter(Node $node) + { + if (($node->getType() == 'Stmt_ClassMethod') && + $node->isPublic()) { + $fqcn = $this->getCurrentFqcn(); + $this->enterPublicMethod($fqcn, $node); + } + } + + abstract protected function enterPublicMethod($fqcn, Stmt\ClassMethod $node); } \ No newline at end of file diff --git a/src/Visitor/Vertex/TraitLevel.php b/src/Visitor/Vertex/TraitLevel.php new file mode 100644 index 0000000..69db27b --- /dev/null +++ b/src/Visitor/Vertex/TraitLevel.php @@ -0,0 +1,54 @@ +name; + $index = "$fqcn::$methodName"; + // create implemenation node + // if not abstract we add the vertex for the implementation + if (!$node->isAbstract()) { + $this->pushImplementation($node, $index); + } + + // push param for implementation, these parameters will be connected + // to copy-pasted signature (see below) + foreach ($node->params as $order => $aParam) { + $this->pushParameter($index, $order); + } + + // copy paste this signature in every class which use this current trait + // Anyway we check if there is no other parent which declaring first this method + $traitUser = $this->getReflectionContext()->getClassesUsingTraitForDeclaringMethod($fqcn, $methodName); + foreach ($traitUser as $classname) { + // we copy-paste the signature declaration in the class which using the current trait + $index = $classname . '::' . $methodName; + if (!$this->getGraphContext()->existsVertex('method', $index)) { + $v = new MethodVertex($index); + $this->getGraph()->addVertex($v); + $this->getGraphContext()->indicesVertex('method', $index, $v); + } + // we do not copy-paste the parameters, there will be connected to original parameters from trait (see above) + } + } + +} \ No newline at end of file diff --git a/tests/Visitor/Vertex/CollectorTest.php b/tests/Visitor/Vertex/CollectorTest.php index 876c098..1f3538b 100644 --- a/tests/Visitor/Vertex/CollectorTest.php +++ b/tests/Visitor/Vertex/CollectorTest.php @@ -42,7 +42,7 @@ public function getTypeNodeSetting() return array( array('class', 'Tubular\Bells', $vertexNS . 'ClassVertex', array($fileNode, $nsNode, $classNode)), array('interface', 'Tubular\Bells', $vertexNS . 'InterfaceVertex', array($fileNode, $nsNode, $interfNode)), - // array('trait', 'Tubular\Bells', $vertexNS . 'TraitVertex', array($fileNode, $nsNode, $traitNode)) + array('trait', 'Tubular\Bells', $vertexNS . 'TraitVertex', array($fileNode, $nsNode, $traitNode)) ); } @@ -108,31 +108,34 @@ public function testNewMethodVertex($type, $fqcn, $graphVertex, array $nodeList) ->will($this->returnValue($fqcn)); $this->graph - ->expects($this->exactly($type == 'interface' ? 3 : 4)) + ->expects($this->exactly($type == 'class' ? 4 : 3)) ->method('addVertex'); - +/* + $cpt = 0; $this->graph - ->expects($this->at(0)) + ->expects($this->at($cpt++)) ->method('addVertex') ->with($this->isInstanceOf($graphVertex)); - $this->graph - ->expects($this->at(1)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); + if ($type != 'trait') { + $this->graph + ->expects($this->at($cpt++)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); + } $this->graph - ->expects($this->at(2)) + ->expects($this->at($cpt++)) ->method('addVertex') ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ParamVertex')); if ($type != 'interface') { $this->graph - ->expects($this->at(3)) + ->expects($this->at($cpt++)) ->method('addVertex') ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ImplVertex')); } - +*/ foreach ($nodeList as $node) { $this->visitor->enterNode($node); } @@ -148,12 +151,6 @@ public function testCopyPasteImportedMethodFromTrait($type, $fqcn, $graphVertex, $method->params[] = new \PHPParser_Node_Param('incantations'); $nodeList[] = $method; - $this->reflection - ->expects($this->once()) - ->method('isTrait') - ->with($fqcn) - ->will($this->returnValue(true)); - $this->reflection ->expects($this->once()) ->method('getClassesUsingTraitForDeclaringMethod') From 29a7eb7a1157edaae6eeb1a8a42874a35fa6ebc6 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 13:41:22 +0200 Subject: [PATCH 20/36] fixing silent bug in vertex collector tests --- tests/Visitor/Vertex/CollectorTest.php | 204 ++++++++++++++++--------- 1 file changed, 134 insertions(+), 70 deletions(-) diff --git a/tests/Visitor/Vertex/CollectorTest.php b/tests/Visitor/Vertex/CollectorTest.php index 1f3538b..4de4464 100644 --- a/tests/Visitor/Vertex/CollectorTest.php +++ b/tests/Visitor/Vertex/CollectorTest.php @@ -92,11 +92,9 @@ public function testNewClassVertex($type, $fqcn, $graphVertex, array $nodeList) } } - /** - * @dataProvider getTypeNodeSetting - */ - public function testNewMethodVertex($type, $fqcn, $graphVertex, array $nodeList) + public function testNewMethodVertexForClass() { + list($type, $fqcn, $graphVertex, $nodeList) = $this->getTypeNodeSetting()[0]; $method = new \PHPParser_Node_Stmt_ClassMethod('crisis'); $method->params[] = new \PHPParser_Node_Param('incantations'); $nodeList[] = $method; @@ -108,90 +106,156 @@ public function testNewMethodVertex($type, $fqcn, $graphVertex, array $nodeList) ->will($this->returnValue($fqcn)); $this->graph - ->expects($this->exactly($type == 'class' ? 4 : 3)) + ->expects($this->exactly(4)) ->method('addVertex'); -/* - $cpt = 0; + $this->graph - ->expects($this->at($cpt++)) + ->expects($this->at(0)) ->method('addVertex') ->with($this->isInstanceOf($graphVertex)); - if ($type != 'trait') { - $this->graph - ->expects($this->at($cpt++)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); + $this->graph + ->expects($this->at(1)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); + + $this->graph + ->expects($this->at(2)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ParamVertex')); + + $this->graph + ->expects($this->at(3)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ImplVertex')); + + foreach ($nodeList as $node) { + $this->visitor->enterNode($node); } + } + + public function testNewMethodVertexForInterface() + { + list($type, $fqcn, $graphVertex, $nodeList) = $this->getTypeNodeSetting()[1]; + $method = new \PHPParser_Node_Stmt_ClassMethod('crisis'); + $method->params[] = new \PHPParser_Node_Param('incantations'); + $nodeList[] = $method; + + $this->reflection + ->expects($this->once()) + ->method('getDeclaringClass') + ->with($fqcn, 'crisis') + ->will($this->returnValue($fqcn)); + + $this->graph + ->expects($this->exactly(3)) + ->method('addVertex'); $this->graph - ->expects($this->at($cpt++)) + ->expects($this->at(0)) + ->method('addVertex') + ->with($this->isInstanceOf($graphVertex)); + + $this->graph + ->expects($this->at(1)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); + + $this->graph + ->expects($this->at(2)) ->method('addVertex') ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ParamVertex')); - if ($type != 'interface') { - $this->graph - ->expects($this->at($cpt++)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ImplVertex')); + foreach ($nodeList as $node) { + $this->visitor->enterNode($node); } -*/ + } + + public function testNewImplementationVertexForTrait() + { + list($type, $fqcn, $graphVertex, $nodeList) = $this->getTypeNodeSetting()[2]; + $method = new \PHPParser_Node_Stmt_ClassMethod('crisis'); + $method->params[] = new \PHPParser_Node_Param('incantations'); + $nodeList[] = $method; + + $this->reflection + ->expects($this->once()) + ->method('getClassesUsingTraitForDeclaringMethod') + ->with($fqcn, 'crisis') + ->will($this->returnValue([])); + + $this->graph + ->expects($this->exactly(3)) + ->method('addVertex'); + + $this->graph + ->expects($this->at(0)) + ->method('addVertex') + ->with($this->isInstanceOf($graphVertex)); + + $this->graph + ->expects($this->at(1)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ImplVertex')); + + $this->graph + ->expects($this->at(2)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ParamVertex')); + foreach ($nodeList as $node) { $this->visitor->enterNode($node); } } - /** - * @dataProvider getTypeNodeSetting - */ - public function testCopyPasteImportedMethodFromTrait($type, $fqcn, $graphVertex, array $nodeList) + public function testCopyPasteImportedMethodFromTrait() { - if ($type === 'trait') { - $method = new \PHPParser_Node_Stmt_ClassMethod('crisis'); - $method->params[] = new \PHPParser_Node_Param('incantations'); - $nodeList[] = $method; - - $this->reflection - ->expects($this->once()) - ->method('getClassesUsingTraitForDeclaringMethod') - ->with($fqcn, 'crisis') - ->will($this->returnValue(['TraitUser1', 'TraitUser2'])); - - $this->graph - ->expects($this->exactly(5)) - ->method('addVertex'); - - // the trait vertex - $this->graph - ->expects($this->at(0)) - ->method('addVertex') - ->with($this->isInstanceOf($graphVertex)); - - // implementation - $this->graph - ->expects($this->at(1)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ImplVertex')); - $this->graph - ->expects($this->at(2)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ParamVertex')); - - // first copy-pasted method - $this->graph - ->expects($this->at(3)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); - - // second copy-pasted method - $this->graph - ->expects($this->at(4)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); - - foreach ($nodeList as $node) { - $this->visitor->enterNode($node); - } + list($type, $fqcn, $graphVertex, $nodeList) = $this->getTypeNodeSetting()[2]; + + $method = new \PHPParser_Node_Stmt_ClassMethod('crisis'); + $method->params[] = new \PHPParser_Node_Param('incantations'); + $nodeList[] = $method; + + $this->reflection + ->expects($this->once()) + ->method('getClassesUsingTraitForDeclaringMethod') + ->with($fqcn, 'crisis') + ->will($this->returnValue(['TraitUser1', 'TraitUser2'])); + + $this->graph + ->expects($this->exactly(5)) + ->method('addVertex'); + + // the trait vertex + $this->graph + ->expects($this->at(0)) + ->method('addVertex') + ->with($this->isInstanceOf($graphVertex)); + + // implementation + $this->graph + ->expects($this->at(1)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ImplVertex')); + $this->graph + ->expects($this->at(2)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ParamVertex')); + + // first copy-pasted method + $this->graph + ->expects($this->at(3)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); + + // second copy-pasted method + $this->graph + ->expects($this->at(4)) + ->method('addVertex') + ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); + + foreach ($nodeList as $node) { + $this->visitor->enterNode($node); } } From 550a99236aa81c9f570e168515fda650a78f5172 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 14:20:58 +0200 Subject: [PATCH 21/36] starting refactor edge collector --- src/Visitor/Edge/ClassLevel.php | 27 ++ src/Visitor/Edge/Collector.php | 33 ++ src/Visitor/Edge/FileLevel.php | 55 +++ src/Visitor/Edge/InterfaceLevel.php | 27 ++ src/Visitor/Edge/ObjectLevelHelper.php | 17 + src/Visitor/Edge/TraitLevel.php | 27 ++ src/Visitor/State/AbstractState.php | 5 + src/Visitor/State/VisitorContext.php | 17 + tests/Visitor/Edge/CollectorTest.php | 507 +++++++++++++++++++++++++ 9 files changed, 715 insertions(+) create mode 100644 src/Visitor/Edge/ClassLevel.php create mode 100644 src/Visitor/Edge/Collector.php create mode 100644 src/Visitor/Edge/FileLevel.php create mode 100644 src/Visitor/Edge/InterfaceLevel.php create mode 100644 src/Visitor/Edge/ObjectLevelHelper.php create mode 100644 src/Visitor/Edge/TraitLevel.php create mode 100644 tests/Visitor/Edge/CollectorTest.php diff --git a/src/Visitor/Edge/ClassLevel.php b/src/Visitor/Edge/ClassLevel.php new file mode 100644 index 0000000..36f0547 --- /dev/null +++ b/src/Visitor/Edge/ClassLevel.php @@ -0,0 +1,27 @@ +getNamespacedName($node); + $src = $this->findVertex('class', $fqcn); + + // extends + if (!is_null($node->extends)) { + if (null !== $dst = $this->findVertex('class', (string) $this->resolveClassName($node->extends))) { + $this->getGraph()->addEdge($src, $dst); + } + } + // implements + foreach ($node->implements as $interf) { + if (null !== $dst = $this->findVertex('interface', (string) $this->resolveClassName($interf))) { + $this->getGraph()->addEdge($src, $dst); + } + } + } + + protected function enterInterfaceNode(Stmt\Interface_ $node) + { + $fqcn = $this->getNamespacedName($node); + $src = $this->findVertex('interface', $fqcn); + + // implements + foreach ($node->extends as $interf) { + if (null !== $dst = $this->findVertex('interface', (string) $this->resolveClassName($interf))) { + $this->getGraph()->addEdge($src, $dst); + } + } + } + + protected function enterTraitNode(Stmt\Trait_ $node) + { + + } + +} \ No newline at end of file diff --git a/src/Visitor/Edge/InterfaceLevel.php b/src/Visitor/Edge/InterfaceLevel.php new file mode 100644 index 0000000..b4e0f4a --- /dev/null +++ b/src/Visitor/Edge/InterfaceLevel.php @@ -0,0 +1,27 @@ +context->getGraph(); } + protected function findVertex($type, $key) + { + return $this->context->getGraphContext()->findVertex($type, $key); + } + } \ No newline at end of file diff --git a/src/Visitor/State/VisitorContext.php b/src/Visitor/State/VisitorContext.php index ab82e5f..ae7f670 100644 --- a/src/Visitor/State/VisitorContext.php +++ b/src/Visitor/State/VisitorContext.php @@ -23,13 +23,30 @@ interface VisitorContext */ public function pushState($stateKey, Node $node); + /** + * @param string $stateKey + * @return Node + */ public function getNodeFor($stateKey); + /** + * @param string $stateKey + * @return State + */ public function getState($stateKey); + /** + * @return \Trismegiste\Mondrian\Transform\ReflectionContext + */ public function getReflectionContext(); + /** + * @return \Trismegiste\Mondrian\Transform\GraphContext + */ public function getGraphContext(); + /** + * @return \Trismegiste\Mondrian\Graph\Graph + */ public function getGraph(); } \ No newline at end of file diff --git a/tests/Visitor/Edge/CollectorTest.php b/tests/Visitor/Edge/CollectorTest.php new file mode 100644 index 0000000..69edaa6 --- /dev/null +++ b/tests/Visitor/Edge/CollectorTest.php @@ -0,0 +1,507 @@ +reflection = $this->getMockBuilder('Trismegiste\Mondrian\Transform\ReflectionContext') + ->getMock(); + $this->dictionary = $this->getMockBuilder('Trismegiste\Mondrian\Transform\GraphContext') + ->disableOriginalConstructor() + ->getMock(); + $this->graph = $this->getMockBuilder('Trismegiste\Mondrian\Graph\Graph') + ->getMock(); + $this->visitor = new Collector($this->reflection, $this->dictionary, $this->graph); + + $vertexNS = 'Trismegiste\Mondrian\Transform\Vertex'; + $this->vertex = array( + 'C' => $this->getMockBuilder("$vertexNS\ClassVertex") + ->disableOriginalConstructor() + ->getMock(), + 'I' => $this->getMockBuilder("$vertexNS\InterfaceVertex") + ->disableOriginalConstructor() + ->getMock(), + 'M' => $this->getMockBuilder("$vertexNS\MethodVertex") + ->disableOriginalConstructor() + ->getMock(), + 'S' => $this->getMockBuilder("$vertexNS\ImplVertex") + ->disableOriginalConstructor() + ->getMock(), + 'P' => $this->getMockBuilder("$vertexNS\ParamVertex") + ->disableOriginalConstructor() + ->getMock(), + 'T' => $this->getMockBuilder("$vertexNS\TraitVertex") + ->disableOriginalConstructor() + ->getMock() + ); + + $this->dictionary + ->expects($this->any()) + ->method('findVertex') + ->will($this->returnValueMap(array( + array('class', 'Atavachron\Funnels', $this->vertex['C']), + array('class', 'Atavachron\Looking', $this->vertex['C']), + array('interface', 'Atavachron\Glass', $this->vertex['I']), + array('interface', 'Atavachron\Berwell', $this->vertex['I']), + array('method', 'Atavachron\Berwell::clown', $this->vertex['M']), + array('method', "Atavachron\Funnels::sand", $this->vertex['M']), + array('impl', "Atavachron\Funnels::sand", $this->vertex['S']), + array('param', 'Atavachron\Berwell::clown/0', $this->vertex['P']), + array('param', 'Atavachron\Funnels::sand/0', $this->vertex['P']), + ['trait', 'Atavachron\Dominant', $this->vertex['T']], + ['trait', 'Atavachron\Synthaxe', $this->vertex['T']], + ['impl', 'Atavachron\Dominant::plague', $this->vertex['S']], + ['param', 'Atavachron\Dominant::plague/0', $this->vertex['P']], + ['method', 'Atavachron\Funnels::plague', $this->vertex['M']], + ['method', 'Atavachron\Looking::plague', $this->vertex['M']] + ))); + + $this->reflection + ->expects($this->any()) + ->method('isInterface') + ->will($this->returnValueMap(array( + array('Atavachron\Glass', true), + array('Atavachron\Berwell', true) + ))); + + $this->nodeList[-1] = new \Trismegiste\Mondrian\Parser\PhpFile('dummy', []); + $this->nodeList[0] = new \PHPParser_Node_Stmt_Namespace(new \PHPParser_Node_Name('Atavachron')); + } + + protected function visitNodeList() + { + foreach ($this->nodeList as $node) { + $this->visitor->enterNode($node); + } + } + + /** + * Test for : + * * C -> C + * * C -> I + */ + public function testClassInheritance() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); + $this->nodeList[1]->extends = new \PHPParser_Node_Name('Looking'); + $this->nodeList[1]->implements[] = new \PHPParser_Node_Name('Glass'); + + $this->graph + ->expects($this->at(0)) + ->method('addEdge') + ->with($this->vertex['C'], $this->vertex['C']); + + $this->graph + ->expects($this->at(1)) + ->method('addEdge') + ->with($this->vertex['C'], $this->vertex['I']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * I -> I + */ + public function testInterfaceInheritance() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Interface('Berwell'); + $this->nodeList[1]->extends[] = new \PHPParser_Node_Name('Glass'); + + $this->graph + ->expects($this->once()) + ->method('addEdge') + ->with($this->vertex['I'], $this->vertex['I']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * C -> M + * * M -> S + * * S -> C + */ + public function testConcreteMethod() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); + + $this->reflection + ->expects($this->once()) + ->method('getDeclaringClass') + ->with('Atavachron\Funnels', 'sand') + ->will($this->returnValue('Atavachron\Funnels')); + + $this->graph + ->expects($this->at(0)) + ->method('addEdge') + ->with($this->vertex['C'], $this->vertex['M']); + + $this->graph + ->expects($this->at(2)) + ->method('addEdge') + ->with($this->vertex['M'], $this->vertex['S']); + + $this->graph + ->expects($this->at(1)) + ->method('addEdge') + ->with($this->vertex['S'], $this->vertex['C']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * C -> S + * * S -> C + */ + public function testOverridenMethod() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); + + $this->graph + ->expects($this->at(1)) + ->method('addEdge') + ->with($this->vertex['C'], $this->vertex['S']); + + $this->graph + ->expects($this->at(0)) + ->method('addEdge') + ->with($this->vertex['S'], $this->vertex['C']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * I -> M + */ + public function testInterfaceMethod() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Interface('Berwell'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('clown'); + + $this->reflection + ->expects($this->once()) + ->method('getDeclaringClass') + ->with('Atavachron\Berwell', 'clown') + ->will($this->returnValue('Atavachron\Berwell')); + + $this->graph + ->expects($this->once()) + ->method('addEdge') + ->with($this->vertex['I'], $this->vertex['M']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * M -> P + * * P -> C + */ + public function testTypedParameterInInterface() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Interface('Berwell'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('clown'); + $this->nodeList[2]->params[] = new \PHPParser_Node_Param('obj', null, new \PHPParser_Node_Name('Funnels')); + + $this->reflection + ->expects($this->once()) + ->method('getDeclaringClass') + ->with('Atavachron\Berwell', 'clown') + ->will($this->returnValue('Atavachron\Berwell')); + + $this->graph + ->expects($this->at(0)) + ->method('addEdge') + ->with($this->vertex['I'], $this->vertex['M']); + + $this->graph + ->expects($this->at(1)) + ->method('addEdge') + ->with($this->vertex['M'], $this->vertex['P']); + + $this->graph + ->expects($this->at(2)) + ->method('addEdge') + ->with($this->vertex['P'], $this->vertex['C']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * S -> P + */ + public function testNonTypedParameterInClass() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); + $this->nodeList[2]->params[] = new \PHPParser_Node_Param('obj'); + + // Method is owned by the class + $this->reflection + ->expects($this->once()) + ->method('getDeclaringClass') + ->with('Atavachron\Funnels', 'sand') + ->will($this->returnValue('Atavachron\Funnels')); + + // edges : + $this->graph + ->expects($this->at(0)) + ->method('addEdge') + ->with($this->vertex['C'], $this->vertex['M']); + + $this->graph + ->expects($this->at(1)) + ->method('addEdge') + ->with($this->vertex['M'], $this->vertex['P']); + + $this->graph + ->expects($this->at(2)) + ->method('addEdge') + ->with($this->vertex['S'], $this->vertex['C']); + + $this->graph + ->expects($this->at(3)) + ->method('addEdge') + ->with($this->vertex['M'], $this->vertex['S']); + + $this->graph + ->expects($this->at(4)) + ->method('addEdge') + ->with($this->vertex['S'], $this->vertex['P']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * S -> C + */ + public function testNewInstance() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); + $this->nodeList[3] = new \PHPParser_Node_Expr_New(new \PHPParser_Node_Name('Looking')); + + // edges : + $this->graph + ->expects($this->at(2)) + ->method('addEdge') + ->with($this->vertex['S'], $this->vertex['C']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * S -> M + */ + public function testSimpleCallFallback() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); + $this->nodeList[3] = new \PHPParser_Node_Expr_MethodCall( + new \PHPParser_Node_Expr_Variable('obj'), 'clown'); + + $this->dictionary + ->expects($this->once()) + ->method('findAllMethodSameName') + ->with('clown') + ->will($this->returnValue(array($this->vertex['M']))); + + $this->dictionary + ->expects($this->any()) + ->method('getExcludedCall') + ->will($this->returnValue(array())); + + // edges : + $this->graph + ->expects($this->at(2)) + ->method('addEdge') + ->with($this->vertex['S'], $this->vertex['M']); + + $this->visitNodeList(); + } + + /** + * Test static call S -> M + */ + public function testStaticCall() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); + $this->nodeList[3] = new \PHPParser_Node_Expr_StaticCall( + new \PHPParser_Node_Name('Berwell'), 'clown'); + + // edges : + $this->graph + ->expects($this->at(2)) + ->method('addEdge') + ->with($this->vertex['S'], $this->vertex['M']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * S -> M + */ + public function testTypedCall() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); + $this->nodeList[2]->params[] = new \PHPParser_Node_Param('obj', null, new \PHPParser_Node_Name('Berwell')); + $this->nodeList[3] = new \PHPParser_Node_Expr_MethodCall(new \PHPParser_Node_Expr_Variable('obj'), 'clown'); + + $this->dictionary + ->expects($this->any()) + ->method('getExcludedCall') + ->will($this->returnValue(array())); + + $this->reflection + ->expects($this->once()) + ->method('hasDeclaringClass') + ->will($this->returnValue(true)); + + $this->reflection + ->expects($this->once()) + ->method('findMethodInInheritanceTree') + ->will($this->returnArgument(0)); + + // edges : + $this->graph + ->expects($this->at(2)) + ->method('addEdge') + ->with($this->vertex['S'], $this->vertex['M']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * S -> M + */ + public function testExcludingCall() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); + $this->nodeList[3] = new \PHPParser_Node_Expr_MethodCall( + new \PHPParser_Node_Expr_Variable('obj'), 'clown'); + + $this->dictionary + ->expects($this->once()) + ->method('findAllMethodSameName') + ->with('clown') + ->will($this->returnValue(array($this->vertex['M']))); + + $this->vertex['M'] + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue('excluded')); + + $this->dictionary + ->expects($this->once()) + ->method('getExcludedCall') + ->with('Atavachron\Funnels', 'sand') + ->will($this->returnValue(array('excluded'))); + + // edges : + $this->graph + ->expects($this->exactly(2)) + ->method('addEdge'); + + $this->graph + ->expects($this->at(0)) + ->method('addEdge') + ->with($this->vertex['S'], $this->vertex['C']); + + $this->graph + ->expects($this->at(1)) + ->method('addEdge') + ->with($this->vertex['C'], $this->vertex['S']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * T -> S + * * S -> T + */ + public function testSimpleTrait() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Trait('Dominant'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('plague'); + + // edges : + $this->graph + ->expects($this->exactly(2)) + ->method('addEdge'); + + $this->graph + ->expects($this->at(1)) + ->method('addEdge') + ->with($this->vertex['T'], $this->vertex['S']); + + $this->graph + ->expects($this->at(0)) + ->method('addEdge') + ->with($this->vertex['S'], $this->vertex['T']); + + $this->visitNodeList(); + } + + /** + * Test for : + * * T -> T + */ + public function testTraitUsingTrait() + { + $this->nodeList[1] = new \PHPParser_Node_Stmt_Trait('Synthaxe'); + $this->nodeList[2] = new \PHPParser_Node_Stmt_Trait('Dominant'); + $this->nodeList[3] = new \PHPParser_Node_Stmt_TraitUse([new \PHPParser_Node_Name('Synthaxe')]); + + // edges : + $this->graph + ->expects($this->once()) + ->method('addEdge') + ->with($this->vertex['T'], $this->vertex['T']); + + $this->visitNodeList(); + } + +} From 05de76a564320c8aff5322139808966fb8704245 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 18:08:07 +0200 Subject: [PATCH 22/36] first draft for method level --- src/Visitor/Edge/ClassLevel.php | 86 +++++++++++++++++++++++++++- tests/Visitor/Edge/CollectorTest.php | 8 +-- 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/Visitor/Edge/ClassLevel.php b/src/Visitor/Edge/ClassLevel.php index 36f0547..9f29814 100644 --- a/src/Visitor/Edge/ClassLevel.php +++ b/src/Visitor/Edge/ClassLevel.php @@ -7,6 +7,7 @@ namespace Trismegiste\Mondrian\Visitor\Edge; use PhpParser\Node\Stmt; +use Trismegiste\Mondrian\Transform\Vertex\MethodVertex; /** * ClassLevel is ... @@ -16,7 +17,18 @@ class ClassLevel extends ObjectLevelHelper public function enter(\PhpParser\Node $node) { - + switch ($node->getType()) { + + case 'Stmt_ClassMethod': + if ($node->isPublic()) { +// $this->context->pushState('class-method', $node); + $this->enterPublicMethod($node); + } + break; + + case 'Stmt_TraitUse': + break; + } } public function getName() @@ -24,4 +36,76 @@ public function getName() return 'class'; } + protected function enterPublicMethod(Stmt\ClassMethod $node) + { + $fileState = $this->context->getState('file'); + // NS + $methodName = $node->name; + $currentFqcn = $this->getCurrentFqcn(); + $declaringFqcn = $this->getReflectionContext()->getDeclaringClass($currentFqcn, $methodName); + // Vertices + $signatureIndex = $declaringFqcn . '::' . $methodName; + $classVertex = $this->findVertex('class', $currentFqcn); + $signatureVertex = $this->findVertex('method', $signatureIndex); + $implVertex = $this->findVertex('impl', $currentFqcn . '::' . $methodName); + + // if current class == declaring class, we add the edge + if ($declaringFqcn == $currentFqcn) { + $this->getGraph()->addEdge($classVertex, $signatureVertex); // C -> M + if (!$node->isAbstract()) { + $this->getGraph()->addEdge($signatureVertex, $implVertex); // M -> S + $this->getGraph()->addEdge($implVertex, $classVertex); // S -> C + } + } else { + if (!$node->isAbstract()) { + $this->getGraph()->addEdge($classVertex, $implVertex); // C -> S + $this->getGraph()->addEdge($implVertex, $classVertex); // S -> C + } + } + + // in any case, we link the implementation to the params + foreach ($node->params as $idx => $param) { + // adding edge from signature to param : + $paramVertex = $this->findVertex('param', $signatureIndex . '/' . $idx); + // it is possible to not find the param because the signature + // is external to the source code : + if (!is_null($paramVertex)) { + if (!$node->isAbstract()) { + $this->getGraph()->addEdge($implVertex, $paramVertex); // S -> P + } + if ($currentFqcn === $declaringFqcn) { + $this->getGraph()->addEdge($signatureVertex, $paramVertex); // M -> P + } + // now the type of the param : + if ($param->type instanceof \PhpParser\Node\Name) { + $paramType = (string) $fileState->resolveClassName($param->type); + // there is a type, we add a link to the type, if it is found + $typeVertex = $this->findTypeVertex($paramType); + if (!is_null($typeVertex)) { + // we add the edge + $this->getGraph()->addEdge($paramVertex, $typeVertex); + } + } + } + } + } + + /** + * Find a class or interface + * + * @param string $type fqcn to be found + * @return Vertex + */ + protected function findTypeVertex($type) + { + foreach (array('class', 'interface') as $pool) { + $typeVertex = $this->findVertex($pool, $type); + if (!is_null($typeVertex)) { + return $typeVertex; + } + } + + return null; + } + } \ No newline at end of file diff --git a/tests/Visitor/Edge/CollectorTest.php b/tests/Visitor/Edge/CollectorTest.php index 69edaa6..4c2dd21 100644 --- a/tests/Visitor/Edge/CollectorTest.php +++ b/tests/Visitor/Edge/CollectorTest.php @@ -168,12 +168,12 @@ public function testConcreteMethod() ->with($this->vertex['C'], $this->vertex['M']); $this->graph - ->expects($this->at(2)) + ->expects($this->at(1)) ->method('addEdge') ->with($this->vertex['M'], $this->vertex['S']); $this->graph - ->expects($this->at(1)) + ->expects($this->at(2)) ->method('addEdge') ->with($this->vertex['S'], $this->vertex['C']); @@ -191,12 +191,12 @@ public function testOverridenMethod() $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); $this->graph - ->expects($this->at(1)) + ->expects($this->at(0)) ->method('addEdge') ->with($this->vertex['C'], $this->vertex['S']); $this->graph - ->expects($this->at(0)) + ->expects($this->at(1)) ->method('addEdge') ->with($this->vertex['S'], $this->vertex['C']); From d9cc9802b5d1866fb588e7d1bc6ec3e45abc71af Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 18:30:34 +0200 Subject: [PATCH 23/36] interface level ok --- src/Visitor/Edge/ClassLevel.php | 31 ++--------------------- src/Visitor/Edge/InterfaceLevel.php | 28 ++++++++++++++++++++- src/Visitor/Edge/ObjectLevelHelper.php | 35 +++++++++++++++++++++++++- tests/Visitor/Edge/CollectorTest.php | 16 ++++++------ 4 files changed, 71 insertions(+), 39 deletions(-) diff --git a/src/Visitor/Edge/ClassLevel.php b/src/Visitor/Edge/ClassLevel.php index 9f29814..4c6cd6b 100644 --- a/src/Visitor/Edge/ClassLevel.php +++ b/src/Visitor/Edge/ClassLevel.php @@ -38,7 +38,6 @@ public function getName() protected function enterPublicMethod(Stmt\ClassMethod $node) { - $fileState = $this->context->getState('file'); // NS $methodName = $node->name; $currentFqcn = $this->getCurrentFqcn(); @@ -75,37 +74,11 @@ protected function enterPublicMethod(Stmt\ClassMethod $node) } if ($currentFqcn === $declaringFqcn) { $this->getGraph()->addEdge($signatureVertex, $paramVertex); // M -> P - } - // now the type of the param : - if ($param->type instanceof \PhpParser\Node\Name) { - $paramType = (string) $fileState->resolveClassName($param->type); - // there is a type, we add a link to the type, if it is found - $typeVertex = $this->findTypeVertex($paramType); - if (!is_null($typeVertex)) { - // we add the edge - $this->getGraph()->addEdge($paramVertex, $typeVertex); - } + // now the type of the param : + $this->typeHintParam($param, $paramVertex); } } } } - /** - * Find a class or interface - * - * @param string $type fqcn to be found - * @return Vertex - */ - protected function findTypeVertex($type) - { - foreach (array('class', 'interface') as $pool) { - $typeVertex = $this->findVertex($pool, $type); - if (!is_null($typeVertex)) { - return $typeVertex; - } - } - - return null; - } - } \ No newline at end of file diff --git a/src/Visitor/Edge/InterfaceLevel.php b/src/Visitor/Edge/InterfaceLevel.php index b4e0f4a..639a35a 100644 --- a/src/Visitor/Edge/InterfaceLevel.php +++ b/src/Visitor/Edge/InterfaceLevel.php @@ -16,7 +16,33 @@ class InterfaceLevel extends ObjectLevelHelper public function enter(\PhpParser\Node $node) { - + if ($node->getType() == 'Stmt_ClassMethod') { + // NS + $methodName = $node->name; + $currentFqcn = $this->getCurrentFqcn(); + $declaringFqcn = $this->getReflectionContext()->getDeclaringClass($currentFqcn, $methodName); + // Vertices + $signatureIndex = $declaringFqcn . '::' . $methodName; + $classVertex = $this->findVertex('interface', $currentFqcn); + $signatureVertex = $this->findVertex('method', $signatureIndex); + + // if current class == declaring class, we add the edge + if ($declaringFqcn == $currentFqcn) { + $this->getGraph()->addEdge($classVertex, $signatureVertex); // I -> M + // and we link the signature to the params + foreach ($node->params as $idx => $param) { + // adding edge from signature to param : + $paramVertex = $this->findVertex('param', $signatureIndex . '/' . $idx); + // it is possible to not find the param because the signature + // is external to the source code : + if (!is_null($paramVertex)) { + $this->getGraph()->addEdge($signatureVertex, $paramVertex); // M -> P + // now the type of the param : + $this->typeHintParam($param, $paramVertex); + } + } + } + } } public function getName() diff --git a/src/Visitor/Edge/ObjectLevelHelper.php b/src/Visitor/Edge/ObjectLevelHelper.php index 249c928..f8b704e 100644 --- a/src/Visitor/Edge/ObjectLevelHelper.php +++ b/src/Visitor/Edge/ObjectLevelHelper.php @@ -7,11 +7,44 @@ namespace Trismegiste\Mondrian\Visitor\Edge; use Trismegiste\Mondrian\Visitor\State\AbstractObjectLevel; +use Trismegiste\Mondrian\Transform\Vertex\ParamVertex; +use PhpParser\Node\Param; /** * ObjectLevelHelper is */ abstract class ObjectLevelHelper extends AbstractObjectLevel { - + + /** + * Find a class or interface + * + * @param string $type fqcn to be found + * @return Vertex + */ + protected function findTypeVertex($type) + { + foreach (array('class', 'interface') as $pool) { + $typeVertex = $this->findVertex($pool, $type); + if (!is_null($typeVertex)) { + return $typeVertex; + } + } + + return null; + } + + protected function typeHintParam(Param $param, ParamVertex $source) + { + if ($param->type instanceof \PhpParser\Node\Name) { + $paramType = (string) $this->context->getState('file')->resolveClassName($param->type); + // there is a type, we add a link to the type, if it is found + $typeVertex = $this->findTypeVertex($paramType); + if (!is_null($typeVertex)) { + // we add the edge + $this->getGraph()->addEdge($source, $typeVertex); + } + } + } + } \ No newline at end of file diff --git a/tests/Visitor/Edge/CollectorTest.php b/tests/Visitor/Edge/CollectorTest.php index 4c2dd21..39a795f 100644 --- a/tests/Visitor/Edge/CollectorTest.php +++ b/tests/Visitor/Edge/CollectorTest.php @@ -285,7 +285,7 @@ public function testNonTypedParameterInClass() ->with($this->vertex['C'], $this->vertex['M']); $this->graph - ->expects($this->at(1)) + ->expects($this->at(4)) ->method('addEdge') ->with($this->vertex['M'], $this->vertex['P']); @@ -295,12 +295,12 @@ public function testNonTypedParameterInClass() ->with($this->vertex['S'], $this->vertex['C']); $this->graph - ->expects($this->at(3)) + ->expects($this->at(1)) ->method('addEdge') ->with($this->vertex['M'], $this->vertex['S']); $this->graph - ->expects($this->at(4)) + ->expects($this->at(3)) ->method('addEdge') ->with($this->vertex['S'], $this->vertex['P']); @@ -311,7 +311,7 @@ public function testNonTypedParameterInClass() * Test for : * * S -> C */ - public function testNewInstance() + public function no_testNewInstance() { $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); @@ -330,7 +330,7 @@ public function testNewInstance() * Test for : * * S -> M */ - public function testSimpleCallFallback() + public function no_testSimpleCallFallback() { $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); @@ -360,7 +360,7 @@ public function testSimpleCallFallback() /** * Test static call S -> M */ - public function testStaticCall() + public function no_testStaticCall() { $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); @@ -380,7 +380,7 @@ public function testStaticCall() * Test for : * * S -> M */ - public function testTypedCall() + public function no_testTypedCall() { $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); @@ -415,7 +415,7 @@ public function testTypedCall() * Test for : * * S -> M */ - public function testExcludingCall() + public function no_testExcludingCall() { $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); From 33feb7f518ef2e9fcb3378cdb0f2f646c5b92ced Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 19:18:53 +0200 Subject: [PATCH 24/36] object level are ok --- src/Visitor/Edge/ClassLevel.php | 5 ++- src/Visitor/Edge/ClassMethodLevel.php | 27 +++++++++++++ src/Visitor/Edge/MethodLevelHelper.php | 17 ++++++++ src/Visitor/Edge/TraitLevel.php | 56 ++++++++++++++++++++++++-- src/Visitor/Edge/TraitUserHelper.php | 37 +++++++++++++++++ tests/Visitor/Edge/CollectorTest.php | 4 ++ 6 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 src/Visitor/Edge/ClassMethodLevel.php create mode 100644 src/Visitor/Edge/MethodLevelHelper.php create mode 100644 src/Visitor/Edge/TraitUserHelper.php diff --git a/src/Visitor/Edge/ClassLevel.php b/src/Visitor/Edge/ClassLevel.php index 4c6cd6b..e21534a 100644 --- a/src/Visitor/Edge/ClassLevel.php +++ b/src/Visitor/Edge/ClassLevel.php @@ -12,7 +12,7 @@ /** * ClassLevel is ... */ -class ClassLevel extends ObjectLevelHelper +class ClassLevel extends TraitUserHelper { public function enter(\PhpParser\Node $node) @@ -27,6 +27,9 @@ public function enter(\PhpParser\Node $node) break; case 'Stmt_TraitUse': + $fqcn = $this->getCurrentFqcn(); + $currentVertex = $this->findVertex('class', $fqcn); + $this->enterTraitUse($node, $currentVertex); break; } } diff --git a/src/Visitor/Edge/ClassMethodLevel.php b/src/Visitor/Edge/ClassMethodLevel.php new file mode 100644 index 0000000..9e97765 --- /dev/null +++ b/src/Visitor/Edge/ClassMethodLevel.php @@ -0,0 +1,27 @@ +getType()) { + case 'Stmt_TraitUse': + $fqcn = $this->getCurrentFqcn(); + $currentVertex = $this->findVertex('trait', $fqcn); + $this->enterTraitUse($node, $currentVertex); + break; + + case 'Stmt_ClassMethod': + if ($node->isPublic()) { + $this->enterPublicMethod($node); + } + break; + } } public function getName() @@ -24,4 +36,40 @@ public function getName() return 'trait'; } + protected function enterPublicMethod(Node\Stmt\ClassMethod $node) + { + // NS + $methodName = $node->name; + $currentFqcn = $this->getCurrentFqcn(); + // Vertices + $traitVertex = $this->findVertex('trait', $currentFqcn); + $implVertex = $this->findVertex('impl', $currentFqcn . '::' . $methodName); + // edge between impl and trait : + $this->getGraph()->addEdge($implVertex, $traitVertex); + $this->getGraph()->addEdge($traitVertex, $implVertex); + + // edges between impl towards param (with typed param) + foreach ($node->params as $idx => $param) { + // adding edge from implementation to param : + $paramVertex = $this->findVertex('param', "$currentFqcn::$methodName/$idx"); + $this->getGraph()->addEdge($implVertex, $paramVertex); + // now the type of the param : + $this->typeHintParam($param, $implVertex); + } + + // edge between class vertex which using the trait and copy-pasted methods : + $traitUser = $this->getReflectionContext()->getClassesUsingTraitForDeclaringMethod($currentFqcn, $methodName); + foreach ($traitUser as $classname) { + // we link the class and the signature + $source = $this->findVertex('class', $classname); + $target = $this->findVertex('method', $classname . '::' . $methodName); + $this->getGraph()->addEdge($source, $target); + // and we link the copypasted signature to unique parameter + foreach ($node->params as $idx => $param) { + $paramVertex = $this->findVertex('param', "$currentFqcn::$methodName/$idx"); + $this->getGraph()->addEdge($target, $paramVertex); + } + } + } + } \ No newline at end of file diff --git a/src/Visitor/Edge/TraitUserHelper.php b/src/Visitor/Edge/TraitUserHelper.php new file mode 100644 index 0000000..fa37c2b --- /dev/null +++ b/src/Visitor/Edge/TraitUserHelper.php @@ -0,0 +1,37 @@ +context->getState('file'); + foreach ($node->traits as $import) { + $name = (string) $fileState->resolveClassName($import); + $target = $this->findVertex('trait', $name); + // it's possible to not find a trait if it coming from an external library for example + // or could be dead code too + if (!is_null($target)) { + $this->getGraph()->addEdge($source, $target); + } + } + } + +} \ No newline at end of file diff --git a/tests/Visitor/Edge/CollectorTest.php b/tests/Visitor/Edge/CollectorTest.php index 39a795f..646ecb7 100644 --- a/tests/Visitor/Edge/CollectorTest.php +++ b/tests/Visitor/Edge/CollectorTest.php @@ -467,6 +467,10 @@ public function testSimpleTrait() $this->nodeList[1] = new \PHPParser_Node_Stmt_Trait('Dominant'); $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('plague'); + $this->reflection->expects($this->once()) + ->method('getClassesUsingTraitForDeclaringMethod') + ->will($this->returnValue([])); + // edges : $this->graph ->expects($this->exactly(2)) From 0dfc03e39ccddcd4f503aac6c81557d2e0e352ba Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 20:06:14 +0200 Subject: [PATCH 25/36] cover for call --- src/Visitor/Edge/ClassLevel.php | 3 +- src/Visitor/Edge/ClassMethodLevel.php | 126 +++++++++++++++++++++++++- src/Visitor/Edge/Collector.php | 3 +- tests/Visitor/Edge/CollectorTest.php | 2 +- 4 files changed, 126 insertions(+), 8 deletions(-) diff --git a/src/Visitor/Edge/ClassLevel.php b/src/Visitor/Edge/ClassLevel.php index e21534a..196a0cf 100644 --- a/src/Visitor/Edge/ClassLevel.php +++ b/src/Visitor/Edge/ClassLevel.php @@ -7,7 +7,6 @@ namespace Trismegiste\Mondrian\Visitor\Edge; use PhpParser\Node\Stmt; -use Trismegiste\Mondrian\Transform\Vertex\MethodVertex; /** * ClassLevel is ... @@ -21,7 +20,7 @@ public function enter(\PhpParser\Node $node) case 'Stmt_ClassMethod': if ($node->isPublic()) { -// $this->context->pushState('class-method', $node); + $this->context->pushState('class-method', $node); $this->enterPublicMethod($node); } break; diff --git a/src/Visitor/Edge/ClassMethodLevel.php b/src/Visitor/Edge/ClassMethodLevel.php index 9e97765..82341c8 100644 --- a/src/Visitor/Edge/ClassMethodLevel.php +++ b/src/Visitor/Edge/ClassMethodLevel.php @@ -6,17 +6,38 @@ namespace Trismegiste\Mondrian\Visitor\Edge; +use PhpParser\Node; + /** * ClassMethodLevel is ... - * - * @author flo */ class ClassMethodLevel extends MethodLevelHelper { - public function enter(\PhpParser\Node $node) + private $fileState; + private $currentFqcn; + private $currentMethodNode; + + public function enter(Node $node) { - + $this->currentFqcn = $this->context->getState('file')->getNamespacedName($this->context->getNodeFor('class')); + $this->currentMethodNode = $this->context->getNodeFor($this->getName()); + $this->fileState = $this->context->getState('file'); + + switch ($node->getType()) { + + case 'Expr_MethodCall' : + $this->enterMethodCall($node); + break; + + case 'Expr_New': + $this->enterNewInstance($node); + break; + + case 'Expr_StaticCall': + $this->enterStaticCall($node); + break; + } } public function getName() @@ -24,4 +45,101 @@ public function getName() return 'class-method'; } + /** + * Links the current implementation vertex to all methods with the same + * name. Filters on some obvious cases. + * + * @param \PHPParser_Node_Expr_MethodCall $node + * @return void + * + */ + protected function enterMethodCall(Node\Expr\MethodCall $node) + { + if (is_string($node->name)) { + $this->enterNonDynamicMethodCall($node); + } + } + + /** + * Process of simple call of a method + * Sample: $obj->getThing($arg); + * Do not process : call_user_func(array($obj, 'getThing'), $arg); + * Do not process : $reflectionMethod->invoke($obj, 'getThing', $arg); + * + * @param \PHPParser_Node_Expr_MethodCall $node + * @return void + */ + protected function enterNonDynamicMethodCall(Node\Expr\MethodCall $node) + { + $method = $node->name; + $candidate = null; + // skipping some obvious calls : + if (($node->var->getType() == 'Expr_Variable') && (is_string($node->var->name))) { + // searching a candidate for $called::$method + // I think there is a chain of responsibility beneath that : + $candidate = $this->getCalledMethodVertexOn($node->var->name, $method); + } + // fallback : link to every methods with the same name : + if (is_null($candidate)) { + $candidate = $this->getGraphContext() + ->findAllMethodSameName($method); + if (count($candidate)) { + // store the fallback for futher report + foreach ($candidate as $called) { + $this->getGraphContext() + ->logFallbackCall($this->currentFqcn, $this->currentMethodNode->name, $called->getName()); + } + } + } + $impl = $this->findVertex('impl', $this->currentFqcn . '::' . $this->currentMethodNode->name); + // fallback or not, we exclude calls from annotations + $exclude = $this->getGraphContext() + ->getExcludedCall($this->currentFqcn, $this->currentMethodNode->name); + foreach ($candidate as $methodVertex) { + if (!in_array($methodVertex->getName(), $exclude)) { + $this->getGraph()->addEdge($impl, $methodVertex); + } + } + } + + /** + * Try to find a signature to link with the method to call and the object against to + * + * @param string $called + * @param string $method + * @return null|array null if cannot determine vertex or an array of vertices (can be empty if no call must be made) + */ + protected function getCalledMethodVertexOn($called, $method) + { + // skipping $this : + if ($called == 'this') { + return array(); // nothing to call + } + + // checking if the called is a method param + $idx = false; + foreach ($this->currentMethodNode->params as $k => $paramSign) { + if ($paramSign->name == $called) { + $idx = $k; + break; + } + } + if (false !== $idx) { + $param = $this->currentMethodNode->params[$idx]; + // is it a typed param ? + if ($param->type instanceof \PHPParser_Node_Name) { + $paramType = (string) $this->fileState->resolveClassName($param->type); + // we check if it is an outer class or not : is it known ? + if (!is_null($cls = $this->getReflectionContext() + ->findMethodInInheritanceTree($paramType, $method))) { + if (!is_null($signature = $this->findVertex('method', "$cls::$method"))) { + return array($signature); + } + } + } + } + + return null; // can't see shit captain + } + } \ No newline at end of file diff --git a/src/Visitor/Edge/Collector.php b/src/Visitor/Edge/Collector.php index 1be8492..9423299 100644 --- a/src/Visitor/Edge/Collector.php +++ b/src/Visitor/Edge/Collector.php @@ -24,7 +24,8 @@ public function __construct(ReflectionContext $ref, GraphContext $grf, Graph $g) new FileLevel(), new ClassLevel(), new InterfaceLevel(), - new TraitLevel() + new TraitLevel(), + new ClassMethodLevel() ]; parent::__construct($visitor, $ref, $grf, $g); diff --git a/tests/Visitor/Edge/CollectorTest.php b/tests/Visitor/Edge/CollectorTest.php index 646ecb7..6d3bb53 100644 --- a/tests/Visitor/Edge/CollectorTest.php +++ b/tests/Visitor/Edge/CollectorTest.php @@ -330,7 +330,7 @@ public function no_testNewInstance() * Test for : * * S -> M */ - public function no_testSimpleCallFallback() + public function testSimpleCallFallback() { $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); From 2e08ca73ee723dd9fd34852b04653f9f0124a884 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 20:18:23 +0200 Subject: [PATCH 26/36] test for calling --- src/Visitor/Edge/ClassMethodLevel.php | 17 +++++++++++++++-- tests/Visitor/Edge/CollectorTest.php | 8 ++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Visitor/Edge/ClassMethodLevel.php b/src/Visitor/Edge/ClassMethodLevel.php index 82341c8..fbf031c 100644 --- a/src/Visitor/Edge/ClassMethodLevel.php +++ b/src/Visitor/Edge/ClassMethodLevel.php @@ -130,8 +130,7 @@ protected function getCalledMethodVertexOn($called, $method) if ($param->type instanceof \PHPParser_Node_Name) { $paramType = (string) $this->fileState->resolveClassName($param->type); // we check if it is an outer class or not : is it known ? - if (!is_null($cls = $this->getReflectionContext() - ->findMethodInInheritanceTree($paramType, $method))) { + if (!is_null($cls = $this->findMethodInInheritanceTree($paramType, $method))) { if (!is_null($signature = $this->findVertex('method', "$cls::$method"))) { return array($signature); } @@ -142,4 +141,18 @@ protected function getCalledMethodVertexOn($called, $method) return null; // can't see shit captain } + /** + * Check if the class exists before searching for the + * declaring class of the method, because class could be unknown, outside + * or code could be bugged + */ + protected function findMethodInInheritanceTree($cls, $method) + { + if ($this->context->getReflectionContext()->hasDeclaringClass($cls)) { + return $this->context->getReflectionContext()->findMethodInInheritanceTree($cls, $method); + } + + return null; + } + } \ No newline at end of file diff --git a/tests/Visitor/Edge/CollectorTest.php b/tests/Visitor/Edge/CollectorTest.php index 6d3bb53..54c2e6c 100644 --- a/tests/Visitor/Edge/CollectorTest.php +++ b/tests/Visitor/Edge/CollectorTest.php @@ -380,7 +380,7 @@ public function no_testStaticCall() * Test for : * * S -> M */ - public function no_testTypedCall() + public function testTypedCall() { $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); @@ -415,7 +415,7 @@ public function no_testTypedCall() * Test for : * * S -> M */ - public function no_testExcludingCall() + public function testExcludingCall() { $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); @@ -447,12 +447,12 @@ public function no_testExcludingCall() $this->graph ->expects($this->at(0)) ->method('addEdge') - ->with($this->vertex['S'], $this->vertex['C']); + ->with($this->vertex['C'], $this->vertex['S']); $this->graph ->expects($this->at(1)) ->method('addEdge') - ->with($this->vertex['C'], $this->vertex['S']); + ->with($this->vertex['S'], $this->vertex['C']); $this->visitNodeList(); } From 4b27ad2f8ef5e42a6a61327b586a473b03bae1f1 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 20:54:35 +0200 Subject: [PATCH 27/36] all tests clear --- src/Transform/GraphBuilder.php | 2 +- src/Visitor/Edge/ClassMethodLevel.php | 30 +++++++++++++++++++ tests/Transform/BuildGraph/ConcreteTest.php | 4 +-- .../Transform/BuildGraph/MinimalGraphTest.php | 4 +-- tests/Visitor/Edge/CollectorTest.php | 4 +-- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/Transform/GraphBuilder.php b/src/Transform/GraphBuilder.php index 7d27101..6e621ac 100644 --- a/src/Transform/GraphBuilder.php +++ b/src/Transform/GraphBuilder.php @@ -59,7 +59,7 @@ public function buildCollectors() return array( new Visitor\SymbolMap\Collector($this->reflection, $this->vertexContext, $this->graphResult), new Visitor\Vertex\Collector($this->reflection, $this->vertexContext, $this->graphResult), - new Visitor\EdgeCollector($this->reflection, $this->vertexContext, $this->graphResult) + new Visitor\Edge\Collector($this->reflection, $this->vertexContext, $this->graphResult) ); } diff --git a/src/Visitor/Edge/ClassMethodLevel.php b/src/Visitor/Edge/ClassMethodLevel.php index fbf031c..483e64e 100644 --- a/src/Visitor/Edge/ClassMethodLevel.php +++ b/src/Visitor/Edge/ClassMethodLevel.php @@ -155,4 +155,34 @@ protected function findMethodInInheritanceTree($cls, $method) return null; } + /** + * Visits a "new" statement node + * + * Add an edge from current implementation to the class which a new instance + * is created + * + * @param \PHPParser_Node_Expr_New $node + */ + protected function enterNewInstance(Node\Expr\New_ $node) + { + if ($node->class instanceof Node\Name) { + $classVertex = $this->findVertex('class', (string) $this->fileState->resolveClassName($node->class)); + if (!is_null($classVertex)) { + $impl = $this->findVertex('impl', $this->currentFqcn . '::' . $this->currentMethodNode->name); + $this->getGraph()->addEdge($impl, $classVertex); + } + } + } + + protected function enterStaticCall(Node\Expr\StaticCall $node) + { + if (($node->class instanceof Node\Name) && is_string($node->name)) { + $impl = $this->findVertex('impl', $this->currentFqcn . '::' . $this->currentMethodNode->name); + $target = $this->findVertex('method', (string) $this->fileState->resolveClassName($node->class) . '::' . $node->name); + if (!is_null($target)) { + $this->getGraph()->addEdge($impl, $target); + } + } + } + } \ No newline at end of file diff --git a/tests/Transform/BuildGraph/ConcreteTest.php b/tests/Transform/BuildGraph/ConcreteTest.php index 7102133..c500014 100644 --- a/tests/Transform/BuildGraph/ConcreteTest.php +++ b/tests/Transform/BuildGraph/ConcreteTest.php @@ -56,8 +56,8 @@ public function testConcreteInheritance() $this->graph->expects($this->exactly(6))->method('addEdge'); $this->expectsAddEdge(5, 'class', 'Kitty\Soft', 'class', 'Kitty\Warm'); - $this->expectsAddEdge(6, 'impl', 'Kitty\Soft::purr', 'class', 'Kitty\Soft'); - $this->expectsAddEdge(7, 'class', 'Kitty\Soft', 'impl', 'Kitty\Soft::purr'); + $this->expectsAddEdge(6, 'class', 'Kitty\Soft', 'impl', 'Kitty\Soft::purr'); + $this->expectsAddEdge(7, 'impl', 'Kitty\Soft::purr', 'class', 'Kitty\Soft'); $this->compile($package); } diff --git a/tests/Transform/BuildGraph/MinimalGraphTest.php b/tests/Transform/BuildGraph/MinimalGraphTest.php index 4ef5344..e65fa67 100644 --- a/tests/Transform/BuildGraph/MinimalGraphTest.php +++ b/tests/Transform/BuildGraph/MinimalGraphTest.php @@ -55,8 +55,8 @@ public function testHintType() ->setTypeHint('Cfg')))) ->getNode(); - $this->expectsAddEdge(7, 'param', 'Project\Service::run/0', 'class', 'Project\Cfg'); - $this->expectsAddEdge(10, 'impl', 'Project\Service::run', 'param', 'Project\Service::run/0'); + $this->expectsAddEdge(10, 'param', 'Project\Service::run/0', 'class', 'Project\Cfg'); + $this->expectsAddEdge(8, 'impl', 'Project\Service::run', 'param', 'Project\Service::run/0'); $this->compile($pack); } diff --git a/tests/Visitor/Edge/CollectorTest.php b/tests/Visitor/Edge/CollectorTest.php index 54c2e6c..7b00d87 100644 --- a/tests/Visitor/Edge/CollectorTest.php +++ b/tests/Visitor/Edge/CollectorTest.php @@ -311,7 +311,7 @@ public function testNonTypedParameterInClass() * Test for : * * S -> C */ - public function no_testNewInstance() + public function testNewInstance() { $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); @@ -360,7 +360,7 @@ public function testSimpleCallFallback() /** * Test static call S -> M */ - public function no_testStaticCall() + public function testStaticCall() { $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); From ee3b635c6b9e4f70def5646e8ba26e5c6937c059 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 21:03:56 +0200 Subject: [PATCH 28/36] cleaning --- src/Visitor/EdgeCollector.php | 429 ---------------------- src/Visitor/PassCollector.php | 144 -------- src/Visitor/SymbolMap.php | 111 ------ src/Visitor/VertexCollector.php | 176 --------- src/Visitor/VisitorGateway.php | 1 - tests/Visitor/EdgeCollectorTest.php | 507 -------------------------- tests/Visitor/PassCollectorTest.php | 27 -- tests/Visitor/SymbolMapTest.php | 193 ---------- tests/Visitor/VertexCollectorTest.php | 206 ----------- 9 files changed, 1794 deletions(-) delete mode 100644 src/Visitor/EdgeCollector.php delete mode 100644 src/Visitor/PassCollector.php delete mode 100644 src/Visitor/SymbolMap.php delete mode 100644 src/Visitor/VertexCollector.php delete mode 100644 tests/Visitor/EdgeCollectorTest.php delete mode 100644 tests/Visitor/PassCollectorTest.php delete mode 100644 tests/Visitor/SymbolMapTest.php delete mode 100644 tests/Visitor/VertexCollectorTest.php diff --git a/src/Visitor/EdgeCollector.php b/src/Visitor/EdgeCollector.php deleted file mode 100644 index 8d05c86..0000000 --- a/src/Visitor/EdgeCollector.php +++ /dev/null @@ -1,429 +0,0 @@ -currentMethod) { - - switch ($node->getType()) { - - case 'Expr_MethodCall' : - $this->enterMethodCall($node); - break; - - case 'Expr_New': - $this->enterNewInstance($node); - break; - - case 'Expr_StaticCall': - $this->enterStaticCall($node); - break; - } - } - - // edge for use trait - if ($node->getType() === 'Stmt_TraitUse') { - $this->enterTraitUse($node); - } - } - - /** - * {@inheritDoc} - */ - public function leaveNode(\PHPParser_Node $node) - { - parent::leaveNode($node); - - switch ($node->getType()) { - - case 'Stmt_Class': - case 'Stmt_Interface': - case 'Stmt_Trait': - $this->currentClassVertex = null; - break; - - case 'Stmt_ClassMethod' : - $this->currentMethodNode = null; - break; - } - } - - /** - * Find a ParamVertex by its [classname x mehodName x position] - * @param string $className - * @param string $methodName - * @param int $idx - * @return ParamVertex - */ - protected function findParamVertexIdx($className, $methodName, $idx) - { - return $this->findVertex('param', $className . '::' . $methodName . '/' . $idx); - } - - /** - * Find a class or interface - * - * @param string $type fqcn to be found - * @return Vertex - */ - protected function findTypeVertex($type) - { - foreach (array('class', 'interface') as $pool) { - $typeVertex = $this->findVertex($pool, $type); - if (!is_null($typeVertex)) { - return $typeVertex; - } - } - - return null; - } - - /** - * Process the method node and adding the vertex of the first declared method - * - * @param \PHPParser_Node_Stmt_ClassMethod $node - * @param \Trismegiste\Mondrian\Transform\Vertex\MethodVertex $signature - */ - protected function enterDeclaredMethodNode(\PHPParser_Node_Stmt_ClassMethod $node, MethodVertex $signature) - { - $this->graph->addEdge($this->currentClassVertex, $signature); - // managing params of the signature : - foreach ($node->params as $idx => $param) { - // adding edge from signature to param : - $paramVertex = $this->findParamVertexIdx($this->currentClass, $this->currentMethod, $idx); - $this->graph->addEdge($signature, $paramVertex); - // now the type of the param : - if ($param->type instanceof \PHPParser_Node_Name) { - $paramType = (string) $this->resolveClassName($param->type); - // there is a type, we add a link to the type, if it is found - $typeVertex = $this->findTypeVertex($paramType); - if (!is_null($typeVertex)) { - // we add the edge - $this->graph->addEdge($paramVertex, $typeVertex); - } - } - } - } - - /** - * Process the implementation vertex with the method node - * - * @param \PHPParser_Node_Stmt_ClassMethod $node - * @param MethodVertex|null $signature the first declaring method vertex - * @param string $declaringClass the first declaring class of this method - */ - protected function enterImplementationNode(\PHPParser_Node_Stmt_ClassMethod $node, $signature, $declaringClass) - { - $impl = $this->findVertex('impl', $this->currentClass . '::' . $node->name); - $this->graph->addEdge($impl, $this->currentClassVertex); - // who is embedding the impl ? - if ($declaringClass == $this->currentClass) { - $this->graph->addEdge($signature, $impl); - } else { - $this->graph->addEdge($this->currentClassVertex, $impl); - } - // in any case, we link the implementation to the params - foreach ($node->params as $idx => $param) { - // adding edge from signature to param : - $paramVertex = $this->findParamVertexIdx($declaringClass, $this->currentMethod, $idx); - // it is possible to not find the param because the signature - // is external to the source code : - if (!is_null($paramVertex)) { - $this->graph->addEdge($impl, $paramVertex); - } - } - } - - /** - * {@inheritDoc} - */ - protected function enterPublicMethodNode(\PHPParser_Node_Stmt_ClassMethod $node) - { - $this->currentMethodNode = $node; - - if ($this->isTrait($this->currentClass)) { - $this->enterTraitMethod($node); - } elseif ($this->isInterface($this->currentClass)) { - $this->enterInterfaceMethod($node); - } else { - $this->enterClassMethod($node); - } - } - - private function enterInterfaceMethod(\PHPParser_Node_Stmt_ClassMethod $node) - { - // search for the declaring class of this method - $declaringClass = $this->getDeclaringClass($this->currentClass, $this->currentMethod); - $signature = $this->findVertex('method', $declaringClass . '::' . $node->name); - // if current class == declaring class, we add the edge - if ($declaringClass == $this->currentClass) { - $this->enterDeclaredMethodNode($node, $signature); - } - } - - private function enterClassMethod(\PHPParser_Node_Stmt_ClassMethod $node) - { - // search for the declaring class of this method - $declaringClass = $this->getDeclaringClass($this->currentClass, $this->currentMethod); - $signature = $this->findVertex('method', $declaringClass . '::' . $node->name); - // if current class == declaring class, we add the edge - if ($declaringClass == $this->currentClass) { - $this->enterDeclaredMethodNode($node, $signature); - } - // if not abstract, the implementation depends on the class. - // For odd reason, a method in an interface is not abstract - // that's why, there is a double check - if (!$node->isAbstract()) { - $this->enterImplementationNode($node, $signature, $declaringClass); - } - } - - private function enterTraitMethod(\PHPParser_Node_Stmt_ClassMethod $node) - { - // edge between impl and trait : - $implVetex = $this->findVertex('impl', $this->getCurrentMethodIndex()); - $traitVertex = $this->currentClassVertex; - $this->graph->addEdge($implVetex, $traitVertex); - $this->graph->addEdge($traitVertex, $implVetex); - - // edges between impl towards param (with typed param) - foreach ($node->params as $idx => $param) { - // adding edge from implementation to param : - $paramVertex = $this->findParamVertexIdx($this->currentClass, $this->currentMethod, $idx); - $this->graph->addEdge($implVetex, $paramVertex); - // now the type of the param : - if ($param->type instanceof \PHPParser_Node_Name) { - $paramType = (string) $this->resolveClassName($param->type); - // there is a type, we add a link to the type, if it is found - $typeVertex = $this->findTypeVertex($paramType); - if (!is_null($typeVertex)) { - // we add the edge - $this->graph->addEdge($paramVertex, $typeVertex); - } - } - } - - // edge between class vertex which using the trait and copy-pasted methods : - $traitUser = $this->getClassesUsingTraitForDeclaringMethod($this->currentClass, $this->currentMethod); - foreach ($traitUser as $classname) { - // we link the class and the signature - $source = $this->findVertex('class', $classname); - $target = $this->findVertex('method', $classname . '::' . $this->currentMethod); - $this->graph->addEdge($source, $target); - // and copypasted signature to unique parameter - foreach ($node->params as $idx => $param) { - $paramVertex = $this->findParamVertexIdx($this->currentClass, $this->currentMethod, $idx); - $this->graph->addEdge($target, $paramVertex); - } - } - } - - /** - * {@inheritDoc} - */ - protected function enterInterfaceNode(\PHPParser_Node_Stmt_Interface $node) - { - $src = $this->findVertex('interface', $this->currentClass); - $this->currentClassVertex = $src; - - // implements - foreach ($node->extends as $interf) { - if (null !== $dst = $this->findVertex('interface', (string) $this->resolveClassName($interf))) { - $this->graph->addEdge($src, $dst); - } - } - } - - /** - * {@inheritDoc} - */ - protected function enterClassNode(\PHPParser_Node_Stmt_Class $node) - { - $src = $this->findVertex('class', $this->currentClass); - $this->currentClassVertex = $src; - - // extends - if (!is_null($node->extends)) { - if (null !== $dst = $this->findVertex('class', (string) $this->resolveClassName($node->extends))) { - $this->graph->addEdge($src, $dst); - } - } - // implements - foreach ($node->implements as $interf) { - if (null !== $dst = $this->findVertex('interface', (string) $this->resolveClassName($interf))) { - $this->graph->addEdge($src, $dst); - } - } - } - - /** - * Links the current implementation vertex to all methods with the same - * name. Filters on some obvious cases. - * - * @param \PHPParser_Node_Expr_MethodCall $node - * @return void - * - */ - protected function enterMethodCall(\PHPParser_Node_Expr_MethodCall $node) - { - if (is_string($node->name)) { - $this->enterNonDynamicMethodCall($node); - } - } - - protected function enterStaticCall(\PHPParser_Node_Expr_StaticCall $node) - { - if (($node->class instanceof \PHPParser_Node_Name) && is_string($node->name)) { - $impl = $this->findVertex('impl', $this->getCurrentMethodIndex()); - $target = $this->findVertex('method', (string) $this->resolveClassName($node->class) . '::' . $node->name); - if (!is_null($target)) { - $this->graph->addEdge($impl, $target); - } - } - } - - /** - * Try to find a signature to link with the method to call and the object against to - * - * @param string $called - * @param string $method - * @return null|array null if cannot determine vertex or an array of vertices (can be empty if no call must be made) - */ - protected function getCalledMethodVertexOn($called, $method) - { - // skipping $this : - if ($called == 'this') { - return array(); // nothing to call - } - - // checking if the called is a method param - $idx = false; - foreach ($this->currentMethodNode->params as $k => $paramSign) { - if ($paramSign->name == $called) { - $idx = $k; - break; - } - } - if (false !== $idx) { - $param = $this->currentMethodNode->params[$idx]; - // is it a typed param ? - if ($param->type instanceof \PHPParser_Node_Name) { - $paramType = (string) $this->resolveClassName($param->type); - // we check if it is an outer class or not : is it known ? - if (!is_null($cls = $this->findMethodInInheritanceTree($paramType, $method))) { - if (!is_null($signature = $this->findVertex('method', "$cls::$method"))) { - return array($signature); - } - } - } - } - - return null; // can't see shit captain - } - - /** - * Process of simple call of a method - * Sample: $obj->getThing($arg); - * Do not process : call_user_func(array($obj, 'getThing'), $arg); - * Do not process : $reflectionMethod->invoke($obj, 'getThing', $arg); - * - * @param \PHPParser_Node_Expr_MethodCall $node - * @return void - */ - protected function enterNonDynamicMethodCall(\PHPParser_Node_Expr_MethodCall $node) - { - $method = $node->name; - $candidate = null; - // skipping some obvious calls : - if (($node->var->getType() == 'Expr_Variable') && (is_string($node->var->name))) { - // searching a candidate for $called::$method - // I think there is a chain of responsibility beneath that : - $candidate = $this->getCalledMethodVertexOn($node->var->name, $method); - } - // fallback : link to every methods with the same name : - if (is_null($candidate)) { - $candidate = $this->findAllMethodSameName($method); - if (count($candidate)) { - // store the fallback for futher report - foreach ($candidate as $called) { - $this->logFallbackCall($this->currentClass, $this->currentMethod, $called->getName()); - } - } - } - $impl = $this->findVertex('impl', $this->currentClass . '::' . $this->currentMethod); - // fallback or not, we exclude calls from annotations - $exclude = $this->getExcludedCall($this->currentClass, $this->currentMethod); - foreach ($candidate as $methodVertex) { - if (!in_array($methodVertex->getName(), $exclude)) { - $this->graph->addEdge($impl, $methodVertex); - } - } - } - - /** - * Visits a "new" statement node - * - * Add an edge from current implementation to the class which a new instance - * is created - * - * @param \PHPParser_Node_Expr_New $node - */ - protected function enterNewInstance(\PHPParser_Node_Expr_New $node) - { - if ($node->class instanceof \PHPParser_Node_Name) { - $classVertex = $this->findVertex('class', (string) $this->resolveClassName($node->class)); - if (!is_null($classVertex)) { - $impl = $this->findVertex('impl', $this->getCurrentMethodIndex()); - $this->graph->addEdge($impl, $classVertex); - } - } - } - - protected function enterTraitNode(\PHPParser_Node_Stmt_Trait $node) - { - $src = $this->findVertex('trait', $this->currentClass); - $this->currentClassVertex = $src; - } - - protected function enterTraitUse(\PHPParser_Node_Stmt_TraitUse $node) - { - if (!$this->currentClassVertex) { - throw new \LogicException('using a trait when not in a class'); - } - - foreach ($node->traits as $import) { - $name = (string) $this->resolveClassName($import); - $target = $this->findVertex('trait', $name); - // it's possible to not find a trait if it is from an external library for example - // or could be dead code too - if (!is_null($target)) { - $this->graph->addEdge($this->currentClassVertex, $target); - } - } - } - -} diff --git a/src/Visitor/PassCollector.php b/src/Visitor/PassCollector.php deleted file mode 100644 index 7bc606c..0000000 --- a/src/Visitor/PassCollector.php +++ /dev/null @@ -1,144 +0,0 @@ -reflection = $ref; - $this->vertexDict = $grf; - $this->graph = $g; - } - - /** - * Finds the FQCN of the first declaring class/interface of a method - * - * @param string $cls subclass name - * @param string $meth method name - * @return string - */ - protected function getDeclaringClass($cls, $meth) - { - return $this->reflection->getDeclaringClass($cls, $meth); - } - - /** - * Is FQCN an interface ? - * - * @param string $cls FQCN - * - * @return bool - */ - protected function isInterface($cls) - { - return $this->reflection->isInterface($cls); - } - - /** - * Is FQCN a trait ? - * - * @param string $cls FQCN - * - * @return bool - */ - protected function isTrait($cls) - { - return $this->reflection->isTrait($cls); - } - - /** - * Find a vertex by its type and name - * - * @param string $type - * @param string $key - * @return Vertex or null - */ - protected function findVertex($type, $key) - { - return $this->vertexDict->findVertex($type, $key); - } - - /** - * See Context - */ - protected function findAllMethodSameName($method) - { - return $this->vertexDict->findAllMethodSameName($method); - } - - /** - * See Context - */ - protected function existsVertex($type, $key) - { - return $this->vertexDict->existsVertex($type, $key); - } - - /** - * Check if the class exists before searching for the - * declaring class of the method, because class could be unknown, outside - * or code could be bugged - */ - protected function findMethodInInheritanceTree($cls, $method) - { - if ($this->reflection->hasDeclaringClass($cls)) { - return $this->reflection->findMethodInInheritanceTree($cls, $method); - } - - return null; - } - - /** - * Returns a list of all classes using a trait for declaring a given method - * - * @param string $cls FQCN of trait - * - * @return array - */ - protected function getClassesUsingTraitForDeclaringMethod($cls, $method) - { - return $this->reflection->getClassesUsingTraitForDeclaringMethod($cls, $method); - } - - /** - * See Context - */ - protected function indicesVertex($typ, $index, Vertex $v) - { - $this->vertexDict->indicesVertex($typ, $index, $v); - } - - protected function logFallbackCall($class, $method, $called) - { - $this->vertexDict->logFallbackCall($class, $method, $called); - } - - protected function getExcludedCall($class, $method) - { - return $this->vertexDict->getExcludedCall($class, $method); - } - -} diff --git a/src/Visitor/SymbolMap.php b/src/Visitor/SymbolMap.php deleted file mode 100644 index 14869b0..0000000 --- a/src/Visitor/SymbolMap.php +++ /dev/null @@ -1,111 +0,0 @@ -context = $ctx; - } - - /** - * {@inheritDoc} - */ - public function enterNode(\PHPParser_Node $node) - { - parent::enterNode($node); - - switch ($node->getType()) { - - case 'Stmt_TraitUse' : - $this->importSignatureTrait($node); - break; - } - } - - /** - * {@inheritDoc} - */ - protected function enterClassNode(\PHPParser_Node_Stmt_Class $node) - { - $this->context->initSymbol($this->currentClass, ReflectionContext::SYMBOL_CLASS); - // extends - if (!is_null($node->extends)) { - $name = (string) $this->resolveClassName($node->extends); - $this->context->initSymbol($name, ReflectionContext::SYMBOL_CLASS); - $this->context->pushParentClass($this->currentClass, $name); - } - // implements - foreach ($node->implements as $parent) { - $name = (string) $this->resolveClassName($parent); - $this->context->initSymbol($name, ReflectionContext::SYMBOL_INTERFACE); - $this->context->pushParentClass($this->currentClass, $name); - } - } - - /** - * {@inheritDoc} - */ - protected function enterInterfaceNode(\PHPParser_Node_Stmt_Interface $node) - { - $this->context->initSymbol($this->currentClass, ReflectionContext::SYMBOL_INTERFACE); - // extends - foreach ($node->extends as $interf) { - $name = (string) $this->resolveClassName($interf); - $this->context->initSymbol($name, ReflectionContext::SYMBOL_INTERFACE); - $this->context->pushParentClass($this->currentClass, $name); - } - } - - /** - * {@inheritDoc} - */ - protected function enterPublicMethodNode(\PHPParser_Node_Stmt_ClassMethod $node) - { - $this->context->addMethodToClass($this->currentClass, $node->name); - } - - /** - * Compiling the pass : resolving symbols in the context - */ - public function afterTraverse(array $dummy) - { - $this->context->resolveSymbol(); - } - - protected function enterTraitNode(\PHPParser_Node_Stmt_Trait $node) - { - $this->context->initSymbol($this->currentClass, ReflectionContext::SYMBOL_TRAIT); - } - - protected function importSignatureTrait(\PHPParser_Node_Stmt_TraitUse $node) - { - // @todo do not forget aliases - foreach ($node->traits as $import) { - $name = (string) $this->resolveClassName($import); - $this->context->initSymbol($name, ReflectionContext::SYMBOL_TRAIT); - $this->context->pushUseTrait($this->currentClass, $name); - } - } - -} diff --git a/src/Visitor/VertexCollector.php b/src/Visitor/VertexCollector.php deleted file mode 100644 index 9d6b024..0000000 --- a/src/Visitor/VertexCollector.php +++ /dev/null @@ -1,176 +0,0 @@ -currentClass; - if (!$this->existsVertex('class', $index)) { - $v = new Vertex\ClassVertex($index); - $this->graph->addVertex($v); - $this->indicesVertex('class', $index, $v); - } - } - - /** - * {@inheritDoc} - */ - protected function enterInterfaceNode(\PHPParser_Node_Stmt_Interface $node) - { - $index = $this->currentClass; - if (!$this->existsVertex('interface', $index)) { - $v = new Vertex\InterfaceVertex($index); - $this->graph->addVertex($v); - $this->indicesVertex('interface', $index, $v); - } - } - - /** - * {@inheritDoc} - */ - protected function enterPublicMethodNode(\PHPParser_Node_Stmt_ClassMethod $node) - { - if ($this->isTrait($this->currentClass)) { - $this->enterTraitMethod($node); - } elseif ($this->isInterface($this->currentClass)) { - $this->enterInterfaceMethod($node); - } else { - $this->enterClassMethod($node); - } - } - - private function enterTraitMethod(\PHPParser_Node_Stmt_ClassMethod $node) - { - // create implemenation node - // if not abstract we add the vertex for the implementation - if (!$node->isAbstract()) { - $this->pushImplementation($node); - } - - // push param for implementation, these parameters will be connected - // to copy-pasted signature (see below) - $index = $this->currentClass . '::' . $this->currentMethod; - foreach ($node->params as $order => $aParam) { - $this->pushParameter($index, $order); - } - - // copy paste this signature in every class which use this current trait - // Anyway we check if there is no other parent which declaring first this method - $traitUser = $this->getClassesUsingTraitForDeclaringMethod($this->currentClass, $this->currentMethod); - foreach ($traitUser as $classname) { - // we copy-paste the signature declaration in the class which using the current trait - $index = $classname . '::' . $this->currentMethod; - if (!$this->existsVertex('method', $index)) { - $v = new Vertex\MethodVertex($index); - $this->graph->addVertex($v); - $this->indicesVertex('method', $index, $v); - } - // we do not copy-paste the parameters, there will be connected to original parameters from trait (see above) - } - } - - private function enterClassMethod(\PHPParser_Node_Stmt_ClassMethod $node) - { - // if this class is declaring the method, we create a vertex for this signature - $declaringClass = $this->getDeclaringClass($this->currentClass, $this->currentMethod); - if ($this->currentClass == $declaringClass) { - $this->pushMethod($node); - } - - // if not abstract we add the vertex for the implementation - if (!$node->isAbstract()) { - $this->pushImplementation($node); - } - } - - private function enterInterfaceMethod(\PHPParser_Node_Stmt_ClassMethod $node) - { - // if this interface is declaring the method, we create a vertex for this signature - $declaringClass = $this->getDeclaringClass($this->currentClass, $this->currentMethod); - if ($this->currentClass == $declaringClass) { - $this->pushMethod($node); - } - } - - /** - * Adding a new vertex if the method is not already indexed - * Since it is a method, I'm also adding the parameters - * - * @param \PHPParser_Node_Stmt_ClassMethod $node - */ - protected function pushMethod(\PHPParser_Node_Stmt_ClassMethod $node, $index = null) - { - if (is_null($index)) { - $index = $this->getCurrentMethodIndex(); - } - if (!$this->existsVertex('method', $index)) { - $v = new Vertex\MethodVertex($index); - $this->graph->addVertex($v); - $this->indicesVertex('method', $index, $v); - // now param - foreach ($node->params as $order => $aParam) { - $this->pushParameter($index, $order); - } - } - } - - /** - * Adding a new vertex if the implementation is not already indexed - * - * @param \PHPParser_Node_Stmt_ClassMethod $node - */ - protected function pushImplementation(\PHPParser_Node_Stmt_ClassMethod $node) - { - $index = $this->getCurrentMethodIndex(); - if (!$this->existsVertex('impl', $index)) { - $v = new Vertex\ImplVertex($index); - $this->graph->addVertex($v); - $this->indicesVertex('impl', $index, $v); - } - } - - /** - * Add a parameter vertex. I must point out that I store the order - * of the parameter, not its name. Why ? Because, name can change accross - * inheritance tree. Therefore, it could fail the refactoring of the source - * from the digraph. - * - * @param string $methodName like 'FQCN::method' - * @param int $order - */ - protected function pushParameter($methodName, $order) - { - $index = $methodName . '/' . $order; - if (!$this->existsVertex('param', $index)) { - $v = new Vertex\ParamVertex($index); - $this->graph->addVertex($v); - $this->indicesVertex('param', $index, $v); - } - } - - protected function enterTraitNode(\PHPParser_Node_Stmt_Trait $node) - { - $index = $this->currentClass; - if (!$this->existsVertex('trait', $index)) { - $v = new Vertex\TraitVertex($index); - $this->graph->addVertex($v); - $this->indicesVertex('trait', $index, $v); - } - } - -} diff --git a/src/Visitor/VisitorGateway.php b/src/Visitor/VisitorGateway.php index f9c0e9b..10b131f 100644 --- a/src/Visitor/VisitorGateway.php +++ b/src/Visitor/VisitorGateway.php @@ -10,7 +10,6 @@ use PhpParser\Node; use Trismegiste\Mondrian\Transform\ReflectionContext; use Trismegiste\Mondrian\Transform\GraphContext; -use Trismegiste\Mondrian\Graph\Vertex; use Trismegiste\Mondrian\Graph\Graph; /** diff --git a/tests/Visitor/EdgeCollectorTest.php b/tests/Visitor/EdgeCollectorTest.php deleted file mode 100644 index 931fb69..0000000 --- a/tests/Visitor/EdgeCollectorTest.php +++ /dev/null @@ -1,507 +0,0 @@ -reflection = $this->getMockBuilder('Trismegiste\Mondrian\Transform\ReflectionContext') - ->getMock(); - $this->dictionary = $this->getMockBuilder('Trismegiste\Mondrian\Transform\GraphContext') - ->disableOriginalConstructor() - ->getMock(); - $this->graph = $this->getMockBuilder('Trismegiste\Mondrian\Graph\Graph') - ->getMock(); - $this->visitor = new EdgeCollector($this->reflection, $this->dictionary, $this->graph); - - $vertexNS = 'Trismegiste\Mondrian\Transform\Vertex'; - $this->vertex = array( - 'C' => $this->getMockBuilder("$vertexNS\ClassVertex") - ->disableOriginalConstructor() - ->getMock(), - 'I' => $this->getMockBuilder("$vertexNS\InterfaceVertex") - ->disableOriginalConstructor() - ->getMock(), - 'M' => $this->getMockBuilder("$vertexNS\MethodVertex") - ->disableOriginalConstructor() - ->getMock(), - 'S' => $this->getMockBuilder("$vertexNS\ImplVertex") - ->disableOriginalConstructor() - ->getMock(), - 'P' => $this->getMockBuilder("$vertexNS\ParamVertex") - ->disableOriginalConstructor() - ->getMock(), - 'T' => $this->getMockBuilder("$vertexNS\TraitVertex") - ->disableOriginalConstructor() - ->getMock() - ); - - $this->dictionary - ->expects($this->any()) - ->method('findVertex') - ->will($this->returnValueMap(array( - array('class', 'Atavachron\Funnels', $this->vertex['C']), - array('class', 'Atavachron\Looking', $this->vertex['C']), - array('interface', 'Atavachron\Glass', $this->vertex['I']), - array('interface', 'Atavachron\Berwell', $this->vertex['I']), - array('method', 'Atavachron\Berwell::clown', $this->vertex['M']), - array('method', "Atavachron\Funnels::sand", $this->vertex['M']), - array('impl', "Atavachron\Funnels::sand", $this->vertex['S']), - array('param', 'Atavachron\Berwell::clown/0', $this->vertex['P']), - array('param', 'Atavachron\Funnels::sand/0', $this->vertex['P']), - ['trait', 'Atavachron\Dominant', $this->vertex['T']], - ['trait', 'Atavachron\Synthaxe', $this->vertex['T']], - ['impl', 'Atavachron\Dominant::plague', $this->vertex['S']], - ['param', 'Atavachron\Dominant::plague/0', $this->vertex['P']], - ['method', 'Atavachron\Funnels::plague', $this->vertex['M']], - ['method', 'Atavachron\Looking::plague', $this->vertex['M']] - ))); - - $this->reflection - ->expects($this->any()) - ->method('isInterface') - ->will($this->returnValueMap(array( - array('Atavachron\Glass', true), - array('Atavachron\Berwell', true) - ))); - - - $this->nodeList[0] = new \PHPParser_Node_Stmt_Namespace(new \PHPParser_Node_Name('Atavachron')); - } - - protected function visitNodeList() - { - foreach ($this->nodeList as $node) { - $this->visitor->enterNode($node); - } - } - - /** - * Test for : - * * C -> C - * * C -> I - */ - public function testClassInheritance() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); - $this->nodeList[1]->extends = new \PHPParser_Node_Name('Looking'); - $this->nodeList[1]->implements[] = new \PHPParser_Node_Name('Glass'); - - $this->graph - ->expects($this->at(0)) - ->method('addEdge') - ->with($this->vertex['C'], $this->vertex['C']); - - $this->graph - ->expects($this->at(1)) - ->method('addEdge') - ->with($this->vertex['C'], $this->vertex['I']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * I -> I - */ - public function testInterfaceInheritance() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Interface('Berwell'); - $this->nodeList[1]->extends[] = new \PHPParser_Node_Name('Glass'); - - $this->graph - ->expects($this->once()) - ->method('addEdge') - ->with($this->vertex['I'], $this->vertex['I']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * C -> M - * * M -> S - * * S -> C - */ - public function testConcreteMethod() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); - - $this->reflection - ->expects($this->once()) - ->method('getDeclaringClass') - ->with('Atavachron\Funnels', 'sand') - ->will($this->returnValue('Atavachron\Funnels')); - - $this->graph - ->expects($this->at(0)) - ->method('addEdge') - ->with($this->vertex['C'], $this->vertex['M']); - - $this->graph - ->expects($this->at(2)) - ->method('addEdge') - ->with($this->vertex['M'], $this->vertex['S']); - - $this->graph - ->expects($this->at(1)) - ->method('addEdge') - ->with($this->vertex['S'], $this->vertex['C']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * C -> S - * * S -> C - */ - public function testOverridenMethod() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); - - $this->graph - ->expects($this->at(1)) - ->method('addEdge') - ->with($this->vertex['C'], $this->vertex['S']); - - $this->graph - ->expects($this->at(0)) - ->method('addEdge') - ->with($this->vertex['S'], $this->vertex['C']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * I -> M - */ - public function testInterfaceMethod() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Interface('Berwell'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('clown'); - - $this->reflection - ->expects($this->once()) - ->method('getDeclaringClass') - ->with('Atavachron\Berwell', 'clown') - ->will($this->returnValue('Atavachron\Berwell')); - - $this->graph - ->expects($this->once()) - ->method('addEdge') - ->with($this->vertex['I'], $this->vertex['M']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * M -> P - * * P -> C - */ - public function testTypedParameterInInterface() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Interface('Berwell'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('clown'); - $this->nodeList[2]->params[] = new \PHPParser_Node_Param('obj', null, new \PHPParser_Node_Name('Funnels')); - - $this->reflection - ->expects($this->once()) - ->method('getDeclaringClass') - ->with('Atavachron\Berwell', 'clown') - ->will($this->returnValue('Atavachron\Berwell')); - - $this->graph - ->expects($this->at(0)) - ->method('addEdge') - ->with($this->vertex['I'], $this->vertex['M']); - - $this->graph - ->expects($this->at(1)) - ->method('addEdge') - ->with($this->vertex['M'], $this->vertex['P']); - - $this->graph - ->expects($this->at(2)) - ->method('addEdge') - ->with($this->vertex['P'], $this->vertex['C']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * S -> P - */ - public function testNonTypedParameterInClass() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); - $this->nodeList[2]->params[] = new \PHPParser_Node_Param('obj'); - - // Method is owned by the class - $this->reflection - ->expects($this->once()) - ->method('getDeclaringClass') - ->with('Atavachron\Funnels', 'sand') - ->will($this->returnValue('Atavachron\Funnels')); - - // edges : - $this->graph - ->expects($this->at(0)) - ->method('addEdge') - ->with($this->vertex['C'], $this->vertex['M']); - - $this->graph - ->expects($this->at(1)) - ->method('addEdge') - ->with($this->vertex['M'], $this->vertex['P']); - - $this->graph - ->expects($this->at(2)) - ->method('addEdge') - ->with($this->vertex['S'], $this->vertex['C']); - - $this->graph - ->expects($this->at(3)) - ->method('addEdge') - ->with($this->vertex['M'], $this->vertex['S']); - - $this->graph - ->expects($this->at(4)) - ->method('addEdge') - ->with($this->vertex['S'], $this->vertex['P']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * S -> C - */ - public function testNewInstance() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); - $this->nodeList[3] = new \PHPParser_Node_Expr_New(new \PHPParser_Node_Name('Looking')); - - // edges : - $this->graph - ->expects($this->at(2)) - ->method('addEdge') - ->with($this->vertex['S'], $this->vertex['C']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * S -> M - */ - public function testSimpleCallFallback() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); - $this->nodeList[3] = new \PHPParser_Node_Expr_MethodCall( - new \PHPParser_Node_Expr_Variable('obj'), 'clown'); - - $this->dictionary - ->expects($this->once()) - ->method('findAllMethodSameName') - ->with('clown') - ->will($this->returnValue(array($this->vertex['M']))); - - $this->dictionary - ->expects($this->any()) - ->method('getExcludedCall') - ->will($this->returnValue(array())); - - // edges : - $this->graph - ->expects($this->at(2)) - ->method('addEdge') - ->with($this->vertex['S'], $this->vertex['M']); - - $this->visitNodeList(); - } - - /** - * Test static call S -> M - */ - public function testStaticCall() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); - $this->nodeList[3] = new \PHPParser_Node_Expr_StaticCall( - new \PHPParser_Node_Name('Berwell'), 'clown'); - - // edges : - $this->graph - ->expects($this->at(2)) - ->method('addEdge') - ->with($this->vertex['S'], $this->vertex['M']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * S -> M - */ - public function testTypedCall() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); - $this->nodeList[2]->params[] = new \PHPParser_Node_Param('obj', null, new \PHPParser_Node_Name('Berwell')); - $this->nodeList[3] = new \PHPParser_Node_Expr_MethodCall(new \PHPParser_Node_Expr_Variable('obj'), 'clown'); - - $this->dictionary - ->expects($this->any()) - ->method('getExcludedCall') - ->will($this->returnValue(array())); - - $this->reflection - ->expects($this->once()) - ->method('hasDeclaringClass') - ->will($this->returnValue(true)); - - $this->reflection - ->expects($this->once()) - ->method('findMethodInInheritanceTree') - ->will($this->returnArgument(0)); - - // edges : - $this->graph - ->expects($this->at(2)) - ->method('addEdge') - ->with($this->vertex['S'], $this->vertex['M']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * S -> M - */ - public function testExcludingCall() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Class('Funnels'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('sand'); - $this->nodeList[3] = new \PHPParser_Node_Expr_MethodCall( - new \PHPParser_Node_Expr_Variable('obj'), 'clown'); - - $this->dictionary - ->expects($this->once()) - ->method('findAllMethodSameName') - ->with('clown') - ->will($this->returnValue(array($this->vertex['M']))); - - $this->vertex['M'] - ->expects($this->any()) - ->method('getName') - ->will($this->returnValue('excluded')); - - $this->dictionary - ->expects($this->once()) - ->method('getExcludedCall') - ->with('Atavachron\Funnels', 'sand') - ->will($this->returnValue(array('excluded'))); - - // edges : - $this->graph - ->expects($this->exactly(2)) - ->method('addEdge'); - - $this->graph - ->expects($this->at(0)) - ->method('addEdge') - ->with($this->vertex['S'], $this->vertex['C']); - - $this->graph - ->expects($this->at(1)) - ->method('addEdge') - ->with($this->vertex['C'], $this->vertex['S']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * T -> S - * * S -> T - */ - public function testSimpleTrait() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Trait('Dominant'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_ClassMethod('plague'); - - // edges : - $this->graph - ->expects($this->exactly(2)) - ->method('addEdge'); - - $this->graph - ->expects($this->at(1)) - ->method('addEdge') - ->with($this->vertex['T'], $this->vertex['S']); - - $this->graph - ->expects($this->at(0)) - ->method('addEdge') - ->with($this->vertex['S'], $this->vertex['T']); - - $this->visitNodeList(); - } - - /** - * Test for : - * * T -> T - */ - public function testTraitUsingTrait() - { - $this->nodeList[1] = new \PHPParser_Node_Stmt_Trait('Synthaxe'); - $this->nodeList[2] = new \PHPParser_Node_Stmt_Trait('Dominant'); - $this->nodeList[3] = new \PHPParser_Node_Stmt_TraitUse([new \PHPParser_Node_Name('Synthaxe')]); - - // edges : - $this->graph - ->expects($this->once()) - ->method('addEdge') - ->with($this->vertex['T'], $this->vertex['T']); - - $this->visitNodeList(); - } - -} diff --git a/tests/Visitor/PassCollectorTest.php b/tests/Visitor/PassCollectorTest.php deleted file mode 100644 index b826b5f..0000000 --- a/tests/Visitor/PassCollectorTest.php +++ /dev/null @@ -1,27 +0,0 @@ -getMockForAbstractClass( - 'Trismegiste\Mondrian\Visitor\PassCollector', array( - $this->getMock('Trismegiste\Mondrian\Transform\ReflectionContext'), - $this->getMockBuilder('Trismegiste\Mondrian\Transform\GraphContext') - ->disableOriginalConstructor() - ->getMock(), - $this->getMock('Trismegiste\Mondrian\Graph\Graph') - )); - } - -} diff --git a/tests/Visitor/SymbolMapTest.php b/tests/Visitor/SymbolMapTest.php deleted file mode 100644 index ad03034..0000000 --- a/tests/Visitor/SymbolMapTest.php +++ /dev/null @@ -1,193 +0,0 @@ -context = new ReflectionContext(); - $this->visitor = new SymbolMap($this->context); - $this->parser = new \PHPParser_Parser(new \PHPParser_Lexer()); - $this->traverser = new \PHPParser_NodeTraverser(); - $this->traverser->addVisitor($this->visitor); - } - - public function testExternalInterfaceInheritance() - { - $iter = array(__DIR__ . '/../Fixtures/Project/InheritExtra.php'); - foreach ($iter as $fch) { - $code = file_get_contents($fch); - $stmts = $this->parser->parse($code); - $this->traverser->traverse($stmts); - } - $this->visitor->afterTraverse(array()); - - $this->assertAttributeEquals(array( - 'Project\\InheritExtra' => array( - 'type' => 'c', - 'parent' => array(0 => 'IteratorAggregate'), - 'method' => array('getIterator' => 'IteratorAggregate'), - 'use' => [] - ), - 'IteratorAggregate' => array( - 'type' => 'i', - 'parent' => array(), - 'method' => array(), - 'use' => [] - ), - ), 'inheritanceMap', $this->context); - } - - public function testSimpleCase() - { - $iter = array(__DIR__ . '/../Fixtures/Project/Concrete.php'); - foreach ($iter as $fch) { - $code = file_get_contents($fch); - $stmts = $this->parser->parse($code); - $this->traverser->traverse($stmts); - } - $this->visitor->afterTraverse(array()); - - $this->assertAttributeEquals(array( - 'Project\\Concrete' => array( - 'type' => 'c', - 'parent' => array(), - 'method' => array('simple' => 'Project\\Concrete'), - 'use' => [] - ), - ), 'inheritanceMap', $this->context); - } - - public function testAliasing() - { - $iter = array(__DIR__ . '/../Fixtures/Project/Alias1.php', - __DIR__ . '/../Fixtures/Project/Alias2.php'); - foreach ($iter as $fch) { - $code = file_get_contents($fch); - $stmts = $this->parser->parse($code); - $this->traverser->traverse($stmts); - } - $this->visitor->afterTraverse(array()); - - $this->assertAttributeEquals(array( - 'Project\\Aliasing' => array( - 'type' => 'c', - 'parent' => array('Project\Maid', 'Project\Peril'), - 'method' => array('spokes' => 'Project\\Aliasing'), - 'use' => [] - ), - 'Project\Maid' => array( - 'type' => 'c', - 'parent' => array(), - 'method' => array(), - 'use' => [] - ), - 'Project\Peril' => array( - 'type' => 'i', - 'parent' => array(), - 'method' => array(), - 'use' => [] - ) - ), 'inheritanceMap', $this->context); - } - - public function testSimpleTrait() - { - $iter = array(__DIR__ . '/../Fixtures/Project/SimpleTrait.php'); - foreach ($iter as $fch) { - $code = file_get_contents($fch); - $stmts = $this->parser->parse($code); - $this->traverser->traverse($stmts); - } - $this->visitor->afterTraverse(array()); - - $this->assertAttributeEquals(array( - 'Project\\SimpleTrait' => array( - 'type' => 't', - 'parent' => [], - 'method' => ['someService' => 'Project\\SimpleTrait'], - 'use' => [] - ) - ), 'inheritanceMap', $this->context); - } - - protected function scanFile($iter) - { - foreach ($iter as $fch) { - $code = file_get_contents(__DIR__ . '/../Fixtures/Project/' . $fch); - $stmts = $this->parser->parse($code); - $this->traverser->traverse($stmts); - } - $this->visitor->afterTraverse(array()); - } - - public function testImportingMethodFromTrait() - { - $this->scanFile([ - 'ServiceWrong.php', - 'ServiceTrait.php' - ]); - - $this->assertAttributeEquals(array( - 'Project\\ServiceWrong' => array( - 'type' => 'c', - 'parent' => [], - 'method' => array('someService' => 'Project\\ServiceWrong'), - 'use' => ['Project\\ServiceTrait'] - ), - 'Project\\ServiceTrait' => array( - 'type' => 't', - 'parent' => [], - 'method' => array('someService' => 'Project\\ServiceTrait'), - 'use' => [] - )), 'inheritanceMap', $this->context); - } - - public function testImportingMethodFromTraitWithInterfaceCollision() - { - $this->scanFile([ - 'ServiceRight.php', - 'ServiceTrait.php', - 'ServiceInterface.php' - ]); - - $this->assertAttributeEquals(array( - 'Project\\ServiceRight' => array( - 'type' => 'c', - 'parent' => ['Project\\ServiceInterface'], - 'method' => array('someService' => 'Project\\ServiceInterface'), - 'use' => ['Project\\ServiceTrait'] - ), - 'Project\\ServiceInterface' => array( - 'type' => 'i', - 'parent' => [], - 'method' => array('someService' => 'Project\\ServiceInterface'), - 'use' => [] - ), - 'Project\\ServiceTrait' => array( - 'type' => 't', - 'parent' => [], - 'method' => array('someService' => 'Project\\ServiceTrait'), - 'use' => [] - )), 'inheritanceMap', $this->context); - } - -} diff --git a/tests/Visitor/VertexCollectorTest.php b/tests/Visitor/VertexCollectorTest.php deleted file mode 100644 index bcc9b9a..0000000 --- a/tests/Visitor/VertexCollectorTest.php +++ /dev/null @@ -1,206 +0,0 @@ -reflection = $this->getMockBuilder('Trismegiste\Mondrian\Transform\ReflectionContext') - ->getMock(); - $this->vertex = $this->getMockBuilder('Trismegiste\Mondrian\Transform\GraphContext') - ->disableOriginalConstructor() - ->getMock(); - $this->graph = $this->getMockBuilder('Trismegiste\Mondrian\Graph\Graph') - ->getMock(); - $this->visitor = new VertexCollector($this->reflection, $this->vertex, $this->graph); - } - - public function getTypeNodeSetting() - { - $vertexNS = 'Trismegiste\Mondrian\Transform\Vertex\\'; - $nsNode = new \PHPParser_Node_Stmt_Namespace(new \PHPParser_Node_Name('Tubular')); - $classNode = new \PHPParser_Node_Stmt_Class('Bells'); - $interfNode = new \PHPParser_Node_Stmt_Interface('Bells'); - $traitNode = new \PHPParser_Node_Stmt_Trait('Bells'); - return array( - array('class', 'Tubular\Bells', $vertexNS . 'ClassVertex', array($nsNode, $classNode)), - array('interface', 'Tubular\Bells', $vertexNS . 'InterfaceVertex', array($nsNode, $interfNode)), - array('trait', 'Tubular\Bells', $vertexNS . 'TraitVertex', array($nsNode, $traitNode)) - ); - } - - /** - * @dataProvider getTypeNodeSetting - */ - public function testNoNewClassVertex($type, $fqcn, $graphVertex, array $nodeList) - { - $this->vertex - ->expects($this->once()) - ->method('existsVertex') - ->with($type, $fqcn) - ->will($this->returnValue(true)); - - $this->vertex - ->expects($this->never()) - ->method('addVertex'); - - foreach ($nodeList as $node) { - $this->visitor->enterNode($node); - } - } - - /** - * @dataProvider getTypeNodeSetting - */ - public function testNewClassVertex($type, $fqcn, $graphVertex, array $nodeList) - { - $this->vertex - ->expects($this->once()) - ->method('existsVertex') - ->with($type, $fqcn) - ->will($this->returnValue(false)); - - $this->vertex - ->expects($this->once()) - ->method('indicesVertex') - ->with($type, $fqcn); - - $this->graph - ->expects($this->once()) - ->method('addVertex') - ->with($this->isInstanceOf($graphVertex)); - - foreach ($nodeList as $node) { - $this->visitor->enterNode($node); - } - } - - /** - * @dataProvider getTypeNodeSetting - */ - public function testNewMethodVertex($type, $fqcn, $graphVertex, array $nodeList) - { - $method = new \PHPParser_Node_Stmt_ClassMethod('crisis'); - $method->params[] = new \PHPParser_Node_Param('incantations'); - $nodeList[] = $method; - - $this->reflection - ->expects($this->once()) - ->method('getDeclaringClass') - ->with($fqcn, 'crisis') - ->will($this->returnValue($fqcn)); - - $this->reflection - ->expects($this->once()) - ->method('isInterface') - ->with($fqcn) - ->will($this->returnValue($type == 'interface')); - - $this->graph - ->expects($this->exactly($type == 'interface' ? 3 : 4)) - ->method('addVertex'); - - $this->graph - ->expects($this->at(0)) - ->method('addVertex') - ->with($this->isInstanceOf($graphVertex)); - - $this->graph - ->expects($this->at(1)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); - - $this->graph - ->expects($this->at(2)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ParamVertex')); - - if ($type != 'interface') { - $this->graph - ->expects($this->at(3)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ImplVertex')); - } - - foreach ($nodeList as $node) { - $this->visitor->enterNode($node); - } - } - - /** - * @dataProvider getTypeNodeSetting - */ - public function testCopyPasteImportedMethodFromTrait($type, $fqcn, $graphVertex, array $nodeList) - { - if ($type === 'trait') { - $method = new \PHPParser_Node_Stmt_ClassMethod('crisis'); - $method->params[] = new \PHPParser_Node_Param('incantations'); - $nodeList[] = $method; - - $this->reflection - ->expects($this->once()) - ->method('isTrait') - ->with($fqcn) - ->will($this->returnValue(true)); - - $this->reflection - ->expects($this->once()) - ->method('getClassesUsingTraitForDeclaringMethod') - ->with($fqcn, 'crisis') - ->will($this->returnValue(['TraitUser1', 'TraitUser2'])); - - $this->graph - ->expects($this->exactly(5)) - ->method('addVertex'); - - // the trait vertex - $this->graph - ->expects($this->at(0)) - ->method('addVertex') - ->with($this->isInstanceOf($graphVertex)); - - // implementation - $this->graph - ->expects($this->at(1)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ImplVertex')); - $this->graph - ->expects($this->at(2)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\ParamVertex')); - - // first copy-pasted method - $this->graph - ->expects($this->at(3)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); - - // second copy-pasted method - $this->graph - ->expects($this->at(4)) - ->method('addVertex') - ->with($this->isInstanceOf('Trismegiste\Mondrian\Transform\Vertex\MethodVertex')); - - foreach ($nodeList as $node) { - $this->visitor->enterNode($node); - } - } - } - -} From 4892728ee26ab16422a0ba82440339a5b1da2cb0 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 21:10:46 +0200 Subject: [PATCH 29/36] fine tuning typing --- src/Builder/Compiler/AbstractTraverser.php | 7 ++++--- src/Builder/Compiler/BuilderInterface.php | 5 ++--- tests/Visitor/SymbolMap/CollectorTest.php | 5 +++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Builder/Compiler/AbstractTraverser.php b/src/Builder/Compiler/AbstractTraverser.php index 3e8690b..ba874ef 100644 --- a/src/Builder/Compiler/AbstractTraverser.php +++ b/src/Builder/Compiler/AbstractTraverser.php @@ -6,7 +6,8 @@ namespace Trismegiste\Mondrian\Builder\Compiler; -use Trismegiste\Mondrian\Visitor; +use PhpParser\NodeVisitor; +use PhpParser\NodeTraverser; /** * AbstractTraverser partly builds the compiler with a traverser @@ -14,9 +15,9 @@ abstract class AbstractTraverser implements BuilderInterface { - public function buildTraverser(/*Visitor\FqcnHelper*/ $collector) + public function buildTraverser(NodeVisitor $collector) { - $traverser = new \PHPParser_NodeTraverser(); + $traverser = new NodeTraverser(); $traverser->addVisitor($collector); return $traverser; diff --git a/src/Builder/Compiler/BuilderInterface.php b/src/Builder/Compiler/BuilderInterface.php index e610115..abd518a 100644 --- a/src/Builder/Compiler/BuilderInterface.php +++ b/src/Builder/Compiler/BuilderInterface.php @@ -2,7 +2,7 @@ namespace Trismegiste\Mondrian\Builder\Compiler; -use Trismegiste\Mondrian\Visitor\FqcnHelper; +use PhpParser\NodeVisitor; /** * BuilderInterface is a contract to build a compiler @@ -14,6 +14,5 @@ public function buildContext(); public function buildCollectors(); - // @todo add VisitorGateway type_hint - public function buildTraverser(/*FqcnHelper*/ $collector); + public function buildTraverser(NodeVisitor $collector); } \ No newline at end of file diff --git a/tests/Visitor/SymbolMap/CollectorTest.php b/tests/Visitor/SymbolMap/CollectorTest.php index b22bab7..505885a 100644 --- a/tests/Visitor/SymbolMap/CollectorTest.php +++ b/tests/Visitor/SymbolMap/CollectorTest.php @@ -231,8 +231,9 @@ public function testTraitUsingTrait() )), 'inheritanceMap', $this->context); $this->markTestIncomplete(); // @todo the commented line above must be incommented - // I will ot create vertex for imported implementation from trait but a class using ServiceUsingTrait - // must copy-paste all methods signature + // I will not create vertex for imported implementation from trait in a trait + // but a class using ServiceUsingTrait must copy-paste all methods signatures + // coming from all aggregated traits } } From 2cef43f2c58545a28247a69f9e6ecd137cfc3797 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 15 Sep 2014 21:22:29 +0200 Subject: [PATCH 30/36] fix a bug with type-hinter parameter in trait method --- src/Visitor/Edge/TraitLevel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Visitor/Edge/TraitLevel.php b/src/Visitor/Edge/TraitLevel.php index 50d6644..ff9f7b4 100644 --- a/src/Visitor/Edge/TraitLevel.php +++ b/src/Visitor/Edge/TraitLevel.php @@ -54,7 +54,7 @@ protected function enterPublicMethod(Node\Stmt\ClassMethod $node) $paramVertex = $this->findVertex('param', "$currentFqcn::$methodName/$idx"); $this->getGraph()->addEdge($implVertex, $paramVertex); // now the type of the param : - $this->typeHintParam($param, $implVertex); + $this->typeHintParam($param, $paramVertex); } // edge between class vertex which using the trait and copy-pasted methods : From bb9aed33205ec1d9c1b9fed5701991efee68bbad Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Tue, 16 Sep 2014 00:01:32 +0200 Subject: [PATCH 31/36] phpdoc --- src/Visitor/Edge/ClassMethodLevel.php | 7 ++----- src/Visitor/Edge/Collector.php | 2 +- src/Visitor/Edge/FileLevel.php | 2 +- src/Visitor/Edge/TraitLevel.php | 12 +++++++----- src/Visitor/State/AbstractObjectLevel.php | 5 +++++ src/Visitor/State/AbstractState.php | 14 ++++++++++++++ src/Visitor/State/FileLevelTemplate.php | 17 ++++++++++++++--- src/Visitor/State/PackageLevel.php | 6 ++++++ src/Visitor/State/State.php | 3 +++ src/Visitor/SymbolMap/Collector.php | 3 ++- src/Visitor/SymbolMap/FileLevel.php | 2 +- src/Visitor/Vertex/Collector.php | 2 +- 12 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/Visitor/Edge/ClassMethodLevel.php b/src/Visitor/Edge/ClassMethodLevel.php index 483e64e..ff3200d 100644 --- a/src/Visitor/Edge/ClassMethodLevel.php +++ b/src/Visitor/Edge/ClassMethodLevel.php @@ -49,9 +49,7 @@ public function getName() * Links the current implementation vertex to all methods with the same * name. Filters on some obvious cases. * - * @param \PHPParser_Node_Expr_MethodCall $node - * @return void - * + * @param Node\Expr\MethodCall $node */ protected function enterMethodCall(Node\Expr\MethodCall $node) { @@ -66,8 +64,7 @@ protected function enterMethodCall(Node\Expr\MethodCall $node) * Do not process : call_user_func(array($obj, 'getThing'), $arg); * Do not process : $reflectionMethod->invoke($obj, 'getThing', $arg); * - * @param \PHPParser_Node_Expr_MethodCall $node - * @return void + * @param Node\Expr\MethodCall $node */ protected function enterNonDynamicMethodCall(Node\Expr\MethodCall $node) { diff --git a/src/Visitor/Edge/Collector.php b/src/Visitor/Edge/Collector.php index 9423299..b29d241 100644 --- a/src/Visitor/Edge/Collector.php +++ b/src/Visitor/Edge/Collector.php @@ -12,7 +12,7 @@ use Trismegiste\Mondrian\Graph\Graph; /** - * Collector is ... + * Collector is the main visitor for creating edges between already-created vertices */ class Collector extends VisitorGateway { diff --git a/src/Visitor/Edge/FileLevel.php b/src/Visitor/Edge/FileLevel.php index 6c9b2fc..05e6545 100644 --- a/src/Visitor/Edge/FileLevel.php +++ b/src/Visitor/Edge/FileLevel.php @@ -49,7 +49,7 @@ protected function enterInterfaceNode(Stmt\Interface_ $node) protected function enterTraitNode(Stmt\Trait_ $node) { - + // no edge to create } } \ No newline at end of file diff --git a/src/Visitor/Edge/TraitLevel.php b/src/Visitor/Edge/TraitLevel.php index ff9f7b4..2ba9fe0 100644 --- a/src/Visitor/Edge/TraitLevel.php +++ b/src/Visitor/Edge/TraitLevel.php @@ -17,17 +17,19 @@ class TraitLevel extends TraitUserHelper public function enter(Node $node) { switch ($node->getType()) { - case 'Stmt_TraitUse': - $fqcn = $this->getCurrentFqcn(); - $currentVertex = $this->findVertex('trait', $fqcn); - $this->enterTraitUse($node, $currentVertex); - break; case 'Stmt_ClassMethod': if ($node->isPublic()) { + // $this->context->pushState('trait-method', $node); $this->enterPublicMethod($node); } break; + + case 'Stmt_TraitUse': + $fqcn = $this->getCurrentFqcn(); + $currentVertex = $this->findVertex('trait', $fqcn); + $this->enterTraitUse($node, $currentVertex); + break; } } diff --git a/src/Visitor/State/AbstractObjectLevel.php b/src/Visitor/State/AbstractObjectLevel.php index eb1151c..3b1fdbc 100644 --- a/src/Visitor/State/AbstractObjectLevel.php +++ b/src/Visitor/State/AbstractObjectLevel.php @@ -12,6 +12,11 @@ abstract class AbstractObjectLevel extends AbstractState { + /** + * returns the current fqcn of this class|trait|interface + * + * @return string + */ protected function getCurrentFqcn() { $objectNode = $this->context->getNodeFor($this->getName()); diff --git a/src/Visitor/State/AbstractState.php b/src/Visitor/State/AbstractState.php index cb1f573..9d8942f 100644 --- a/src/Visitor/State/AbstractState.php +++ b/src/Visitor/State/AbstractState.php @@ -17,11 +17,17 @@ abstract class AbstractState implements State /** @var VisitorContext */ protected $context; + /** + * @inheritdoc + */ public function setContext(VisitorContext $ctx) { $this->context = $ctx; } + /** + * @inheritdoc + */ public function leave(Node $node) { @@ -51,6 +57,14 @@ protected function getGraph() return $this->context->getGraph(); } + /** + * Search for a vertex of a given type + * + * @param string $type trait|class|interface|param|method|impl + * @param string $key the key for this vertex + * + * @return \Trismegiste\Mondrian\Graph\Vertex + */ protected function findVertex($type, $key) { return $this->context->getGraphContext()->findVertex($type, $key); diff --git a/src/Visitor/State/FileLevelTemplate.php b/src/Visitor/State/FileLevelTemplate.php index 23a9f1f..edbcc45 100644 --- a/src/Visitor/State/FileLevelTemplate.php +++ b/src/Visitor/State/FileLevelTemplate.php @@ -63,10 +63,19 @@ final public function enter(Node $node) } } + /** + * Enters in a class node + */ abstract protected function enterClassNode(Node\Stmt\Class_ $node); + /** + * Enters in an interface node + */ abstract protected function enterInterfaceNode(Node\Stmt\Interface_ $node); + /** + * Enters in a trait node + */ abstract protected function enterTraitNode(Node\Stmt\Trait_ $node); public function getName() @@ -77,8 +86,9 @@ public function getName() /** * resolve the Name with current namespace and alias * - * @param \PHPParser_Node_Name $src - * @return \PHPParser_Node_Name|\PHPParser_Node_Name_FullyQualified + * @param Node\Name $src + * + * @return Node\Name|Node\Name\FullyQualified */ public function resolveClassName(Node\Name $src) { @@ -107,7 +117,8 @@ public function resolveClassName(Node\Name $src) /** * Helper : get the FQCN of the given $node->name * - * @param PHPParser_Node $node + * @param Node $node + * * @return string */ public function getNamespacedName(Node $node) diff --git a/src/Visitor/State/PackageLevel.php b/src/Visitor/State/PackageLevel.php index bb80b24..17b441f 100644 --- a/src/Visitor/State/PackageLevel.php +++ b/src/Visitor/State/PackageLevel.php @@ -14,6 +14,9 @@ class PackageLevel extends AbstractState { + /** + * @inheritdoc + */ public function enter(Node $node) { switch ($node->getType()) { @@ -23,6 +26,9 @@ public function enter(Node $node) } } + /** + * @inheritdoc + */ public function getName() { return 'package'; diff --git a/src/Visitor/State/State.php b/src/Visitor/State/State.php index bc321ab..0eed0ea 100644 --- a/src/Visitor/State/State.php +++ b/src/Visitor/State/State.php @@ -39,5 +39,8 @@ public function leave(Node $node); */ public function setContext(VisitorContext $ctx); + /** + * Gets the name of this state + */ public function getName(); } \ No newline at end of file diff --git a/src/Visitor/SymbolMap/Collector.php b/src/Visitor/SymbolMap/Collector.php index 6b8178a..5b78e02 100644 --- a/src/Visitor/SymbolMap/Collector.php +++ b/src/Visitor/SymbolMap/Collector.php @@ -12,7 +12,8 @@ use Trismegiste\Mondrian\Graph\Graph; /** - * Collector is ... + * Collector is the main visitor for indexing all namespace, method signature and inheritance + * pushed to the reflexion context */ class Collector extends VisitorGateway { diff --git a/src/Visitor/SymbolMap/FileLevel.php b/src/Visitor/SymbolMap/FileLevel.php index bc40a10..21e9dc9 100644 --- a/src/Visitor/SymbolMap/FileLevel.php +++ b/src/Visitor/SymbolMap/FileLevel.php @@ -10,7 +10,7 @@ use Trismegiste\Mondrian\Visitor\State\FileLevelTemplate; /** - * FileLevel is ... + * FileLevel registers trait/interface/class in the reflection context */ class FileLevel extends FileLevelTemplate { diff --git a/src/Visitor/Vertex/Collector.php b/src/Visitor/Vertex/Collector.php index 1655af9..4992455 100644 --- a/src/Visitor/Vertex/Collector.php +++ b/src/Visitor/Vertex/Collector.php @@ -12,7 +12,7 @@ use Trismegiste\Mondrian\Graph\Graph; /** - * Collector is ... + * Collector is the main visitor for creating vertices for each relevant nodes */ class Collector extends VisitorGateway { From d26f2120e5f423e7f55d15b38d985cb5945d1a9b Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Tue, 16 Sep 2014 01:39:39 +0200 Subject: [PATCH 32/36] starting unit test for visitor gateway --- src/Visitor/VisitorGateway.php | 3 ++ tests/Visitor/VisitorGatewayTest.php | 71 ++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/Visitor/VisitorGatewayTest.php diff --git a/src/Visitor/VisitorGateway.php b/src/Visitor/VisitorGateway.php index 10b131f..563ded8 100644 --- a/src/Visitor/VisitorGateway.php +++ b/src/Visitor/VisitorGateway.php @@ -41,6 +41,9 @@ class VisitorGateway extends NodeVisitorAbstract implements State\VisitorContext */ public function __construct(array $visitor, ReflectionContext $ref, GraphContext $grf, Graph $g) { + if (!count($visitor)) { + throw new \InvalidArgumentException("The visitors list cannot be empty"); + } $this->graphCtx = $grf; $this->graph = $g; $this->reflectionCtx = $ref; diff --git a/tests/Visitor/VisitorGatewayTest.php b/tests/Visitor/VisitorGatewayTest.php new file mode 100644 index 0000000..0785223 --- /dev/null +++ b/tests/Visitor/VisitorGatewayTest.php @@ -0,0 +1,71 @@ +sut = new VisitorGateway($visitor, $this->reflectionCtx, $this->graphCtx, $this->graph); + } + + protected function setUp() + { + $this->reflectionCtx = $this->getMock('Trismegiste\Mondrian\Transform\ReflectionContext'); + $this->graphCtx = $this->getMockBuilder('Trismegiste\Mondrian\Transform\GraphContext') + ->disableOriginalConstructor() + ->getMock(); + $this->graph = $this->getMock('Trismegiste\Mondrian\Graph\Graph'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testEmpty() + { + $this->buildVisitor(); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadState() + { + $this->buildVisitor(['sfd']); + } + + public function testStateKey() + { + $state0 = $this->getMock('Trismegiste\Mondrian\Visitor\State\State'); + $state0->expects($this->exactly(2)) + ->method('getName') + ->will($this->returnValue('key0')); + $state0->expects($this->once()) + ->method('setContext'); + + $state1 = $this->getMock('Trismegiste\Mondrian\Visitor\State\State'); + $state1->expects($this->once()) + ->method('getName') + ->will($this->returnValue('key1')); + $state1->expects($this->once()) + ->method('setContext'); + + $this->buildVisitor([$state0, $state1]); + } + +} \ No newline at end of file From 784e7f57731ed7e747bb3c2ee49a2aff49f90a82 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Tue, 16 Sep 2014 02:27:31 +0200 Subject: [PATCH 33/36] CC 100% for visitor gateway --- tests/Visitor/VisitorGatewayTest.php | 96 +++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/tests/Visitor/VisitorGatewayTest.php b/tests/Visitor/VisitorGatewayTest.php index 0785223..86bd12b 100644 --- a/tests/Visitor/VisitorGatewayTest.php +++ b/tests/Visitor/VisitorGatewayTest.php @@ -14,16 +14,34 @@ class VisitorGatewayTest extends \PHPUnit_Framework_TestCase { - protected $sut; protected $reflectionCtx; protected $graphCtx; protected $graph; + /** @var VisitorGateway */ + protected $sut; + private function buildVisitor(array $visitor = []) { $this->sut = new VisitorGateway($visitor, $this->reflectionCtx, $this->graphCtx, $this->graph); } + private function buildVisitorUnique() + { + $state = $this->getMockState('key'); + $this->buildVisitor([$state]); + } + + public function getMockState($key) + { + $state = $this->getMock('Trismegiste\Mondrian\Visitor\State\State'); + $state->expects($this->any()) + ->method('getName') + ->will($this->returnValue($key)); + + return $state; + } + protected function setUp() { $this->reflectionCtx = $this->getMock('Trismegiste\Mondrian\Transform\ReflectionContext'); @@ -68,4 +86,80 @@ public function testStateKey() $this->buildVisitor([$state0, $state1]); } + public function testEnteringNode() + { + $state = $this->getMockState('key'); + $state->expects($this->once()) + ->method('enter'); + $this->buildVisitor([$state]); + $node = $this->getMock('PhpParser\Node'); + $this->sut->enterNode($node); + } + + public function testGetState() + { + $this->buildVisitorUnique(); + $this->assertInstanceOf('Trismegiste\Mondrian\Visitor\State\State', $this->sut->getState('key')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetUnknownState() + { + $this->buildVisitorUnique(); + $this->sut->getState('svfdgbtgf'); + } + + public function testShortcut() + { + $this->buildVisitorUnique(); + $this->sut->getGraph(); + $this->sut->getGraphContext(); + $this->sut->getReflectionContext(); + } + + public function testPushState() + { + $listing = [ + $this->getMockState('one'), + $this->getMockState('two'), + $this->getMockState('three') + ]; + $this->buildVisitor($listing); + $node = [ + $this->getMock('PhpParser\Node'), + $this->getMock('PhpParser\Node'), + $this->getMock('PhpParser\Node'), + ]; + + $this->assertNull($this->sut->getNodeFor('one')); + + $this->sut->pushState('two', $node[0]); + $this->assertNull($this->sut->getNodeFor('one')); + $this->assertEquals($node[0], $this->sut->getNodeFor('two')); + + $this->sut->pushState('three', $node[1]); + $this->assertNull($this->sut->getNodeFor('one')); + $this->assertEquals($node[0], $this->sut->getNodeFor('two')); + $this->assertEquals($node[1], $this->sut->getNodeFor('three')); + + $this->sut->leaveNode($node[1]); + $this->assertNull($this->sut->getNodeFor('one')); + $this->assertEquals($node[0], $this->sut->getNodeFor('two')); + $this->assertAttributeCount(2, 'stateStack', $this->sut); + + $this->sut->leaveNode($node[0]); + $this->assertAttributeCount(1, 'stateStack', $this->sut); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testNoNodeFound() + { + $this->buildVisitorUnique(); + $this->sut->getNodeFor('fddbfgb'); + } + } \ No newline at end of file From 84610285bbf308985148e52217d30a53eaf1cf15 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Tue, 16 Sep 2014 03:00:47 +0200 Subject: [PATCH 34/36] adding test for issue with trait internals (call, static...) --- tests/Fixtures/Project/TraitInternals.php | 23 +++++++++++++ tests/Transform/ParseAndGraphTest.php | 40 +++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 tests/Fixtures/Project/TraitInternals.php diff --git a/tests/Fixtures/Project/TraitInternals.php b/tests/Fixtures/Project/TraitInternals.php new file mode 100644 index 0000000..075cb0c --- /dev/null +++ b/tests/Fixtures/Project/TraitInternals.php @@ -0,0 +1,23 @@ +simple(); + } + + public function staticCall() + { + Concrete::simple(); + } + + public function newInstance() + { + new OneClass(); + } + +} \ No newline at end of file diff --git a/tests/Transform/ParseAndGraphTest.php b/tests/Transform/ParseAndGraphTest.php index e4ab925..7e2e374 100644 --- a/tests/Transform/ParseAndGraphTest.php +++ b/tests/Transform/ParseAndGraphTest.php @@ -385,4 +385,44 @@ public function testTraitUsingTrait() , $result); } + public function testInternalForTrait_Isolated() + { + $result = $this->callParse('TraitInternals.php'); + $this->assertCount(5, $result->getVertexSet()); + + $fqcn = 'Project\TraitInternals'; + $this->assertEdges([ + [['Trait', $fqcn], ['Impl', $fqcn . '::nonDynCall']], + [['Impl', $fqcn . '::nonDynCall'], ['Trait', $fqcn]], + [['Trait', $fqcn], ['Impl', $fqcn . '::staticCall']], + [['Impl', $fqcn . '::staticCall'], ['Trait', $fqcn]], + [['Trait', $fqcn], ['Impl', $fqcn . '::newInstance']], + [['Impl', $fqcn . '::newInstance'], ['Trait', $fqcn]], + [['Impl', $fqcn . '::nonDynCall'], ['Param', $fqcn . '::nonDynCall/0']] + ], $result); + } + + public function testInternalForTrait_Calling() + { + $result = $this->callParse('TraitInternals.php', 'Concrete.php'); + $this->assertCount(8, $result->getVertexSet()); + + $fqcn = 'Project\TraitInternals'; + $ext = 'Project\Concrete'; + $this->assertEdges([ + [['Trait', $fqcn], ['Impl', $fqcn . '::nonDynCall']], + [['Impl', $fqcn . '::nonDynCall'], ['Trait', $fqcn]], + [['Trait', $fqcn], ['Impl', $fqcn . '::staticCall']], + [['Impl', $fqcn . '::staticCall'], ['Trait', $fqcn]], + [['Trait', $fqcn], ['Impl', $fqcn . '::newInstance']], + [['Impl', $fqcn . '::newInstance'], ['Trait', $fqcn]], + [['Impl', $fqcn . '::nonDynCall'], ['Param', $fqcn . '::nonDynCall/0']], + [['Param', $fqcn . '::nonDynCall/0'], ['Class', $ext]], + [['Class', $ext], ['Method', "$ext::simple"]], + [['Method', "$ext::simple"], ['Impl', "$ext::simple"]], + [['Impl', "$ext::simple"], ['Class', $ext]], + [['Impl', $fqcn . '::nonDynCall'], ['Method', "$ext::simple"]], // @todo fail + ], $result); + } + } From 6ed0c6250a3363cbe010fa8a7765493baf120d1f Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Tue, 16 Sep 2014 13:23:20 +0200 Subject: [PATCH 35/36] adding missing trait method visitor --- doc/language.dot | 8 +- doc/language.gif | Bin 146517 -> 147276 bytes src/Visitor/Edge/ClassMethodLevel.php | 166 +-------------------- src/Visitor/Edge/Collector.php | 3 +- src/Visitor/Edge/MethodLevelHelper.php | 169 +++++++++++++++++++++- src/Visitor/Edge/TraitLevel.php | 2 +- src/Visitor/Edge/TraitMethodLevel.php | 25 ++++ tests/Fixtures/Project/TraitInternals.php | 20 ++- tests/Transform/ParseAndGraphTest.php | 47 +++--- 9 files changed, 240 insertions(+), 200 deletions(-) create mode 100644 src/Visitor/Edge/TraitMethodLevel.php diff --git a/doc/language.dot b/doc/language.dot index 3ee6601..3de5652 100644 --- a/doc/language.dot +++ b/doc/language.dot @@ -79,8 +79,8 @@ rankdir=TB; { rank=same; owning3; TraitImpl [fixedsize="1", width="2", height="2", shape="pentagon", style="filled", color="hotpink"]; - traitDoSomething [fixedsize="1", width="2", height="2", shape="rectangle", style="filled", label="doSomething"]; - "The trait TraitImpl is owning doSomething() method" [shape=plaintext]; + traitDoSomething [fixedsize="1", width="2", height="2", shape="rectangle", style="filled", label="TraitImpl\ndoSomething"]; + "The trait TraitImpl is owning TraitImpl::doSomething() method" [shape=plaintext]; TraitImpl -> traitDoSomething [label="owns"]; } @@ -110,9 +110,9 @@ rankdir=TB; { rank=same; depend4; - getSize [fixedsize="1", width="2", height="2", shape="rectangle", style="filled", label="getSize"]; + getSize [fixedsize="1", width="2", height="2", shape="rectangle", style="filled", label="Image\ngetSize"]; Image [fixedsize="1", width="2", height="2", shape="pentagon", style="filled", color="hotpink"]; - "The implementation getSize() \ndepends on Image trait" [shape=plaintext]; + "The implementation Image::getSize() \ndepends on Image trait" [shape=plaintext]; getSize -> Image [label="depends"]; } diff --git a/doc/language.gif b/doc/language.gif index 05af07cb9d7d63d6114aae69ac9d189fd4c7fae7..166697d5a9d82562eb59790f020abd0b2d6e4b3d 100644 GIT binary patch delta 68812 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%0DV zy>MX25ZkO7iELW)-T!|0pF|rZs)7%sPOCZ?lTjXsxKQ>k~v0Q^3U2|t^$hw@9n zSfRN>NVG?|D$37kL*tcdi;1AQ1gThRh0Z~i511g&K!r32wl+LMIE+MV zk;8#mK|8#}hIqj~+(b_7L{HoZZ7>B<@Ibi>1s?!~A{+`&(1uZ90v_W&AZ0DMb0W#as+W5ZT3mp~GMNMLaA%T_ylfv10Kl7 zN6E*6dV?xZMz27zsW`<{gb#2OM}&MOC)AlZI7eS>gLaHaiJZvh^9OAJh5l_A1ydje zCJ>t(codIh0wzcUAMgNB(1vC(#1=V!$b@W3&sxZw=|zXM!(gOHo4iS!%*i~0M3?Nz zA%jVrX@i-xLtW5GqdZEaOv;X6NuO-W%6puK9Lgw!NTsYwtGr5zWJ;%WN^%q!U69Ha ztVygKOR_A>P1MS*^vSLa7@-791;hulj7zzk%Lzowv~a&0%LE((y8KIjzzob= zv&*|o$h-s?eBev+vj)LzOvil8>LSd-6vxB-mmyG0&65bo+)U2wOed1e$;`#d{Fi*d zOqugb&rD6#Tuq(<&Cry@(exKDFimgE2iCkz+{{gWan09+!`SSXd7%EyZqrQNEKcJ* zPBY=n-ekhx>=)7y&Lo=$a5Q9P|oEfz~S=!My8c`r!QYJM@Asy0) zDpFoCG9*==AZk)9&C;w4P$>0H0__VFf5v&DA&r)t*5>Uu_Zw99Cz2)+Q={R%4w+WbGMPUDg}n zJZRlkZfzWComOhuRbIu`8#%vjJy&$ynQsNxL=o3qF-LN3j%H0)daYM~Syy&F6nD)P zON`gl&_sI;Sb^OYe9hNF*;ibVNq;>JDjZmceOOv4Sc6Rw{)D|1cwN}iNJog>SdN_% ziJjPStX7}tSB!ms2!QQalucRp_*jtjkczz(g)P~J5KEPw{o0=_q@*QVrcGP9MccJyTShCS#3b8)Q((-w?c1}hTe~fcuoYXq zJq5V*Tf}`@zzy8N7~ERfOR`1H#GTxrU0lWu49BIF?o?X7rCiUgS*&=uX51E|teTIoz(*@f8EW!=oxT&10prkU=coH6mAa^o>&u3VHbYk zD2ZCu{ZtsPVH?(&6*kxwzF{8j;fIpp8D`a^^A9fV>C`r%7F7{$N z-e6LH_!d0QV?EwuKJH^b#^X{@u{s`PLM~)OK4e5rWJO+NMs8$Beq>0FWJ#W6O0Hx} zzGO_!WKG^=PVQt+{$x-NWl#?VlHN5K4xT2W@TP~W@c_?XMSdAj%I0|W@@fxYrbY|&Sq`i zW^V3gZ~kU*4rg&5XL2rQb3SKuPG@yqXLfF9cYbGhj%RtEXL_z@d%kCU&S!nzXMXNy zfBt8H4rqZMXo4mE9o@k1$Xp6q+j`gP&+GvjMXpjDX zXpjzRksfK1E@_ZYK6w?td69u zE;ys^YNQ71ux{$HE~B!(HK|5xs#a@%gcj?z4xzWMHI$CK z7Vg(3ZhJQF<3^O^)~nlQ?%Z~6dWP=kHk9ectKhco;l^%x*6!_Ak?tO-iR&aD?@CKKR2alrumhk_7rf_q%@C#3j42Plv=kNsgaB>Fm5I>9&ccKV4@d-z9 zaaQpaFN_wSpACoc4wrFortumdj2oAq64&t)=W%cL@gEP2Acvn9C-N9Ka&AWQB>#&g zf1Dh5@*RhAZI<#W?~5vToFT{ZBL3HMZ07PV|8mA@axs5$GPh&RKb$LH^DJ+3 zYJT%1k8{8Yb2=Y$JC|lW|8YI<88z>7Hve;I4)huy^qrY=L$7m0cVvubzWb8bzc`|V1I96_ZL|&c3MAnU{3b(UUq)jb!YE&X!m7l-)?Hx z7h=D5W6yS8-uCG3c3)|BaDR4j*JW})Zgb}qYfpDA#p|5(Pzj{#4`XJtV`dE6e zZ+fuzWU-%NvY!ussy}8%l&)z zczeN*d&0M5!)IK?XAitze7$dcN`CynjeP6~e99ku%a>%#w_DA>4#n?$#{Yas4t=&A z{pgr{)31EgcVyN7TGns=3eJCh&yRgZo_(pUeWoaV+&_JP-B)DZFIwMM3fB*Q*dKmG zF8-W7ex$g4<SQ>YLt z{)o1jhf;h$G1*7gvE|~%k0Vch6Z2S49z@-94t+M2(AKYG54~bG_wL@mgAXr$Jo)nG zf6uQqG3vI9P@q7*)sluAfKQ|##3inLKmY#zaWW@XCn4w(Fgp!9orGRo*Go$l4LP}VlvKDH0qzd+&U7na?iV1AO8mp|ce`+?0 z3ogXz+CHf06UwV-=@}?a1QA;-0Le8$kg%2wdXgxj76|QtoQR9GjfR7faUuY)OVTN@qx^T zGmc}D2Y?46&1k5~cB#vj4Dpuq^I7nI3kuL<^{iFWBvtyTC*I=Ol?kFyOy{o)W1Y3u z2OIFoClt@tus)_#%o4~uecV#V$fbSuPg$l^CrS`V`e?Qx4d}tureP|xf8PvtI^MT4 z&bu^Rh7T<_p^BTO?$IzleiY(3TMBQ=U6}$c-ehmxx#yn`xs5(eK+K&F`s5&|SQfi2 z)5mLTv5&U^+N=vxCUU`P}=sYG#%fCf`w^Jc`qlR>Lq9CTjg z3^gf;aVdh?I*|XiSE(r7!)T=g9Sv(}L-Y86fa3v0VpInf)*bI9e;PB|OOj;(qcy=L zJjg*yn!rRY7!ip|Y{EXYP`8#CfCr1=*fxTAndCX^SR(^~_v~iIk!WE+)YA;iuII+< z(9AqNBZzREm$HW4jEE$W-aVwpn2d!&IMuoqv4mrS0Rb#Vc#Pq({CE#Q3es93StJpq z7s-^(Xj+5B)}#0se-cq3s$rsdl_l@7N&b_>s9Gb@q$EEnEomX}lzsHoJxn-BQRGpK z?5dY2Xlct&hEf!xU+CnX*!;{Fgf-xHDBW*@Zf)#8e9<}MCyhRdtt&y5aT`E)O0t$Vk z;XGQ%!{vTxf3|wMu46=m2`GryF(>}fq^lZBmJq)RPOQS@o+D`(vE=tjGLp~}O_M7@ zlte)M(5*D#Dkxt&lf9ZHl&EhtR!N?B(td`tlPT3`jHFkr!%9>nbTe#>29zlBMHZ!x zQYA?ytJt~K4R3=}EMZS#Sz?~{9`Kruqlg8=%p%sXf5dDmZEI^=vUuVXRV~j`p&6EL zdhvBIAqDU>);5vGXb6Sc0@TcnuZHyMSpYN1O;<4dYt+Su3cRwa&t3+zK9 zm)P0L_IYy@?0HEt)R8ccgs_dQKWFP+o8q&Bh|RBjcUE7bq;S6weO6)F``-KBHo*#B za8XL}e+D0xCk>yN;Z}ef#N)R9L{gw2XG@&aMVM%q!y;}jLEhkHNtd0*{OEUgfWpy%GD>aH>lNhRuZ;hHrk{o1z>KKBfeJhbK zr$_<|)yCy5FoV17Wd`$*$!0m9sto8*wAA4Zf4k+1g^!0}5o^p_RMY|}#%bJ+dF#U> z_Erw6IJ^_PB*hZi>z6LD2MI{U(1>pFRsI^D7)z96q<9spxV+;aJI}WI{G;WYHAo1Vs+sj_!1C(>pTI9Z^;cf2Ma! z1@H?pM!jS9;_jrBll}yHtSP<4z5*{6UyoH*oksC1#zVmIWn8@E@zr?!O&(v>Rnq9M zdymmG-V+(ro8k||c*?zAB&C0RbfimN4VvDR0}3B58tkP5e@s9{TV(4NQ+%T9(;h#_)cqPK0iX(LxVlIP9VWp; z1^(DUh6D;9Ge;+Ea@m(lt5nL}{R3bCmI*gR6<1ggqEt`@QPACx8FV<1Gl4>#BuQ6% zlbC5mn~`4z`iJ?sMkx@BJ^;WQ_`nRYn~fmD9sqy@@PXi6fB>Z6GAsoPf4ZOqz~E9W zL;VTB1uz9NTmt?wV3|UQ z7PS=F8S&9FwbIWBhY6WiO5_0ob_E7%U>YU|2eyU>8Vd;WfaQgR-`R)+WB@YA0}9sL z1xUai$ip%K04B%-;PIg!e*&T)65ashA+gAVyIp`X06+%BVBr3?eZW-Z3VlF$%y^D1sz5<1{X!F(PA_;NPsoe;z5ig$cAuC~{*s zieqe;TeNNd1>=;2XO&tj76^N71u7`pQJ`Th(qqVg;Jx5uKI-E>@?$>U<347JyEO&i z)t|9UV?hd`Ku%&)D1!^uo8TcNBO2j2Qe;J1G!R8l2T_yYhK09VwB1v=SB2HA|rfTZA(SGd46dBr@gWLoCL zEv`l`o(m6nfG=vwPd+371mi%~AI* zuoXePoW{k~e_Uawz$x9yolE#^g$LvTgdqi59#68sPF58Q4=@4RZ6=C!CU?{c2y(-= zhy)$t2qa2CU;<(!%EJX1i!%5p0O(+V5=&s7O9tp*{VBr=3aC8PUuzO5K?LSg{zz|v zg(r!{ytLyO4$Y(R%T|1-X&Fw4dWD7xg}@LDI6mi!f3oOUfG1@R#nTYb?O7N5^dsjH z2>!tsOFRli6GZ1_*5aGJgt>K$+38t(rqz4ShICn=`-O=zxEq4@p$^J}G6Z1;7^p;A zX(Uc4CPXBE66Fs@V_=3Um2N4P-rwgWp-~78eYr)O`qx$@A>ynlQNZcqRLwB8=$`Va zSk%EMf4rh*Gy}jnk5)8;K2RT+Eehsgf7JjR%%o1X8v)g zQA~|Hb|d4|X{&Lhd6jBPgz8Xa&MF0JpTcUari2fC0*u;)NsUCIXhjb2z;7+;iQ$f& zC>K2C&N(&Lu@VbA9ZP0vK@Rc2-Hjhx%7%Sze=1Y>WmBABwQ>rNG~hivU_8Z0-EjVk z2~i3$)tu9aWt2z?r2x$VK??YYPr8~<1L@EDIMWBY>-Sj829YbojL_2jtEGGp{m5%b zMBwynkOC@+yNVk5oQP$L1W(E8#8RwD)B!i}s9k`PGc|)58pTs-gRg2J56syWb(rpC ze-RH<(Xkp06SxGCwwgg=K$}g2f zFRksB2|7wP*za^z^`W|?}MzxI9x}!ze)Jc9?P37*T?bPg+R7m91(wRi^(iHAC6iW?ne*|$3 zOd%FWiHcF>>huim_D*aXk^$wZJ20X)^yO(ZaEgd;OOyk< z9%(&RYH^7=?hFQ3aH?|e2uo!tf4G1Je#g{Qfr`o_eM9=b>>W)oL0z;Y;PnmxuO@O)Sll8CiQIAKm zBdZ-534`&9)`37|rFeJ(D!9N#R+tOd6AYV`u|jHdWziGOFr3X(4xiW#f2#(i{%{{N z#p(vyhQDH<)f6e8DTBW@%(a{(L-$OKWR{~=*@jeO?r~X<<$4i9Xu71=D2 z8f(~*!Z{_&JDCvNRa-o9t_}lpcl7WuV{{OgM|Mv0$(_t2SCB9Pe;vt9bG0Dg&0!Q? z{U65V+sUD{zD@JOMclu29ILc6$cc0-%Xa>q1nPBODjD{D;b;ak zHt;F-V^7lYbr13No=NP}zGn94Js##YwvHCdwagxVj9y^_-}H?{#_;o0yEZBML!Fd@ zIkrX}m_i<0by|>Tr1ExM3lv@hxBk9%T@30-6slMBAHBTle{eH5Dat?<Sv0l4(SZu6FbnK|G1Sl4GgTFVelFv41MjGV-RpqyC?{|Om zw|C!#Fbg;_e>=EZB<`YM^;US}1crEukK+3PfD7cnd&q_Ue9#9sh~``DwuY}HhuZ~s zf4F$7_>TiQkkbu&l)@;?_g9#J41_`z)JJg4#f{^5J?c1J1bB}Rc#vaxmKQh#_&^g3 z#3wXC8i2xdtEx8mKn}b?L7)ODfbVc*c$7!#l*@&Oe_J_-Yx$ky`EYB4DKr6-BgPvv z0V()^2W(g~$kngN)y5e>M5I9upaNgCI1iA*I%fuy!#OR=d0g;#o%?v6d-|u#vk4f$ zC@?`2yum>9qg5xHDagQuSNfY@`etVOTU0rxUpc7bdaj$YCuo5-fC9Eb#IIM8KXmnJ zSbD6}fA4><2A$iwo$LCuLwmH7#jzv%%__TUYrO7qhz4d%XuaySF=9&H7qod%SPEy#su}t2n;rd+5G_e8V$0!52JTAG~HTyTbeNygPixYrNn>e|*Fr&%UEYzgzsjZ+yv{JjHgr$GZ*4 zn?=Hp{KB7n%**_Wro76R-^ANR#lQT;&wS7Od}7wT%`XeflZD9dJjwrj(kp#92EEV+ z3ek&&%O5?=FMZWpy#O}7)9(q?gN4pdJ;L}qfB*Y``vZiXdzbd=0Wg20SW1)@nKNnDw0RR}PMtgcdG_@A6KGJOLq(Nxc@$|< zrAwJMb@~))RH;*`R<(K+YgVmWO~M@d6>M0sW674)S(Iy9wQJe7b^8`>T)A`U*0pso`S$humnT}hg9#Tld>C@`k#7H+RmvwQ}jx zsaLmt9eehq%%6AnJ~_H}@#D#tH-8>|Z0_ILw;vlmeSG=z>DRY^e|t>#`}u=~j~~DQ z1sssT0?%8ozXTPm=|6u19efbN2qm1*F$5R9u)hW={@jqm4n6!3M3*eYkVN)03=zc? zRa}up6h|x(M(0j!k;WQryb(w7UW}2)-Db=Y$RLFrlE}|?>=DV!ek_v7CY^i|%DnVT zlFHtej1tQ%wcL_RkB+PoOxA$wlFTyAJQGcWz6_Ji_r^>U&NzSNoYTfN+q{#gH|gAy z&p!Pe&`vxB_36q#4LuamM28D>&_;7ARMAK!os?3@7x zO+6LWR7IR})K&@AQ`J~yot0LRM0Iu0Qfu9n*IsA66<0iU^_AFSjg1u8VA~w_*k+x5 z7R_Xp4U<`Dt-XI1+ajf%w#sU={TAGCU1XJ8ZvMR$7u|H#C2-qvkvwk) zm0sFnqmOpz8mkn_1>HBzWx3i@V9edoAAQ-gk6ygwx zSVSWpQCur%)-#@gL?;T#4)TzOvCQEHH<;szS-g=PPh`G%Ezns6pq% z(T;z56r?Hsm5M`03eh;3^rR?N=}eh|(UxiyO)!nAOljKFmWcGGBt4T(cgjbRx<+BstR>WSk0;PJ zl_6A3t5u=I*0#D8u6ZqpSLX`WC)xF`c-4O^T+!NBwKfT`ffejv%PLpH(v?VwRqSFJ z8&$qO)~`d7>|`lh*`5{_vxg1RW;xqg&#Kh1p@l4tNLyOdp4Ot5Rc&T()LPfR7PbNf z?QBI`Bir5W}Wm{a@t_ZowRqk@1xm)Mj7uY}<*6@Zo3`7Qh*uex6 z@rX%W;vQBQ#TO3Hido!Z7w6E!F$RAzfM{G}8{fEwC)RO_@6%%+{}{+Jgz=DNOdlg3 z8Ocd@VUC%+6)S(vjs1rD8Q&(C!saEx>S#3a0zuMEsk@c);U26fB z8rP>b&aQdgYhU}%)xm}}afn@PV;_5ew^nwpgVSthKO5Tj1NO9qjT>uU8{65QU$VKq zY}6ut4$|NQ56EC2&47R>cuo)#@Z2BG&;}^BF$z*x2eyIr)}fD{D&jxJmP zvp`J0&--8xBt&5p?!gm8K@>hN{Rjg37!Uv|@NJ0C{6b+M(C;2hfE4ntBleH~`p^H) zWE84EA4*~R;KBsbzy6sph!=5HXH0RWn? z8P<>#;*a>GunMDa4LxA{G~x=eFblOXN=(3Qcwh$a!V{Q4AKu^))xrVM5CSbFY%y|6AJm&uk1o1+Eth zg8ioN1iz390pR0*+As(KfD}3h0@~3W-3T2=v5(XdTS7q}RPiyIp$fP`9<3r4>v0w} z!V`{=2>VbA;79AtCZvOhF&CQ8PS&8+f1qt6~~CvKsHg zC0`OI5lJSG1r+q*8<&C<>hLHaAoZ|<(=K2SB;d>xK@nzu>x)7oD1~w;ox=VT-tQ>B zk|ZlaEE}RMp<*d9LM2Th8VTYoZ^Hy`F)q!>1gd~3nIbJQ;s`H78}h&aG_s{K;B9^> zZU&%{1|V+c$|`4zjkJ;>M^P30zzrWTsFe6RD6Li!eLID+~LeY?64*);{_`pHg^ldHy zI@JgOFaZGez>f9+Pc5{Ikl_;EMiHR%0*rww12qHqpo<#)GYxZSMLD`;nP@awFJ9W2iXt*5W)n1 z@bw_dHC%ZWAn)~Uy!H9wvJU&TU{v8QYAnFh_;I?R50y-h&dfpFfTwkr}k|O6aEuHIAsZi$a1z(h z6GD(&uX4OyrZq)n=R7P#~9s;*BJ5Y7kuX7F*AyBt? zgA{mM7a*0_Wmy&zNjG()_iYfDR~MID8&_T*cM=D;dq?sEr}rQdav;o?bZK-ffe0xv zVqQz~8;>vvftLxv(SD-3nNc}eovG{%&uuKs`je9sqw^$h8Sbq(V zj!|%k!4rrLvWNFrhy_w1^o?C@5on?xx~gpbXq$Z)Nz6h`EL}0VVD;k7HJD4q}-h_Y+a_ne~tj-7tO4(>+rZ1uwZ9 z#}bckktx+PM=KdT3z?jknVYrvcM;;5ar8=ucT^W*oY`5M3u2ys;rM*t*@s^=nv1mx z`}riFbp%I`1mRedXJ|<=!kl4KAhk6UH?f?_8FH`n9==!x>hGWxf*aD{DO}B!TX}6u zxQp6U8KiQAyXX?$6r}BFr9(NEU3!A^G^Raxq!2i8ZW^a^`ffmAZ~cIVtq1@DVQ=3E z5Rw^(Bbp(mS)$Ez=3ZH zh2z3*ewhiz;2icA5&(b&tO!I~b#KoB3XGbqBYI%znW-uHx5wIk0h?x}I*!59xDES! ziO>T^ZxTHgatk?&Q?$D0I{p?Lo9~*qr5iniF|EBDs;!%su z{2$fhs>3)m42_-{t}?;BAQ3rlE6gGN4mOs%_;$ zZk|zY4Fbiv5)uLdJGB$WcN--PT^^O)zZq6;e%8=$e9!~Z+2f{jHjqrG9nniLAG5VR z&2LMAaB|~({yIn8MKI6z{9PF^p{X5nZPZzR6a70)(3|I-6i8CpqrKTV7H(>FckNls z4T9OX9r_?3-h&t+)4Jdf{&$Oz-Zc>33mrWdUg80n*)RUtV;08^9d33mMn?f4LB1eH zL306=h-5$?Al<-0GM=}1EQb)U^Dl4sy`lk{&ode!vPL4NP8kFf*HODpFCb9!)M@2^ zri(<|=<#$=F<3(11`{;(57l;p=|+PG^XcV=(@!{s--ZWT9wCBVC{-bE4dK{j zyqPI6pra51P7qjsckN{!HQSz7M~@sAP?}NmBfW9^AYctOv2o$k?~gU`$(7~jF72xu zCGY;df%ST!Zxjsg@$V1u>4xUTH*Q3KuuMzSh^LTw<^Bm7AB$x*V$-^!4YKj+eDWh- zju&wB;btsFzgP14_nk!=*MOYN)2_pH?9|cW;B}u`5^jYHk zsY?d(fElv_Tb=m+HG&5Q{)Z`gQu@~xAmj;@2LQmpg9sA}+>;O)Lxm6{%Cm8=qQ#3C zGiuz(v7^V2ATdHY=cc5|lPFWFT*s5Xm@{kM%(*jXoRve1 zChMsnUW}yyoaXs9ZpFD5J&DrNEYxbzsc(6qZ6mVv>xl~%O#Z0dpmm2C3MyKLJ))fCV=sGN&%V9;_a{N4pC7z?M*BUNH-E0& zlqubz;YZ(q1QuxEfe0oToHlucqM%kX^Tr5=1BhA85QCCS&} zi72M1;)*P`=;Dho_5@0Q9!;TfD2HjT>E@!0vIi%gkVY!$q?A@_sZS<LZbflGOg_s;p-A=%uj6D(kGY)|w(HRpdd+R4{InYZ^9EXX>!TmWO7Xinglk zvM9Z3>$A{CEA6z@YE_3RE_`ANuPe%c3IL_}1Kgm+mTPWbh^ETyy6iez?Yr>CEAPCp zdcs5=E|3Bsf^~R*zzqPRXd~RH=N4@6K&YzA?!v3OOYg%FM=bHgEP7&>K27k4pcYKH zpie1U3@7HnBo}-vvZ^wy@{$fy?DESn$1F2t{qg|7Crk)DAOt>qTSXe;4o7auM3p~716Gw1=(PW#NFw#n=?PyL?x9#@Z za5t?*8gBspkOvQ@XrpyybCvSR&0y~>a@m9rdos#uC*B#;aX0Sx_pwAnj zc%X&gU^OEHDWbSwwV@`|PyWK4ml8Fo8-QmRBJP6Qh8l z1-C)f(To9q9;8tUpapT0gAaNmIJWB4k9juQuV;S~+qQS_{rBLB7akijTO5sFf%g&S3Hfe5vOuWVJ}4LoRnLJmmbgKcOdXevUT0%z!>qc!k> z4~(D%{&&d39{SK_)1#pfFLXWZ;ZSip{Gk$;$iybX3V}n6qJD{9Yw znRb(-9c`ygx3|-x8uh3aIjK)mTGOFIFr-M0s#Gf~)1}h1sZK2sRHw?-uFew-R<&xS zJbF-^diAVmwPaYWs*bS=)uv`mt6b+wS8bB@m~pMEUiZpZSl;!MdF`uU2TNEz{*{h_ zEv#Y}%h)C!_K1mXtYjxkS@1p9fsw6$tY$aMS-4&Hb(!s~Xh%z0mwon%TP>|>SIhp| zww+dKp>3^fXG>eRMl!aw&8=>CYo*sN47a=uu5gFjBHs=&wZu)Xa+g~rkFd$>i53@9xi{~R~2jc*H19acfHqQWd-S#k*xOQDIEu8naf$Pqnd*cPv^Qn*_u>4ziE~ z>SG`e`N&8%2m#?mM^JbEN}VCU@jzoy&UHLGMibA z<|Xr)*UV-oT35|&jI;ynBL&x!o3odHegLVI@4D;cz*7tLs= z{Z-M8j=}vn(TCL;sr$rB1b~Yl`ZBRlEAtLDed( zXH9FzjilAJ&b6+0&FfzK`q#h?wy=jy>|z`H*vL+{vX{;5W;^@Y(2lmWr%ml@Tl?DB z&bGF<&FyY```h3Sx46em?sA*^+~`iXy4TI_cDwuC@Q$~<=S}Z=+xy=5&bPkz&F_Bu z``-W$xWETa@PZrs;0RBDxWX6C@P<45;Si6w#3xSiid+2R7|*!IH_q{n{(Jo6AP>37 zM^5sRoBZS`Pr1rh&hnPK{N*r@xy)xy^P1cI<~Yx}&Ueo9p8NdgKo7dmhfegO8{NQ~ zSVW&mT8AK1AOx=H11(?bELx!xK<+yLcot& z%t98vh`Ze9PWQUU{R^#_11(x1s~C2*Tfe z9{BhS-kezYLkBd__|AKN@hOPK1|&cEn^(SBm&Yav_%VymyZ-fxm&GeW;P=w2{PZ~v zC<$o6dD#2@_jCV$LI+@Bdza&0C20rWJo?ZI;5+~MaZkZ3NKpKgAO1hlsgETrY61Q3 zcOMC^phxvde*GD1vktCkzW3J;{V&ks9peXi`CF3w$IXKuDPX_;ydp@J^c02IiB zNC+L?|!w zFb84ShQOzP0Y^}V`*wzyk%sS50dVJrdgyz7Acy!yhZwd;8?gR|gy?&7z=VD%Z-B^8 zU06Z%po4`NiJzwd448=SwuqZyhqi(OEhvee*mz%ng_hWEm?&XsbOa#yiK^Im8!(FN zR*H+$h~5(dN4Sc%Xm?5Ais%N5o}r1C;tIDIjEpyb2Q#RP+Qy3uCX2H(h{D*6b~gfK zXpG;6j0n|4{NRk(Sa-94h|;)i)F@8M=s2{HjpR6YNWhKVcx~TEVBrWi*I175=nJzD zjp-bJS#EkI>k0Vfw^{8z32p_&UC&0Lm`xp!VIBfyhU+wrbAz+XY*#QX&Z3`J9 z16d`1M*xu;$qy6RY!!)25`+S&sFC~F4;=}RXO}$}2_^LY(0e2ak#TsE#`clB)Q~R2 zhA*j+DKG>wNo+KkBrAC%eUOtcISV~0Y(M!XHVH8mK$JO|i%02eNqJtgGY=!_ln^P3 zQK@TF=}Jo}FR_4?Ik}TtnQL9yCPAqp1u2$)FF68bNo!~MU0+EpLBN(y*@|!JYHXFpxPbVM&=p z`2{?AnV6QDlC+ty0t=prl@ckMlSZ1)Wtx>z2QHbKIa!IUX=tvgEQdKC8!(x)36VyB zd7F!to6EAB#4!)P`I`{=2f|rs#CcpY^9ss|m9pTE{>^D-&$%wf2^{Y*onq+<*O_P8 zSw}C^0p3ZS))}5!HlDu)n{69sup7iNt_W54!nI=Q9 zpJHj5|5;`Mx-$4#8RKZ6PN@M28fFWBYDNP(CG?P-5NeRbH=*m9l35jq^0Jm2nv-tn zp;cC)YSW-+(TOGcl7fk%Nv5Kx1uq%-qE1N*G76v|8ltiIF8Yb1W9gqenqo8xS{E84 zSfHUo8js9bq(XM2Dw(7&(hp7Qln4o>JSL^u!=hi&nOB;V=%}SH#-*4=r7JRj4`J$* zCP}6=W~MoWrYSZUFRr#R%LS^=hYO8$~#il-c=r;sJ54?+)rN|aA3s3JzF zR>Y@Nftrc>lB>z6gW97$N|mu9os&wEvS6wHgh2)%10LW5`4b8`kg83vs-=*sp}+*B zU<#wK4Z;IMP~xb91gStVo1JQZkhTe`%VYxD-~psi3OukU-JuUIa6+iS1J1gtO&|)+ z+CSXz3B02n`tS*;&;-B(3Y^j+xO!WLS|Huot0Xy}!0Jm{-~mj)1EOO&+#m{}kOre5 z16tq@1{4x?;0ed$0j9tNO@IpTng?(2Jo}KZ2@pgX(+ zDxu&5ps*#Ru@9rb1fkPBIp6``Y9I>RN@@P*2_7H{8bJ!Auoay8um+g|5=%-3zyxog z4*>8617td*;SVy920Xw!RgeSQfUv}Ivbq#kCIA3o!5mJT5iaWyO}i5=Dzos20yRrX zCa?{qAUZrN1N+h&b+8YALaPt@zym%|vcjRXcx96^5Vc@&6+98QI)SKIyPR8lNT5&( zd62eiYam_wwE%z$qky!#;kMM&QcWSZUqQDz(YQKsqj|fJP`S5wh+VCih?p1pxsvz_K|K3LIMzp5VHo5H?nS;k0pU8~32HEDOB% z5VxWb7pbcqAVH^fZjykWZT)K5mS4?1!YqL3N@ z@D!N>030hDNC64};Iq5?6wMI6qCgb$TfX-I0pWWOklPV4yS(K{v(B4FQ2_wlFbYI^ zAp6h+08j;eQW@NTyHDeE6|Usk?4p#@)35gs770PwQ+!NE~$6jvd_j_VPv=fCk- zf&sim5Rd~qtGhAM45jeG4veXp3R|*x!2m!Pt?L?eYZUcsANVoCAzZ{3LBcw5l_-pj zKe)nKR~{#27Ju#2{?JMr_7t3=(*|#N~*f zPP|0TfC~CR3J1(0GVlqXAOmNS{>3ZxN_30C9v}mxtG{8qBLzXd8bQQqtjL5M5+uOJ z@R$H^yhJm?4LTDio-o5qP!@a)S5XML%)zo?EEfQv$M=vUyYjoNJH}<)$gb?l`@0bj z3CZMG3zPhRLq4DnIZ($j;sG?`xmeN3Lp4eBd%G3^zC3KkNMQn?V-e*0x*3rhOko%ncI1-jzUHvW9Wg<2KC!^+9wrxqQIyqpytEhiE!(IGLlyb;qEVbd_Z zvOl5I&dk#~fzwnW)ah0S+^{1*QMFnDwJi(M8==%p;js-Z6ri9F?pqZPjS{Nh2Y#Rm z5;aMG$LrL;YZ}Dym+;8X5s(Qz@UWrj75%KbOx+<+4HU3(4|P4fq96*U+|-dj5qjXh8haR&^;0081(uD_}k0MI8_ zvB9sbAe{{qf?N^HeGmRC>&2k}+sf_NTp`<-P2C+~-JIRZ*FDO@jom_#8WmyB=v!v#p&-_8T@V}W8l@rKzYEl@yThd+wf$Uw5fxm`a!bk2AGN2EW{Sozj5`h2!CjkI{(8mm$ z6}g!Ao*yZE^00~~^5x@?ypzzv^@LJ#?Qcv}N^&2w!p72Q@ z@Jm1NSE2MwU%&fs^%?;ItgH4>AI4K%5%nwXaly>g{k{1M_vGvE<^8*0&-96GzwzrJ zac}nbo4;e<_XDrZp)eKNo)O4y5yfo5vLVIF1wjtv zzt&~`<|_{X>Mslcpz^-ZJrp z#DyP6o?Q8FHBFp9haO$}bn4ZuU#I>?^>*$C$@u~wUi^4}^5xB+N1t9i_hA#lN9gX$ zpm;O@*nJOQKRh`A5x~#?4*>NBB(OjO!TX~##h$_mrqs3r%B80ukm(@?@1d}$iMqPy z!PSEDkfIAeGR3qKUmFpoPzdA8E71TT?Vb})bFe#zG;Ga64neeW#kss{W+$vx%t(f- zw%Rbs2$e*C%|?)_a?zy>A6n6a7VoL@$_P~>Wh$y{d@9S2d~6B;wOCUyO*PkKvrRYt z--I(xIV1ZlG?!L1!GwWO)N#oi8|?E(&Sa~sw&e~*G|}Gt0HC-)6ooX>Lpi~&QcEwz z6gus?;}pLGKLwRQ0LdFQyi&!14*&v86_C{cT@5vVR$0wU5Y7u-J1t5ULa1&LGDpqyyWWFKlKHLd(42v0qkMDkf2v&9uxWJkmj024gW0?$y++Sc1$ zyFKh#T?rBvrf3bCc3vS71J~G(?49!ibmP(?`oOC1wIpp3T0e}VDjsXA?kymEfjdP$tI5Qvg;gse#eDLRs{#ke4 zxzzOl5NAA}eVL%XRegc42UF2_wP~K81~r5@_06$jd?nn|BAT|nN9%0Sq&Q=UtcL9) zPkuT(*uV&a0LFJ~T5(!r9flaiiI-XoW3ah@BAZ5ll>$XAr95!CVx9=zE%Gm3M zZ_h20 zL7&Y#{tWSW0l;s6 zf>zl9Kp61&L&xdst7o4)Sf)M<*QiL5UGCNacpXQ7FFqEg`Rg~y|L!rnT>gDzAFn&$ zLfUqu!I&mv;;Koz7__=wxei{j8=(D&^|fsYkbqI~QUS~4Hm4A#SpAbA3NJ{%45B0| zp->0}I>#-(=ma4(1Q@tHNIMDPa3e>5a!N_mwLBsg(TGPx;+s6+0xd~KbJH5k!XR=J zyRfb$+kwCpS5iVxXz_{);bMmZqm>diu`;D#on<_RJT@W+5`l1rGXkNqHr5e1%1h#6 zd|(ioeS!(vKuHSPH^}nv2LMVVPY<{Q3`Mr;Kf?fqFMx40azr2kN@EApe9_2%?-&x1 zgajpcDgY-fn#~|vJmmnnA3krb?}3Iu-D#izt_mS0rm+Ddsv)zJRriMj0M6%#Xx ziV+hkFw~P27g9=$(1b!>`6WT3I2Tu1ag``3B^SNukud&}mj-dBnP>tSi6KTNUb;)g zO8LZWrgCw!Yo{)+2@+2{C=^V8S?5e&R28MLRCg_B?#Ol6kGd{vQkPBj;_Q>m+?d%wxO9d z+R>)FQ3U|}V2GRcG&gv(C`h7`hfo+GF@g*wA>%_HUXl|H2Yq!KetOWAhT8>Mkf ze1RcIlp+d_Y3m?koE%8>x{!~iRiq^4>y{3yQW33Yv3EVG0#|e{QFvf?kz?IpHpEhl zeDg+!X(?S{f?2dK^Dd-+s2GIgy0zI&4zU7#=|@=G*NN0tZk~t=iyTr}a0S)4$3<>( z(`1TIn9(xVLY75y@CMG*)Th_Qu1|rQTocHk4vW#iSH(MC@`{%P6)moE;{@H?sn@;l zg>QW2JKsFkfg8@!%qR*gLWzPxA0eb^@3K2!0{d>e^aTkL%DZ5H1~*t%LqIRU5S%9c zifzId#&Cu;ykQP|MH~7MsATSfk%F$xqFTrUS*k1G0=L-3{>g(`hhyPj8rRsyd+018uMsu3g+!&dJn8^G11P^koChJ<+&UVJKhLg}`KKFT$vRHGV1wCj& z7uwK4ommd%Y-A7Qp$X2N@|`99;yi131AfMIre`&aLwDNKp9XcPMSU4i z-Y^MnJ#A`_Cjr04cDA*>ZEjQ42YL8}k}<3e9sr;eLH>Rdvz@)}l|g&M^r&{cr`>>V z*W2Fr#&^Eg1c4j?;0?;2a5Gkoiv7+>-RwrVqTJ14q{`djeh#a?B|dSASA61X<;EzA z{ayo9r3y2DJ=nn$K62zNoWrwtc*^-a4~w_lfz#E#cDA=&xuD2F9`wNjA1Ka)g-~dMH`qG>u)ZAdfqf9bJUT!0qcZjDus?9W4V0BR zpuZ0U!4Paa6VL_^NP|+~0dCm6?QnrAc!M{8;DH!?4pE3fRCoh!_=FYYqbfj!CSU^n zDnI$EKnpA|4D7IX;J_sOlnoHUCUn9lR5}y>(1HW<03S#LCSZab(8A}?!W;+%CP)Jx zco+lFg36n(AtXZVGD02;FC}zC^*Apml*2is!#YGFG)%*rT0$3_MK4cEJRGq zM3&jaA)7Hz{6j1t#aD#ISZqR5JVlU6#UX0|R`kPIki}l~#b0baTC7EdTf|Sp0$qH= zegMX0WX5K6I$<2f<+#NnTSa8#Kv#5s#%$EaZOk`ljK<@b#vME$8_X1 zZ~VsJ2*)C$hv^~5o4PqeRN2yo{f;z@QYfAs|YX zqc^3b%Br+VuV6~hb4oJ<$f%6BfxODF6icPdO0e5XGeZHdls6_E%eHh&m@Lb{D@J=O zI<=fOSs=)_)XTjr$hbtfvqUq0B(%$0a|OOM%)>NDzjQXhOtU1A$HDY7yhO~&q)c>F z%vWQ~G@FOVgiJmI#mWTD(EP>AO#Z*Ql(-Gx%+97>qoCsa-~G|iTK zhtzyd@>0g?1kdoKzw7KN<|MR~?9K&aN%3UQ_GCWtv?KE@G?YtE^7?}1bI<(LPsW4K zXUoo*3j*$}&p~3#{WQ=6RlEND9r-*o^c+x7T2BO(&^5QT9m!7sb&W#WomC6d47zORLeR zVND$+(jpBt9+gH6ZM#|s(w+X11rIgSD3ww|L(-B{(zc@qj%?CUIf5zm(l1rBD!sBR zbvqP5(Jd{I&jiyoWm6s#Q{V7WULyekMN{+Ohc?C2Jms)A?ME_yT|8Q=(*Y??Jtfpa zJwrsavN-)U2MyHnFh@g$)JRn>J{3DZO+6rORPq>syp+^V^;9F`Q$^LbKFHMZm<4+H z)Kpc~J4Dpwv{cp`%2M?OB*4a1wbfe{9V8XiZX?x9Jyl!<)?h^#7~R!+!&Eel1ymK* zWo1@_YgIjb)#uB9MKqlQPHom}#n#N|&tknCpjcCC67#R+EKU zeU-_SjX4w;0iu)ze2v+ibytj}*`@1;GquK7cvzk_T5{dTpJlos2w7xog)TkXsO{N= zwNhuS00m>k6v$Yqs;oWhYg@yTT6i?SX|SU-Cz~D)P24n;0N2p zv=r!ooSfa?b=+39U3BdO`s6YVNCMvl-sL@AL>%6K55$5Hcm>@puN0Vr4ftE;)m~w> zuIFvSc?bbom<7I_l~(A02FTv+RbTG?81J3KcL;$#=zv+c*jPycB$xwQ_yZEK*7fyY zTh)XjVB4OogCMAY5NLotpoJvpfDWjIIhcb6euX692Uqxm3LpW}{of7d;12fS4+h~7 z7U2q<7C1&C#cH$?7;wYBlDW>8ow&E+s;w;wUE#~4b_Tn!F<1iNEF(%_O zHsdo!<1|*|HD=>BcH=jO<2aV%Ii}+}w&Od0#^XHJ<2~l%KKA232IN2% zM&v|RnzKvb>4)(vNd=4y6PY2FQLw&rb~P;BN6ZRX~1&QEXd4R99cbFNNu_6>7J=XTyr zb^Z-@cIW6Gq8lSUMjR_T`(MV3w! zmxk$@Hbj|L6q>f_ou)&ah7_IV>7W)tpNa2D;s}>oo*6Oc*Ij&wAuLkR~mN>Cy8L~F(weB{wZW*;^>$vtcxAq->xR&d? z4mG;|9lOTszAiMqJ|4dI>%mSlz(yXyChWv6GQ(yg#8&Lb-Y~{)Bgcm9%BC;Lt|Q8} z?9EpGFU+4j9|M zF5K4b-)=I-wH z?(YWg@D}g!Chzh#@AF3Q^v>>XknW?U?gF#!3-bo`rtkW;@B7B@{MK*nwt)8L+4pX- z_(m`&n1uZ{@B>Hi1Xu9xwgms)Spau20jIA6SnvwB@C(QA@3sO5k68$Re=!N4FDuCK z5hw8y$L~oPU=F`l52rE^k1ql@@fer!8Tal6SMiNy@hWq1_@Zzc_wgSe@eRLmjm>c@ z{@d~Q5^*3`@+CKL6d&@4Epj_Ma`l37Cb#k{Pj4H4a)XWXJDYOl>TxU=^D*~sA>Z~D3UfUtbQ(AFK6lqY zhqXWtsx~k5MH^S^in_YL-+J-1@(0k z^@(zHQ-}5Ymh@D|)>UV>R?nkMkM&*8a$5iNh5T$@_BB%H^z2c3`!3pu=_}B6e*JQ{rTvumellND(fQc!%5F#R+!`o<^oq=$Su#JeWgy*Ee$ZOC|pX@*g- z{i!pByI-Dv(YHXjRtm9_u*Eq3G(mgV7xT5BeJI?AxYGww7zN?aqwaz{GqIA=A$}qx zYZG7=HVHGt5dY-A490hUD}Vgx7r~t427mJbGpsKe5Cwh6119J^gZch6^y(1<2mk^F zn!0z-lt2hFMBN(&AXGDh20bAZ1xg~udjjvtcGEYu zD@$h5tZDNm&YU`T^6csJC(xikCqaQ}^eED#N|!Qi>hvkps8Xj=t!nlDD%Px8w{q?3 z^()x0V#ks#YZfFa7kP4MIV+apCw)z{xpi624c@$Z_ww!Q_b=eUf(H{WZ1`|rV&<-H zytnUvAx2T22JX9OA{2m#AVqn&2MR#jmI5m`gDh&}X#kZWK-S2zE0jq>XVb22`!?>K zmg@5E?fWf_fn3UiaP3!T>V!ks;T;DprNh0`YNok$~xcHUt1E7*d2Ee>z&b?5JS%s4BPJ&Nd{i z;fgyhx#gOBu0ba7kcYFeLh!~Xg?a@nvGv;frLjz%HmyOAvHrMhXU!=K(n{&IG#LON znIMoS2sbPch6^VwW5V)z6`QviYpff(9eeyS$RUf2Tpc`o656aLG~oiuThV)eFU&EY zxY$$hkus$yp9-YKX`?J^5CkfP;;Whi)mSi%G}=66SEp|L^v0}?JT=u-TYWXwFE#PW zr?iUl3DH=+EHl|J5tI#R4J zrFvHTU>1trZn>V)-%Z_qH}AAUkjFMsEu0l*n@R+`OT+#HJ^SKQdE&yqSB-u6c&hl} zhbq)_neXcYF$ur(j2pwbbocn~Qov`m&vKp+1_WfU%8npCEzJ@6>O56*B#ARw_r6LBvo4vF5fTF9V% zFdTxeOk1ow>FoY zO`T_WmQ2nhsd6)e43jmPf=}(563nms5+lo8N;8x9$ZWmjnbBOzGocyDs+giv88HP8 z8ONAyCbFD)N&`2hq7tOZ2O;TM$g?1_x{54rBOUP&NHWMblb~dsJ8F$E(@B+0mhv!4 z0KhTu!U6yYv7jP1)sY&M~HRTWuMkukT{zoK25dc`^VDNN^Jk#Y6Xnn*J3%SgF zOt92p?(-oSw5m|)nUXaPwWv;AV<-$Lzk$$4J(F=`J_m+>kWozLfAVW*LXP^7Pj$6< z838Ij`Bv4FY}IHW(Q3Nrld~OdFg}H4i8~>XL4UFYuQ+6^d+zz6x*klQ8RTbQ8$;Qz zLKSJ2nd*mRC=eu4)+MYVWOL?8)OxZ+vT2owXaVpqztXlor8OsRne+w`Hl;C-@uoo} zLmA6pMl+s&;EZQL;~LVK1~sF(2Wz}ps)f-oD%!}5CLKDWKmb550Pur$ABr|wNwi=S zeS;4?0mUDU6dE+q!!2lX0tH+V4giR+K>il61T&~~2@2Sv0@$|$%bg&L8?|K?=Ud@1X%|?9AEuxX64?M^vSsH&0#S<;^3{f{l^;uDiUc}4S%*bdTANaL0A__)QB@bwS zU5XP_J)TqNh37g4&njLm^q~{I=te&}(&aZ%qR(junOi_dXY$|j8Jnh3$EEp_eeA23 z4@f&zkh8(u^00#$XzmnvjRPM3qp>%(K5QRjOL%+`m7g2TnDKzP?|O3Crkt9MdHb4P zzU`QMm_|1)J4Q-VkY?P9Bbicr1TnpTcF*b^=Gg+g@d$f%in-+NFA0UR_CZL@&zt3Y z6ujq0uKEW7Ab3+5)51ZACc>8;M+Y~a2W~DV*FOac=lD5z{E%wA03Z;+2TuvMq5S1D zzxmF8KJ=q6{pnM``qtk@6PEt!H;7?Qw*Y`icQB2t-=yj?-Fo@8ZuRrm$-&KkMxfwf zGJA=PVz9Jti2W^3D)>LU?n#v2G~EbrF-|k2MkbvbnaJMC30&aap8iQ5;&~96=oy*d zQ>NA4>Gj{6{8;>v9pcqr{%PEwz`X=~T*T1$pWsEF1H#?vwP1r(!2v!+rF>BujZtd| zMAxL?3L1nK@K#imhP?FN?;Rh1Vn6}_umE0oQbQ5pVU)wTxY|lg6e^_GJ-8B(INdG8 zQr4M)O_*O6{a1W-q584k`ElW#FdNwMU+HyC<(-KE{|W&0H(|$B#Ss%+_OZ>Uk%>=X<{z& z-~iT;3*?Ga_#P7)h9`lN8$A91LKUN7IN=j&Uq#8CL}ZkYd|ye~ge~xzdx;bol3^BQ z;|{pjG-#n0+EO+?!ZrrLk9^&Ne|TVHI0P>8OD%@lF4|zsN!~j`i?kHPdBEd7vYN=Q7>vAW#G=5*x)}s5mYps7V=tcKJ?@}HP7A*wo->V>We5ZqRboQw;w}DT z8^+&7@?++$AW~rn1bEX)8U=h56-KEau@&r_`_7}e~ZNg62SD> zzzkVJI7~+-jKd&I#DtGfa@k6Vnp2>{FG2+|GUH%CLLmH55NRc0IODja(nPJ2D6SWQ zq|dC$SHVm}fdK$5L4!d4oWNVM)YPez8Kw^xN|8#{B#57gG zgpF$i3@HrEX+S^-KFp>>f97O5CBaOLQX-|n2xVpt1OyP;ON@eMkmgKM*=5|EQbG$# z)L7?LOhEEoQ3}jqt^{K$WnwlZ#4P1UcqV7ICQ+fpXMCANAVg@WgmFP-#o%W6$lP)w zX88POM(F0k@g~Jg%WsM%58|MXNoI7;NK#3sQIN@WE+%s_<#!H5e^M0}ka_&|p>44BgSJ2U8W#O0c3*T-TjBMOK0(V5q`(83Ka}hFFe^ zLoh)QzJwK4V@%n^{xm#-N{!$7j8#Z^=p&fn5=_H?*&2NTrY&{ohyDl}z8?quXJho| z2__kv`OJGTRkjt@f50qFkJ4z{)u_-w8dCvHk=95oI^?2ISw@(IT@}qnR1CGb#C>L2 zeDCTWsQDS#@?kwO=j-lRuxMmVj78eR=`hLZ2@ZtE z6%3o|;f?Acw0w$NSWSdVkb-tVz-eTOnXbv9f66DN5(g;cL4raB=#1ck zyunot;}AxusCvhQUJW%q4yyJ9JG#m>iOZ$}WTnQcth!(+yg}Ygh2n5xgD}A--0D;; z=%@zkV3aD>p#Ej59;;0dW~;Pnxa6w;%__7;Yi@Z$74U#CO2s-EkYP565747l0IRTu zD_;;R)?_HMf1c}1#AvH*Ysh44d8V7R#;d#{QZu+f6I?4)98s5&Q*wBKKImRmfUCHQ zE4gOPsiJGDt}3hgmB{ETo$~9vMy$k+j5fFcDx7JnRVi$Q>cEC;!OD%XBCOje>%^9< z$(}6A8bvcyK^0u6p?E?Q2`g}`qJO@>RMa`0}&Y3OU=xo|@OV7To;0`X~Ud@K+16$w;MYwV9=O1r_D1ANE+)MUqzHX2NZtX5F^EU62XaN(r z0IadqgVX^iH{i}0w@44 ze{x(f1`CS@_m22>ux*s@2oEt47qM4pfe*;=L|JSOn8G$_0S;3~Gk}5*fL;}-k|?CX z4MT_y@9?Aaa0&G<5DNtW8?hOmF&amO31~wV^S~7ggbQ#3599zTn8GM*11M-iGlXWt zz*fWfLo;ZB0my&{_&^$f9=r?$6Ptnzf7tJWbg>tA2^g0U2amB(d~h0PvLUm+ zv;{{qKZmotk~2Ur!8sc=L`Sql`^G*ebYH-8Zpm{w*YiYov`2sR(pt25U^Jrrvn2~O zNT;+)ue5?9v`9;6$ue{LH+SdaBKZ}sqY_39;c+Wsyz zS+})YXR=xAEn2T0RsXG3zcpUxH4(?P&CYcTe)Z0VwO$W4VGl506Kvb6e>J+YbzwKQ zV>9nz2diJVVqGKbT|YKvXEx_Xc7#s0D*`sM3btmCHfg^tXKSTr@1$a*Dr1+nYri(o zo_5(5Ze^$HWy3aZ=XOlHwCbjIe1i7dh_-GIH*rg=Z0~L7uJ+Tkc5yeibI0d#hnH_t zYHdT!Z9g}5XE$B*_P|bef29KV)M~eQkN2!b_v$XMa(4@JlQ(;}_uZKH_d<4@?Cf>B zw|(DtxpH@BJ1=;vig4pMfCu=(qsRIEaT#h7-$%&ntoh$$E#limy1HjCin=_`FK^f3mSSjn_Da>vyTa zc*J)2s@S-X|2Uwy_%x63i3>=I138i>If4i|m+1J!$~b{gxROsfl^+L_*K!f_IDq_j zm3O(93&)jAFVe>s|`d0B*cc#QeULV0dTxth;8oriRer?HmT2A9`4pZ9rI zu=zV{a+#B{g8R9le;@jq<2ZzCGMv+foFh7jkJ|?WONTroTF_!@8nF%B-{gt*GM!spq<| z_c^NfI@_+gIS;$CFMCe!x+JS}t{mb1IR7yQ4& zdlO1Dg44UfH++c8cEE2gl=Hj8SNw-_cEococoV$Ecl^a8{HKGoVl%wQm;8dqb;iqX zZBx9-w|szae|5^w?pJfX%h&wNgM41ZJnjB7$=kfow|7PBJnhOf%m2L5lXp7{eeL2h z%^$tfBfYaLJ$my*&p*A@KX(^P{qk~f(Oy7ZUQ>eQZ$9UDzUO~F=!d@Ok3Q*_zUiMn>Z5T$002Ah+G%9~ delta 68017 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

la@#D2FnQJ@Qm+a=hlnA1bmauQDqq4Jn&)ib^tM zP;x8RvMqNDA;YqYcCusq@h$%{Ff;y#8|!l8qB8KQaxf>eGQ))v6Z4A7@-Qv4G*7dU zbg(nCC@+8ig)dVxH+OUFc`EH1Gj$=eHh^P>LNhH7v_gAxKZZuLG)=cMz`iu8X0J%s zG*7cKx#D!Gj;>1gG*O2#vI2FfI_^vtHB(nIry{kfO6^WJHB~oqkwW#UM(j{mHCGGr ze`58i-s@6#HCfy7aDuh4g6dRlmo;10@nWL&u#)Fiw>4d>@l?XK`o{EF*EL^Xu}0$c z{q{}8TK6?!TQN8ScCq&1To*QDFR>;fcCr56UN<&n2eEHKwy|nRI8-)ggEACoLhn*S fXOA{%m$qr2HfpD~YOgkHx3+7)Hf)=AKmY(c9*D{! diff --git a/src/Visitor/Edge/ClassMethodLevel.php b/src/Visitor/Edge/ClassMethodLevel.php index ff3200d..fb8747d 100644 --- a/src/Visitor/Edge/ClassMethodLevel.php +++ b/src/Visitor/Edge/ClassMethodLevel.php @@ -6,180 +6,20 @@ namespace Trismegiste\Mondrian\Visitor\Edge; -use PhpParser\Node; - /** - * ClassMethodLevel is ... + * ClassMethodLevel is a visitor for method in a class */ class ClassMethodLevel extends MethodLevelHelper { - private $fileState; - private $currentFqcn; - private $currentMethodNode; - - public function enter(Node $node) - { - $this->currentFqcn = $this->context->getState('file')->getNamespacedName($this->context->getNodeFor('class')); - $this->currentMethodNode = $this->context->getNodeFor($this->getName()); - $this->fileState = $this->context->getState('file'); - - switch ($node->getType()) { - - case 'Expr_MethodCall' : - $this->enterMethodCall($node); - break; - - case 'Expr_New': - $this->enterNewInstance($node); - break; - - case 'Expr_StaticCall': - $this->enterStaticCall($node); - break; - } - } - public function getName() { return 'class-method'; } - /** - * Links the current implementation vertex to all methods with the same - * name. Filters on some obvious cases. - * - * @param Node\Expr\MethodCall $node - */ - protected function enterMethodCall(Node\Expr\MethodCall $node) - { - if (is_string($node->name)) { - $this->enterNonDynamicMethodCall($node); - } - } - - /** - * Process of simple call of a method - * Sample: $obj->getThing($arg); - * Do not process : call_user_func(array($obj, 'getThing'), $arg); - * Do not process : $reflectionMethod->invoke($obj, 'getThing', $arg); - * - * @param Node\Expr\MethodCall $node - */ - protected function enterNonDynamicMethodCall(Node\Expr\MethodCall $node) - { - $method = $node->name; - $candidate = null; - // skipping some obvious calls : - if (($node->var->getType() == 'Expr_Variable') && (is_string($node->var->name))) { - // searching a candidate for $called::$method - // I think there is a chain of responsibility beneath that : - $candidate = $this->getCalledMethodVertexOn($node->var->name, $method); - } - // fallback : link to every methods with the same name : - if (is_null($candidate)) { - $candidate = $this->getGraphContext() - ->findAllMethodSameName($method); - if (count($candidate)) { - // store the fallback for futher report - foreach ($candidate as $called) { - $this->getGraphContext() - ->logFallbackCall($this->currentFqcn, $this->currentMethodNode->name, $called->getName()); - } - } - } - $impl = $this->findVertex('impl', $this->currentFqcn . '::' . $this->currentMethodNode->name); - // fallback or not, we exclude calls from annotations - $exclude = $this->getGraphContext() - ->getExcludedCall($this->currentFqcn, $this->currentMethodNode->name); - foreach ($candidate as $methodVertex) { - if (!in_array($methodVertex->getName(), $exclude)) { - $this->getGraph()->addEdge($impl, $methodVertex); - } - } - } - - /** - * Try to find a signature to link with the method to call and the object against to - * - * @param string $called - * @param string $method - * @return null|array null if cannot determine vertex or an array of vertices (can be empty if no call must be made) - */ - protected function getCalledMethodVertexOn($called, $method) - { - // skipping $this : - if ($called == 'this') { - return array(); // nothing to call - } - - // checking if the called is a method param - $idx = false; - foreach ($this->currentMethodNode->params as $k => $paramSign) { - if ($paramSign->name == $called) { - $idx = $k; - break; - } - } - if (false !== $idx) { - $param = $this->currentMethodNode->params[$idx]; - // is it a typed param ? - if ($param->type instanceof \PHPParser_Node_Name) { - $paramType = (string) $this->fileState->resolveClassName($param->type); - // we check if it is an outer class or not : is it known ? - if (!is_null($cls = $this->findMethodInInheritanceTree($paramType, $method))) { - if (!is_null($signature = $this->findVertex('method', "$cls::$method"))) { - return array($signature); - } - } - } - } - - return null; // can't see shit captain - } - - /** - * Check if the class exists before searching for the - * declaring class of the method, because class could be unknown, outside - * or code could be bugged - */ - protected function findMethodInInheritanceTree($cls, $method) - { - if ($this->context->getReflectionContext()->hasDeclaringClass($cls)) { - return $this->context->getReflectionContext()->findMethodInInheritanceTree($cls, $method); - } - - return null; - } - - /** - * Visits a "new" statement node - * - * Add an edge from current implementation to the class which a new instance - * is created - * - * @param \PHPParser_Node_Expr_New $node - */ - protected function enterNewInstance(Node\Expr\New_ $node) - { - if ($node->class instanceof Node\Name) { - $classVertex = $this->findVertex('class', (string) $this->fileState->resolveClassName($node->class)); - if (!is_null($classVertex)) { - $impl = $this->findVertex('impl', $this->currentFqcn . '::' . $this->currentMethodNode->name); - $this->getGraph()->addEdge($impl, $classVertex); - } - } - } - - protected function enterStaticCall(Node\Expr\StaticCall $node) + protected function getParentName() { - if (($node->class instanceof Node\Name) && is_string($node->name)) { - $impl = $this->findVertex('impl', $this->currentFqcn . '::' . $this->currentMethodNode->name); - $target = $this->findVertex('method', (string) $this->fileState->resolveClassName($node->class) . '::' . $node->name); - if (!is_null($target)) { - $this->getGraph()->addEdge($impl, $target); - } - } + return 'class'; } } \ No newline at end of file diff --git a/src/Visitor/Edge/Collector.php b/src/Visitor/Edge/Collector.php index b29d241..251735b 100644 --- a/src/Visitor/Edge/Collector.php +++ b/src/Visitor/Edge/Collector.php @@ -25,7 +25,8 @@ public function __construct(ReflectionContext $ref, GraphContext $grf, Graph $g) new ClassLevel(), new InterfaceLevel(), new TraitLevel(), - new ClassMethodLevel() + new ClassMethodLevel(), + new TraitMethodLevel() ]; parent::__construct($visitor, $ref, $grf, $g); diff --git a/src/Visitor/Edge/MethodLevelHelper.php b/src/Visitor/Edge/MethodLevelHelper.php index d6acae5..75244d8 100644 --- a/src/Visitor/Edge/MethodLevelHelper.php +++ b/src/Visitor/Edge/MethodLevelHelper.php @@ -7,11 +7,178 @@ namespace Trismegiste\Mondrian\Visitor\Edge; use Trismegiste\Mondrian\Visitor\State\AbstractState; +use PhpParser\Node; /** * MethodLevelHelper is ... */ abstract class MethodLevelHelper extends AbstractState { - + + protected $fileState; + protected $currentFqcn; + protected $currentMethodNode; + + public function enter(Node $node) + { + $this->currentFqcn = $this->context + ->getState('file') + ->getNamespacedName($this->context->getNodeFor($this->getParentName())); + $this->currentMethodNode = $this->context->getNodeFor($this->getName()); + $this->fileState = $this->context->getState('file'); + + switch ($node->getType()) { + + case 'Expr_MethodCall' : + $this->enterMethodCall($node); + break; + + case 'Expr_New': + $this->enterNewInstance($node); + break; + + case 'Expr_StaticCall': + $this->enterStaticCall($node); + break; + } + } + + /** + * Links the current implementation vertex to all methods with the same + * name. Filters on some obvious cases. + * + * @param Node\Expr\MethodCall $node + */ + protected function enterMethodCall(Node\Expr\MethodCall $node) + { + if (is_string($node->name)) { + $this->enterNonDynamicMethodCall($node); + } + } + + /** + * Process of simple call of a method + * Sample: $obj->getThing($arg); + * Do not process : call_user_func(array($obj, 'getThing'), $arg); + * Do not process : $reflectionMethod->invoke($obj, 'getThing', $arg); + * + * @param Node\Expr\MethodCall $node + */ + protected function enterNonDynamicMethodCall(Node\Expr\MethodCall $node) + { + $method = $node->name; + $candidate = null; + // skipping some obvious calls : + if (($node->var->getType() == 'Expr_Variable') && (is_string($node->var->name))) { + // searching a candidate for $called::$method + // I think there is a chain of responsibility beneath that : + $candidate = $this->getCalledMethodVertexOn($node->var->name, $method); + } + // fallback : link to every methods with the same name : + if (is_null($candidate)) { + $candidate = $this->getGraphContext() + ->findAllMethodSameName($method); + if (count($candidate)) { + // store the fallback for futher report + foreach ($candidate as $called) { + $this->getGraphContext() + ->logFallbackCall($this->currentFqcn, $this->currentMethodNode->name, $called->getName()); + } + } + } + $impl = $this->findVertex('impl', $this->currentFqcn . '::' . $this->currentMethodNode->name); + // fallback or not, we exclude calls from annotations + $exclude = $this->getGraphContext() + ->getExcludedCall($this->currentFqcn, $this->currentMethodNode->name); + foreach ($candidate as $methodVertex) { + if (!in_array($methodVertex->getName(), $exclude)) { + $this->getGraph()->addEdge($impl, $methodVertex); + } + } + } + + /** + * Try to find a signature to link with the method to call and the object against to + * + * @param string $called + * @param string $method + * @return null|array null if cannot determine vertex or an array of vertices (can be empty if no call must be made) + */ + protected function getCalledMethodVertexOn($called, $method) + { + // skipping $this : + if ($called == 'this') { + return array(); // nothing to call + } + + // checking if the called is a method param + $idx = false; + foreach ($this->currentMethodNode->params as $k => $paramSign) { + if ($paramSign->name == $called) { + $idx = $k; + break; + } + } + if (false !== $idx) { + $param = $this->currentMethodNode->params[$idx]; + // is it a typed param ? + if ($param->type instanceof \PHPParser_Node_Name) { + $paramType = (string) $this->fileState->resolveClassName($param->type); + // we check if it is an outer class or not : is it known ? + if (!is_null($cls = $this->findMethodInInheritanceTree($paramType, $method))) { + if (!is_null($signature = $this->findVertex('method', "$cls::$method"))) { + return array($signature); + } + } + } + } + + return null; // can't see shit captain + } + + /** + * Check if the class exists before searching for the + * declaring class of the method, because class could be unknown, outside + * or code could be bugged + */ + protected function findMethodInInheritanceTree($cls, $method) + { + if ($this->context->getReflectionContext()->hasDeclaringClass($cls)) { + return $this->context->getReflectionContext()->findMethodInInheritanceTree($cls, $method); + } + + return null; + } + + /** + * Visits a "new" statement node + * + * Add an edge from current implementation to the class which a new instance + * is created + * + * @param \PHPParser_Node_Expr_New $node + */ + protected function enterNewInstance(Node\Expr\New_ $node) + { + if ($node->class instanceof Node\Name) { + $classVertex = $this->findVertex('class', (string) $this->fileState->resolveClassName($node->class)); + if (!is_null($classVertex)) { + $impl = $this->findVertex('impl', $this->currentFqcn . '::' . $this->currentMethodNode->name); + $this->getGraph()->addEdge($impl, $classVertex); + } + } + } + + protected function enterStaticCall(Node\Expr\StaticCall $node) + { + if (($node->class instanceof Node\Name) && is_string($node->name)) { + $impl = $this->findVertex('impl', $this->currentFqcn . '::' . $this->currentMethodNode->name); + $target = $this->findVertex('method', (string) $this->fileState->resolveClassName($node->class) . '::' . $node->name); + if (!is_null($target)) { + $this->getGraph()->addEdge($impl, $target); + } + } + } + + abstract protected function getParentName(); } \ No newline at end of file diff --git a/src/Visitor/Edge/TraitLevel.php b/src/Visitor/Edge/TraitLevel.php index 2ba9fe0..807fcb9 100644 --- a/src/Visitor/Edge/TraitLevel.php +++ b/src/Visitor/Edge/TraitLevel.php @@ -20,7 +20,7 @@ public function enter(Node $node) case 'Stmt_ClassMethod': if ($node->isPublic()) { - // $this->context->pushState('trait-method', $node); + $this->context->pushState('trait-method', $node); $this->enterPublicMethod($node); } break; diff --git a/src/Visitor/Edge/TraitMethodLevel.php b/src/Visitor/Edge/TraitMethodLevel.php new file mode 100644 index 0000000..8e0340f --- /dev/null +++ b/src/Visitor/Edge/TraitMethodLevel.php @@ -0,0 +1,25 @@ +simple(); + $obj->calling(); } public function staticCall() { - Concrete::simple(); + TraitHelper::simple(); } public function newInstance() { - new OneClass(); + new TraitDocument(); } -} \ No newline at end of file +} + +class TraitConfig { + public function calling() {} +} + +class TraitHelper { + static public function simple() {} +} + +class TraitDocument {} \ No newline at end of file diff --git a/tests/Transform/ParseAndGraphTest.php b/tests/Transform/ParseAndGraphTest.php index 7e2e374..6283029 100644 --- a/tests/Transform/ParseAndGraphTest.php +++ b/tests/Transform/ParseAndGraphTest.php @@ -385,43 +385,40 @@ public function testTraitUsingTrait() , $result); } - public function testInternalForTrait_Isolated() + public function testInternalForTrait() { $result = $this->callParse('TraitInternals.php'); - $this->assertCount(5, $result->getVertexSet()); - - $fqcn = 'Project\TraitInternals'; - $this->assertEdges([ - [['Trait', $fqcn], ['Impl', $fqcn . '::nonDynCall']], - [['Impl', $fqcn . '::nonDynCall'], ['Trait', $fqcn]], - [['Trait', $fqcn], ['Impl', $fqcn . '::staticCall']], - [['Impl', $fqcn . '::staticCall'], ['Trait', $fqcn]], - [['Trait', $fqcn], ['Impl', $fqcn . '::newInstance']], - [['Impl', $fqcn . '::newInstance'], ['Trait', $fqcn]], - [['Impl', $fqcn . '::nonDynCall'], ['Param', $fqcn . '::nonDynCall/0']] - ], $result); - } - - public function testInternalForTrait_Calling() - { - $result = $this->callParse('TraitInternals.php', 'Concrete.php'); - $this->assertCount(8, $result->getVertexSet()); + $this->assertCount(5 + 2 * 3 + 1, $result->getVertexSet()); $fqcn = 'Project\TraitInternals'; - $ext = 'Project\Concrete'; + $call = 'Project\TraitConfig'; + $helper = 'Project\TraitHelper'; + $instance = 'Project\TraitDocument'; $this->assertEdges([ + // the trait [['Trait', $fqcn], ['Impl', $fqcn . '::nonDynCall']], [['Impl', $fqcn . '::nonDynCall'], ['Trait', $fqcn]], [['Trait', $fqcn], ['Impl', $fqcn . '::staticCall']], [['Impl', $fqcn . '::staticCall'], ['Trait', $fqcn]], [['Trait', $fqcn], ['Impl', $fqcn . '::newInstance']], [['Impl', $fqcn . '::newInstance'], ['Trait', $fqcn]], + // the param in the trait [['Impl', $fqcn . '::nonDynCall'], ['Param', $fqcn . '::nonDynCall/0']], - [['Param', $fqcn . '::nonDynCall/0'], ['Class', $ext]], - [['Class', $ext], ['Method', "$ext::simple"]], - [['Method', "$ext::simple"], ['Impl', "$ext::simple"]], - [['Impl', "$ext::simple"], ['Class', $ext]], - [['Impl', $fqcn . '::nonDynCall'], ['Method', "$ext::simple"]], // @todo fail + [['Param', $fqcn . '::nonDynCall/0'], ['Class', $call]], + // the called class + [['Class', $call], ['Method', "$call::calling"]], + [['Method', "$call::calling"], ['Impl', "$call::calling"]], + [['Impl', "$call::calling"], ['Class', $call]], + // the helper class + [['Class', $helper], ['Method', "$helper::simple"]], + [['Method', "$helper::simple"], ['Impl', "$helper::simple"]], + [['Impl', "$helper::simple"], ['Class', $helper]], + // the edge between for the non-static method call + [['Impl', $fqcn . '::nonDynCall'], ['Method', "$call::calling"]], + // the edge for static call + [['Impl', $fqcn . '::staticCall'], ['Method', "$helper::simple"]], + // the edge for instantiation + [['Impl', $fqcn . '::newInstance'], ['Class', $instance]] ], $result); } From 4d10a853642eea8619f42cbaf3cb4e7a9d832039 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Tue, 16 Sep 2014 13:45:07 +0200 Subject: [PATCH 36/36] fix a bug in hidden coupling since trait can now own an implementation --- src/Analysis/HiddenCoupling.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Analysis/HiddenCoupling.php b/src/Analysis/HiddenCoupling.php index 8d2fee6..487d43d 100644 --- a/src/Analysis/HiddenCoupling.php +++ b/src/Analysis/HiddenCoupling.php @@ -11,7 +11,7 @@ use Trismegiste\Mondrian\Transform\Vertex\MethodVertex; use Trismegiste\Mondrian\Transform\Vertex\ClassVertex; use Trismegiste\Mondrian\Transform\Vertex\InterfaceVertex; -use Trismegiste\Mondrian\Graph\Edge; +use Trismegiste\Mondrian\Transform\Vertex\TraitVertex; /** * HiddenCoupling is an analyser which checks and finds hidden coupling @@ -62,8 +62,8 @@ public function createReducedGraph() $reducedGraph = new \Trismegiste\Mondrian\Graph\Digraph(); $dependency = $this->getEdgeSet(); foreach ($dependency as $edge) { - if (($edge->getSource() instanceof ImplVertex) - && ($edge->getTarget() instanceof MethodVertex)) { + if (($edge->getSource() instanceof ImplVertex) && + ($edge->getTarget() instanceof MethodVertex)) { $this->resetVisited(); $edge->visited = true; @@ -88,8 +88,8 @@ protected function findOwningClassVertex(ImplVertex $impl) { list($className, $methodName) = explode('::', $impl->getName()); foreach ($this->graph->getSuccessor($impl) as $succ) { - if (($succ instanceof ClassVertex) - && ($succ->getName() == $className)) { + if ((($succ instanceof ClassVertex) || ($succ instanceof TraitVertex)) && + ($succ->getName() == $className)) { return $succ; }