Skip to content

Commit

Permalink
SVC Release 5.0.0 (#32)
Browse files Browse the repository at this point in the history
* MC-30776:  Improve Performance of SVC (#28)

- Added nikic/php-parser as hard dependency in composer
- Improved performance of DependencyMap creation.
- Remove stms when adding ClassMethod nodes to dependency tree
- Added better doc comments
- Addeed more aggresive AbstractApiVisitor traversal termination
- Refactored code to use switch statements

Co-authored-by: Raoul Rego <raoulrego@gmail.com>
  • Loading branch information
roribio and Rrego6 committed Mar 12, 2020
1 parent 41dc553 commit a9fb58d
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 157 deletions.
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "magento/magento-semver",
"description": "Magento semantic version checker",
"version": "4.0.0",
"version": "5.0.0",
"license": [
"OSL-3.0",
"AFL-3.0"
Expand All @@ -13,7 +13,8 @@
"symfony/console": "~4.1.0||~4.4.0",
"tomzx/php-semver-checker": "^0.13.0",
"wikimedia/less.php": "~1.8.0",
"zendframework/zend-stdlib": "^3.2.1"
"zendframework/zend-stdlib": "^3.2.1",
"nikic/php-parser": "^3.1"
},
"require-dev": {
"phpunit/phpunit": "^6.5.0",
Expand Down
32 changes: 16 additions & 16 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

210 changes: 100 additions & 110 deletions src/ClassHierarchy/DependencyInspectionVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,31 @@
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_ as InterfaceNode;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\Node\Stmt\Trait_ as TraitNode;
use PhpParser\Node\Stmt\TraitUse;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;

/**
* Implements a visitor for `class`, `interface` and `trait` nodes that generates a dependency graph.
*/
class DependencyInspectionVisitor extends NodeVisitorAbstract
{

/** @var DependencyGraph */
private $dependencyGraph;

/** @var NodeHelper */
private $nodeHelper;

/**
* @var Entity
* Holds current Entity. Stored so we can populate this entity in our dependency graph upon walking relevant child
* nodes.
*/
private $currentClassLike = null;

/**
* Constructor.
*
Expand All @@ -43,135 +53,115 @@ public function __construct(DependencyGraph $dependencyGraph, NodeHelper $nodeHe
}

/**
* @inheritDoc
* Logic to process current node. We aggressively halt walking the AST since this may contain many nodes
* If we are visiting a Classlike node, set currentClassLike so we can populate this entity in our dependency graph
* upon walking relevant child nodes like PropertyProperty and ClassMethod.
*
* Inspect nodes after all visitors have run since we need the fully qualified names of nodes.
*/
public function leaveNode(Node $node)
{
if ($node instanceof ClassNode) {
$this->addClassNode($node);
} elseif ($node instanceof InterfaceNode) {
$this->addInterfaceNode($node);
} elseif ($node instanceof TraitNode) {
$this->addTraitNode($node);
}
}

/**
* Getter for {@link DependencyInspectionVisitor::$dependencyGraph}.
* Subparse tree we want to traverse will be something like:
* Namespace -> ClassLike -> ClassMethod
* -> TraitUse
* -> PropertyProperty
*
* @return DependencyGraph
*/
public function getDependencyGraph(): DependencyGraph
{
return $this->dependencyGraph;
}

/**
* @param ClassNode $node
*
* @inheritdoc
*
* @param Node $node
* @return int tells NodeTraverser whether to continue traversing
*/
private function addClassNode(ClassNode $node)
public function enterNode(Node $node)
{
// name is not set for anonymous classes, therefore they cannot be part of the dependency graph
if ($node->isAnonymous()) {
return;
}

$className = (string)$node->namespacedName;
$class = $this->dependencyGraph->findOrCreateClass($className);

[$methodList, $propertyList] = $this->fetchStmtsNodes($node);
$class->setMethodList($methodList);
$class->setPropertyList($propertyList);
$class->setIsApi($this->nodeHelper->isApiNode($node));

if ($node->extends) {
$parentClassName = (string)$node->extends;
$parentClassEntity = $this->dependencyGraph->findOrCreateClass($parentClassName);
$class->addExtends($parentClassEntity);
}

foreach ($node->implements as $implement) {
$interfaceName = (string)$implement;
$interfaceEntity = $this->dependencyGraph->findOrCreateInterface($interfaceName);
$class->addImplements($interfaceEntity);
}

foreach ($this->nodeHelper->getTraitUses($node) as $traitUse) {
foreach ($traitUse->traits as $trait) {
$traitName = (string)$trait;
$traitEntity = $this->dependencyGraph->findOrCreateTrait($traitName);
$class->addUses($traitEntity);
}
switch (true) {
case $node instanceof Node\Stmt\Namespace_:
return null;
case $node instanceof ClassLike:
//set currentClassLike entity
return $this->handleClassLike($node);
case $node instanceof ClassMethod:
$this->currentClassLike->addMethod($node);
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
case $node instanceof TraitUse:
foreach ($node->traits as $trait) {
$traitName = (string)$trait;
$traitEntity = $this->dependencyGraph->findOrCreateTrait($traitName);
$this->currentClassLike->addUses($traitEntity);
}
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
case $node instanceof PropertyProperty:
$this->currentClassLike->addProperty($node);
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
default:
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}

$this->dependencyGraph->addEntity($class);
}

/**
* @param InterfaceNode $node
* Handles Class, Interface, and Traits nodes. Sets currentClassLike entity and will populate extends, implements,
* and API information
*
* @param ClassLike $node
* @return int|null
*/
private function addInterfaceNode(InterfaceNode $node)
private function handleClassLike(ClassLike $node)
{
$interfaceName = (string)$node->namespacedName;
$interface = $this->dependencyGraph->findOrCreateInterface($interfaceName);

$interface->setIsApi($this->nodeHelper->isApiNode($node));
[$methodList] = $this->fetchStmtsNodes($node);
$interface->setMethodList($methodList);

foreach ($node->extends as $extend) {
$interfaceName = (string)$extend;
$interfaceEntity = $this->dependencyGraph->findOrCreateInterface($interfaceName);
$interface->addExtends($interfaceEntity);
/**
* @var \PhpParser\Node\Name $namespacedName
* This is set in the NamespaceResolver visitor
*/
$namespacedName = $node->namespacedName;
switch (true) {
case $node instanceof ClassNode:
if ($node->isAnonymous()) {
return NodeTraverser::STOP_TRAVERSAL;
}
$this->currentClassLike = $this->dependencyGraph->findOrCreateClass((string)$namespacedName);
if ($node->extends) {
$parentClassName = (string)$node->extends;
$parentClassEntity = $this->dependencyGraph->findOrCreateClass($parentClassName);
$this->currentClassLike->addExtends($parentClassEntity);
}
foreach ($node->implements as $implement) {
$interfaceName = (string)$implement;
$interfaceEntity = $this->dependencyGraph->findOrCreateInterface($interfaceName);
$this->currentClassLike->addImplements($interfaceEntity);
}
break;
case $node instanceof InterfaceNode:
$this->currentClassLike = $this->dependencyGraph->findOrCreateInterface((string)$namespacedName);
foreach ($node->extends as $extend) {
$interfaceName = (string)$extend;
$interfaceEntity = $this->dependencyGraph->findOrCreateInterface($interfaceName);
$this->currentClassLike->addExtends($interfaceEntity);
}
break;
case $node instanceof TraitNode:
$this->currentClassLike = $this->dependencyGraph->findOrCreateTrait((string)$namespacedName);
break;
}

$this->dependencyGraph->addEntity($interface);
$this->currentClassLike->setIsApi($this->nodeHelper->isApiNode($node));
return null;
}

/**
* @param TraitNode $node
/*
* Unsets currentClassLike upon exiting ClassLike node. This is for cleanup, although this is not necessary since
* Classmethod, PropertyProperty, and TraitUse nodes will only be traversed after Classlike
*
* @param Node $node
* @return false|int|Node|Node[]|void|null
*/
private function addTraitNode(TraitNode $node)
public function leaveNode(Node $node)
{
$traitName = (string)$node->namespacedName;
$trait = $this->dependencyGraph->findOrCreateTrait($traitName);

[$methodList, $propertyList] = $this->fetchStmtsNodes($node);
$trait->setMethodList($methodList);
$trait->setPropertyList($propertyList);
$trait->setIsApi($this->nodeHelper->isApiNode($node));

foreach ($this->nodeHelper->getTraitUses($node) as $traitUse) {
foreach ($traitUse->traits as $parentTrait) {
$parentTraitName = (string)$parentTrait;
$parentTraitEntity = $this->dependencyGraph->findOrCreateTrait($parentTraitName);
$trait->addUses($parentTraitEntity);
}
if ($node instanceof ClassLike) {
$this->currentClassLike = null;
}

$this->dependencyGraph->addEntity($trait);
}

/**
* @param ClassLike $node
* @return array
* Getter for {@link DependencyInspectionVisitor::$dependencyGraph}.
*
* @return DependencyGraph
*/
private function fetchStmtsNodes(ClassLike $node): array
public function getDependencyGraph(): DependencyGraph
{
$methodList = [];
$propertyList = [];
foreach ($node->stmts as $stmt) {
if ($stmt instanceof ClassMethod) {
$methodList[$stmt->name] = $stmt;
} elseif ($stmt instanceof Property) {
foreach ($stmt->props as $prop) {
$propertyList[$prop->name] = $prop;
}
}
}

return [$methodList, $propertyList];
return $this->dependencyGraph;
}
}

0 comments on commit a9fb58d

Please sign in to comment.