Skip to content

Commit

Permalink
Collector interface and infrastructure
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 26, 2022
1 parent 78016fe commit deda4bd
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 0 deletions.
7 changes: 7 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,13 @@ services:
arguments:
storage: @cacheStorage

-
class: PHPStan\Collectors\Registry
factory: @PHPStan\Collectors\RegistryFactory::create

-
class: PHPStan\Collectors\RegistryFactory

-
class: PHPStan\Command\AnalyseApplication
arguments:
Expand Down
20 changes: 20 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ parameters:
count: 1
path: src/Analyser/NodeScopeResolver.php

-
message: "#^Binary operation \"\\+\" between array\\{class\\-string\\<TNodeType of PhpParser\\\\Node\\>\\} and array\\<string, class\\-string\\>\\|false results in an error\\.$#"
count: 1
path: src/Collectors/Registry.php

-
message: "#^Method PHPStan\\\\Collectors\\\\Registry\\:\\:__construct\\(\\) has parameter \\$collectors with generic interface PHPStan\\\\Collectors\\\\Collector but does not specify its types\\: TNodeType$#"
count: 1
path: src/Collectors/Registry.php

-
message: "#^Property PHPStan\\\\Collectors\\\\Registry\\:\\:\\$cache with generic interface PHPStan\\\\Collectors\\\\Collector does not specify its types\\: TNodeType$#"
count: 1
path: src/Collectors/Registry.php

-
message: "#^Property PHPStan\\\\Collectors\\\\Registry\\:\\:\\$collectors with generic interface PHPStan\\\\Collectors\\\\Collector does not specify its types\\: TNodeType$#"
count: 1
path: src/Collectors/Registry.php

-
message: "#^Anonymous function has an unused use \\$container\\.$#"
count: 1
Expand Down
25 changes: 25 additions & 0 deletions src/Collectors/Collector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php declare(strict_types = 1);

namespace PHPStan\Collectors;

use PhpParser\Node;
use PHPStan\Analyser\Scope;

/**
* @phpstan-template TNodeType of Node
*/
interface Collector
{

/**
* @phpstan-return class-string<TNodeType>
*/
public function getNodeType(): string;

/**
* @phpstan-param TNodeType $node
* @return mixed Collected data
*/
public function processNode(Node $node, Scope $scope);

}
59 changes: 59 additions & 0 deletions src/Collectors/Registry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php declare(strict_types = 1);

namespace PHPStan\Collectors;

use PhpParser\Node;
use function class_implements;
use function class_parents;

class Registry
{

/** @var Collector[][] */
private array $collectors = [];

/** @var Collector[][] */
private array $cache = [];

/**
* @param Collector[] $collectors
*/
public function __construct(array $collectors)
{
foreach ($collectors as $collector) {
$this->collectors[$collector->getNodeType()][] = $collector;
}
}

/**
* @template TNodeType of Node
* @phpstan-param class-string<TNodeType> $nodeType
* @param Node $nodeType
* @phpstan-return array<Collector<TNodeType>>
* @return Collector[]
*/
public function getCollectors(string $nodeType): array
{
if (!isset($this->cache[$nodeType])) {
$parentNodeTypes = [$nodeType] + class_parents($nodeType) + class_implements($nodeType);

$collectors = [];
foreach ($parentNodeTypes as $parentNodeType) {
foreach ($this->collectors[$parentNodeType] ?? [] as $collector) {
$collectors[] = $collector;
}
}

$this->cache[$nodeType] = $collectors;
}

/**
* @phpstan-var array<Collector<TNodeType>> $selectedCollectors
* @var Collector[] $selectedCollectors
*/
$selectedCollectors = $this->cache[$nodeType];

return $selectedCollectors;
}

}
23 changes: 23 additions & 0 deletions src/Collectors/RegistryFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php declare(strict_types = 1);

namespace PHPStan\Collectors;

use PHPStan\DependencyInjection\Container;

class RegistryFactory
{

public const COLLECTOR_TAG = 'phpstan.collector';

public function __construct(private Container $container)
{
}

public function create(): Registry
{
return new Registry(
$this->container->getServicesByTag(self::COLLECTOR_TAG),
);
}

}
24 changes: 24 additions & 0 deletions tests/PHPStan/Collectors/DummyCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types = 1);

namespace PHPStan\Collectors;

use PhpParser\Node;
use PHPStan\Analyser\Scope;

/**
* @implements Collector<Node\Expr\FuncCall>
*/
class DummyCollector implements Collector
{

public function getNodeType(): string
{
return 'PhpParser\Node\Expr\FuncCall';
}

public function processNode(Node $node, Scope $scope)
{
return [];
}

}
45 changes: 45 additions & 0 deletions tests/PHPStan/Collectors/RegistryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php declare(strict_types = 1);

namespace PHPStan\Collectors;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Testing\PHPStanTestCase;

class RegistryTest extends PHPStanTestCase
{

public function testGetCollectors(): void
{
$collector = new DummyCollector();

$registry = new Registry([
$collector,
]);

$collectors = $registry->getCollectors(Node\Expr\FuncCall::class);
$this->assertCount(1, $collectors);
$this->assertSame($collector, $collectors[0]);

$this->assertCount(0, $registry->getCollectors(Node\Expr\MethodCall::class));
}

public function testGetCollectorsWithTwoDifferentInstances(): void
{
$fooCollector = new UniversalCollector(Node\Expr\FuncCall::class, static fn (Node\Expr\FuncCall $node, Scope $scope): array => ['Foo error']);
$barCollector = new UniversalCollector(Node\Expr\FuncCall::class, static fn (Node\Expr\FuncCall $node, Scope $scope): array => ['Bar error']);

$registry = new Registry([
$fooCollector,
$barCollector,
]);

$collectors = $registry->getCollectors(Node\Expr\FuncCall::class);
$this->assertCount(2, $collectors);
$this->assertSame($fooCollector, $collectors[0]);
$this->assertSame($barCollector, $collectors[1]);

$this->assertCount(0, $registry->getCollectors(Node\Expr\MethodCall::class));
}

}
46 changes: 46 additions & 0 deletions tests/PHPStan/Collectors/UniversalCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php declare(strict_types = 1);

namespace PHPStan\Collectors;

use PhpParser\Node;
use PHPStan\Analyser\Scope;

/**
* @template TNodeType of Node
* @implements Collector<TNodeType>
*/
class UniversalCollector implements Collector
{

/** @phpstan-var class-string<TNodeType> */
private $nodeType;

/** @var (callable(TNodeType, Scope): mixed) */
private $processNodeCallback;

/**
* @param class-string<TNodeType> $nodeType
* @param (callable(TNodeType, Scope): mixed) $processNodeCallback
*/
public function __construct(string $nodeType, callable $processNodeCallback)
{
$this->nodeType = $nodeType;
$this->processNodeCallback = $processNodeCallback;
}

public function getNodeType(): string
{
return $this->nodeType;
}

/**
* @param TNodeType $node
* @return mixed
*/
public function processNode(Node $node, Scope $scope)
{
$callback = $this->processNodeCallback;
return $callback($node, $scope);
}

}

0 comments on commit deda4bd

Please sign in to comment.