Skip to content

Commit

Permalink
New collector for attributes.
Browse files Browse the repository at this point in the history
  • Loading branch information
Denis Brumann committed May 21, 2022
1 parent 556ddd4 commit 7a9546f
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 3 deletions.
4 changes: 4 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
use Qossmic\Deptrac\File\YmlFileLoader;
use Qossmic\Deptrac\InputCollector\FileInputCollector;
use Qossmic\Deptrac\InputCollector\InputCollectorInterface;
use Qossmic\Deptrac\Layer\Collector\AttributeCollector;
use Qossmic\Deptrac\Layer\Collector\BoolCollector;
use Qossmic\Deptrac\Layer\Collector\ClassCollector;
use Qossmic\Deptrac\Layer\Collector\ClassLikeCollector;
Expand Down Expand Up @@ -197,6 +198,9 @@
]);
$services->set(CollectorResolver::class);
$services->alias(CollectorResolverInterface::class, CollectorResolver::class);
$services
->set(AttributeCollector::class)
->tag('collector', ['type' => CollectorTypes::TYPE_ATTRIBUTE]);
$services
->set(BoolCollector::class)
->tag('collector', ['type' => CollectorTypes::TYPE_BOOL]);
Expand Down
15 changes: 15 additions & 0 deletions docs/collectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
Collectors decide if a node (typically a class) is part of a layer. You can use
multiple different collectors for a layer.

## `attribute` Collector

The `attribute` collector finds all class-likes, functions or files using the
provided attribute. You can provide the full attribute name or a substring that
should be matched.

```yaml
parameters:
layers:
- name: Entities
collectors:
- type: attribute
value: Doctrine\ORM\Mapping\Entity
```
## `bool` Collector

The `bool` collector allows combining other collectors with or without negation.
Expand Down
57 changes: 57 additions & 0 deletions src/Layer/Collector/AttributeCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php declare(strict_types=1);

namespace Qossmic\Deptrac\Layer\Collector;

use LogicException;
use Qossmic\Deptrac\Ast\AstMap\AstMap;
use Qossmic\Deptrac\Ast\AstMap\ClassLike\ClassLikeReference;
use Qossmic\Deptrac\Ast\AstMap\DependencyToken;
use Qossmic\Deptrac\Ast\AstMap\File\FileReference;
use Qossmic\Deptrac\Ast\AstMap\FunctionLike\FunctionLikeReference;
use Qossmic\Deptrac\Ast\AstMap\TokenReferenceInterface;

class AttributeCollector implements CollectorInterface
{
public function satisfy(array $config, TokenReferenceInterface $reference, AstMap $astMap): bool
{
if (!$reference instanceof FileReference
&& !$reference instanceof ClassLikeReference
&& !$reference instanceof FunctionLikeReference
) {
return false;
}

$match = $this->getSearchedSubstring($config);

foreach ($reference->getDependencies() as $dependency) {
if (DependencyToken::ATTRIBUTE !== $dependency->getType()) {
continue;
}

$usedAttribute = $dependency->getToken()->toString();

if (str_contains($usedAttribute, $match)) {
return true;
}
}

return false;
}

public function resolvable(array $config): bool
{
return true;
}

/**
* @param array<string, string|array<string, string>> $config
*/
private function getSearchedSubstring(array $config): string
{
if (!isset($config['value']) || !is_string($config['value'])) {
throw new LogicException('UsesCollector needs the trait name as a string.');
}

return $config['value'];
}
}
1 change: 1 addition & 0 deletions src/Layer/Collector/CollectorTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

final class CollectorTypes
{
public const TYPE_ATTRIBUTE = 'attribute';
public const TYPE_BOOL = 'bool';
public const TYPE_CLASS = 'class';
public const TYPE_CLASSLIKE = 'classLike';
Expand Down
6 changes: 3 additions & 3 deletions src/Layer/Collector/UsesCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ public function satisfy(array $config, TokenReferenceInterface $reference, AstMa
return false;
}

$interfaceName = $this->getInterfaceName($config);
$traitName = $this->getTraitName($config);

foreach ($astMap->getClassInherits($reference->getToken()) as $inherit) {
if ($inherit->isUses() && $inherit->getClassLikeName()->equals($interfaceName)) {
if ($inherit->isUses() && $inherit->getClassLikeName()->equals($traitName)) {
return true;
}
}
Expand All @@ -37,7 +37,7 @@ public function satisfy(array $config, TokenReferenceInterface $reference, AstMa
/**
* @param array<string, string|array<string, string>> $config
*/
private function getInterfaceName(array $config): ClassLikeToken
private function getTraitName(array $config): ClassLikeToken
{
if (isset($config['uses']) && !isset($config['value'])) {
trigger_deprecation('qossmic/deptrac', '0.20.0', 'UsesCollector should use the "value" key from this version');
Expand Down
51 changes: 51 additions & 0 deletions tests/Layer/Collector/AttributeCollectorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php declare(strict_types=1);

namespace Tests\Qossmic\Deptrac\Layer\Collector;

use PHPUnit\Framework\TestCase;
use Qossmic\Deptrac\Ast\AstMap\AstMap;
use Qossmic\Deptrac\Ast\AstMap\File\FileReferenceBuilder;
use Qossmic\Deptrac\Layer\Collector\AttributeCollector;

final class AttributeCollectorTest extends TestCase
{
private AttributeCollector $collector;

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

$this->collector = new AttributeCollector();
}

public function dataProviderSatisfy(): iterable
{
yield 'matches usage of attribute with only partial name' => [
['value' => 'MyAttribute'],
true,
];
yield 'does not match unescaped fully qualified class name' => [
['value' => 'App\MyAttribute'],
true,
];
yield 'does not match other attributes' => [
['value' => 'OtherAttribute'],
false,
];
}

/**
* @dataProvider dataProviderSatisfy
*/
public function testSatisfy(array $config, bool $expected): void
{
$classLikeReference = FileReferenceBuilder::create('Foo.php')
->newClass('App\Foo')
->attribute('App\MyAttribute', 2)
->attribute('MyAttribute', 3)
->build();
$actual = $this->collector->satisfy($config, $classLikeReference, new AstMap([]));

self::assertSame($expected, $actual);
}
}

0 comments on commit 7a9546f

Please sign in to comment.