Skip to content

Commit

Permalink
Optional docblock dependency checks
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosas authored Nov 17, 2019
1 parent 37feb58 commit e71dc5b
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 11 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ This is the complete list of options:
* `src` `exclude`: Files you want to be excluded in the tests (default=none).
* `tests` `path`: The path where your tests are.
* `options` `verbosity`: 0/1/2 output verbosity level (default=1).
* `options` `dependency` `ignore_docblocks`: true/false (default=false).

<h2></h2>

Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"require": {
"php": "^7.1",
"nikic/php-parser": "^4",
"phpstan/phpdoc-parser": "^0.3",
"symfony/dependency-injection": "^2.0.5|^3|^4",
"symfony/event-dispatcher": "^2.0.5|^3|^4",
"symfony/finder": "^2.3.0|^3|^4",
Expand Down
7 changes: 6 additions & 1 deletion src/App/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ public function getTestsPath(): string
return $this->config['tests']['path'] ?? '';
}

public function getVerbosity(): int
public function getOptVerbosity(): int
{
return (int) ($this->config['options']['verbosity'] ?? 1);
}

public function getOptDependencyCheckDocBlocks(): bool
{
return (bool) ($this->config['options']['dependency']['ignore_docblocks'] ?? false);
}
}
10 changes: 8 additions & 2 deletions src/App/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
use PhpParser\NodeTraverser;
use PhpParser\NodeTraverserInterface;
use PhpParser\ParserFactory;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TypeParser;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcher;
Expand Down Expand Up @@ -71,6 +74,7 @@ public function __construct(ContainerBuilder $builder, string $autoload, array $
public function register(): ContainerBuilder
{
$phpParser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7);
$phpDocParser = new PhpDocParser(new TypeParser(), new ConstExprParser());

$this->builder
->register(EventDispatcherInterface::class, EventDispatcher::class);
Expand All @@ -89,7 +93,7 @@ public function register(): ContainerBuilder

$this->builder
->register(OutputInterface::class, StdOutput::class)
->addArgument($this->configuration->getVerbosity());
->addArgument($this->configuration->getOptVerbosity());

$this->builder
->register(RuleBuilder::class, RuleBuilder::class)
Expand All @@ -115,7 +119,9 @@ public function register(): ContainerBuilder
->addArgument(new Reference(FileFinder::class))
->addArgument($phpParser)
->addArgument(new Reference(NodeTraverserInterface::class))
->addArgument(new Reference(EventDispatcherInterface::class));
->addArgument($phpDocParser)
->addArgument(new Reference(EventDispatcherInterface::class))
->addArgument($this->configuration->getOptDependencyCheckDocBlocks());

$this->builder
->register(Inheritance::class, Inheritance::class)
Expand Down
28 changes: 27 additions & 1 deletion src/Parser/Collector/DependencyCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,34 @@
use PhpAT\Parser\ClassMatcher;
use PhpAT\Parser\ClassName;
use PhpParser\Node;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;

class DependencyCollector extends AbstractCollector
{
/**
* @var ClassMatcher
*/
private $matcher;
/**
* @var bool
*/
private $ignoreDocBlocks;
/**
* @var array
*/
private $dependencies = [];
/**
* @var PhpDocParser
*/
private $docParser;

public function __construct(ClassMatcher $matcher)
public function __construct(PhpDocParser $docParser, ClassMatcher $matcher, bool $ignoreDocBlocks)
{
$this->docParser = $docParser;
$this->matcher = $matcher;
$this->ignoreDocBlocks = $ignoreDocBlocks;
}

public function leaveNode(Node $node)
Expand All @@ -30,6 +46,16 @@ public function leaveNode(Node $node)
if ($found !== null) {
$this->saveResultIfNotPresent($found);
}
} elseif (!$this->ignoreDocBlocks && $node->getDocComment() !== null) {
$doc = $node->getDocComment()->getText();
$nodes = $this->docParser->parse(new TokenIterator((new Lexer())->tokenize($doc)));
foreach ($nodes->getTags() as $tag) {
if (isset($tag->value->type->name)) {
$type = $tag->value->type->name;
$class = $this->matcher->findClass(explode('\\', $type)) ?? $type;
$this->saveResultIfNotPresent($class);
}
}
}
}

Expand Down
34 changes: 29 additions & 5 deletions src/Rule/Type/Dependency.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,31 @@
use PhpAT\Statement\Event\StatementValidEvent;
use PhpParser\NodeTraverserInterface;
use PhpParser\Parser;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

class Dependency implements RuleType
{
private $traverser;
/**
* @var FileFinder
*/
private $finder;
/**
* @var Parser
*/
private $parser;
/**
* @var NodeTraverserInterface
*/
private $traverser;
/**
* @var PhpDocParser
*/
private $docParser;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* @var ClassName
*/
Expand All @@ -28,18 +46,25 @@ class Dependency implements RuleType
* @var ClassName[]
*/
private $parsedClassDependencies;
private $eventDispatcher;
/**
* @var bool
*/
private $ignoreDocBlocks;

public function __construct(
FileFinder $finder,
Parser $parser,
NodeTraverserInterface $traverser,
EventDispatcherInterface $eventDispatcher
PhpDocParser $docParser,
EventDispatcherInterface $eventDispatcher,
bool $ignoreDocBlocks
) {
$this->finder = $finder;
$this->parser = $parser;
$this->traverser = $traverser;
$this->docParser = $docParser;
$this->eventDispatcher = $eventDispatcher;
$this->ignoreDocBlocks = $ignoreDocBlocks;
}

public function validate(
Expand All @@ -63,7 +88,6 @@ public function validate(
}
$this->traverser->removeVisitor($classNameCollector);

//TODO: Change to FatalErrorEvent (could not find any class in the test)
if (empty($classNameCollector->getResult())) {
return;
}
Expand Down Expand Up @@ -91,7 +115,7 @@ private function extractParsedClassInfo(array $parsedClass): void
$matcher = new ClassMatcher();
$matcher->saveNamespace($this->parsedClassClassName->getNamespace());

$dependencyExtractor = new DependencyCollector($matcher);
$dependencyExtractor = new DependencyCollector($this->docParser, $matcher, $this->ignoreDocBlocks);
$this->traverser->addVisitor($dependencyExtractor);
$this->traverser->traverse($parsedClass);
$this->traverser->removeVisitor($dependencyExtractor);
Expand Down
1 change: 0 additions & 1 deletion src/Rule/Type/Inheritance.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public function validate(
}
$this->traverser->removeVisitor($classNameCollector);

//TODO: Change to FatalErrorEvent (could not find any class in the test)
if (empty($classNameCollector->getResult())) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/PHP7/tests/DependencyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function testDirectDependency(): Rule
->andClassesThat(Selector::havePath('Dependency/MethodReturn.php'))
->andClassesThat(Selector::havePath('Dependency/Instantiation.php'))
//->andClassesThat(Selector::havePath('Dependency/UnusedDeclaration.php'))
//->andClassesThat(Selector::havePath('Dependency/DocBlock.php'))
->andClassesThat(Selector::havePath('Dependency/DocBlock.php'))
->mustDependOn()
->classesThat(Selector::havePath('SimpleClass.php'))
->classesThat(Selector::havePath('AnotherSimpleClass.php'))
Expand Down

0 comments on commit e71dc5b

Please sign in to comment.