forked from qossmic/deptrac
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
5 changed files
with
240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
|
||
namespace Qossmic\Deptrac\Core\Layer\Collector; | ||
|
||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; | ||
use PHPStan\PhpDocParser\Lexer\Lexer; | ||
use PHPStan\PhpDocParser\Parser\ConstExprParser; | ||
use PHPStan\PhpDocParser\Parser\PhpDocParser; | ||
use PHPStan\PhpDocParser\Parser\TokenIterator; | ||
use PHPStan\PhpDocParser\Parser\TypeParser; | ||
use Qossmic\Deptrac\Contract\Ast\CouldNotParseFileException; | ||
use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; | ||
use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; | ||
use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; | ||
use Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\NikicPhpParser; | ||
|
||
class PackageNameCollector extends RegexCollector | ||
{ | ||
private readonly Lexer $lexer; | ||
private readonly PhpDocParser $docParser; | ||
|
||
public function __construct( | ||
private readonly NikicPhpParser $nikicPhpParser | ||
) { | ||
$this->lexer = new Lexer(); | ||
$this->docParser = new PhpDocParser(new TypeParser(), new ConstExprParser()); | ||
} | ||
|
||
public function satisfy(array $config, TokenReferenceInterface $reference): bool | ||
{ | ||
if (!$reference instanceof ClassLikeReference) { | ||
return false; | ||
} | ||
|
||
$regex = $this->getValidatedPattern($config); | ||
|
||
foreach ($this->getPackages($reference) as $package) { | ||
if (1 === preg_match($regex, $package)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
protected function getPattern(array $config): string | ||
{ | ||
if (!isset($config['value']) || !is_string($config['value'])) { | ||
throw new InvalidCollectorDefinitionException('PackageNameCollector needs the value configuration.'); | ||
} | ||
|
||
return '/'.$config['value'].'/im'; | ||
} | ||
|
||
/** | ||
* @return array<string> | ||
* @throws CouldNotParseFileException | ||
*/ | ||
private function getPackages(ClassLikeReference $reference): array | ||
{ | ||
$docBlock = $this->getCommentDoc($reference); | ||
|
||
if (!$docBlock) { | ||
return []; | ||
} | ||
|
||
$tokens = new TokenIterator($this->lexer->tokenize($docBlock)); | ||
$docNode = $this->docParser->parse($tokens); | ||
|
||
return array_map( | ||
static fn (PhpDocTagNode $node) => (string) $node->value, | ||
$docNode->getTagsByName('@package') | ||
); | ||
} | ||
|
||
/** | ||
* @throws CouldNotParseFileException | ||
*/ | ||
private function getCommentDoc(ClassLikeReference $reference): string | ||
{ | ||
$node = $this->nikicPhpParser->getNodeForClassLikeReference($reference); | ||
|
||
if (null === $node) { | ||
return ''; | ||
} | ||
|
||
$doc = $node->getDocComment(); | ||
|
||
if (null === $doc) { | ||
return ''; | ||
} | ||
|
||
return $doc->getText(); | ||
} | ||
} |
122 changes: 122 additions & 0 deletions
122
tests/Core/Layer/Collector/PackageNameCollectorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; | ||
|
||
use PhpParser\Comment\Doc; | ||
use PhpParser\Node; | ||
use PHPUnit\Framework\TestCase; | ||
use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; | ||
use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; | ||
use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; | ||
use Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\NikicPhpParser; | ||
use Qossmic\Deptrac\Core\Layer\Collector\PackageNameCollector; | ||
|
||
final class PackageNameCollectorTest extends TestCase | ||
{ | ||
private NikicPhpParser $astParser; | ||
private PackageNameCollector $collector; | ||
|
||
protected function setUp(): void | ||
{ | ||
parent::setUp(); | ||
|
||
$this->astParser = $this->createMock(NikicPhpParser::class); | ||
|
||
$this->collector = new PackageNameCollector($this->astParser); | ||
} | ||
|
||
public function provideSatisfy(): iterable | ||
{ | ||
yield [ | ||
['value' => 'abc'], | ||
$this->getPackageDocBlock(['abc', 'abcdef', 'xyz']), | ||
true, | ||
]; | ||
|
||
yield [ | ||
['value' => 'abc'], | ||
$this->getPackageDocBlock(['abc', 'xyz']), | ||
true, | ||
]; | ||
|
||
yield [ | ||
['value' => 'abc'], | ||
$this->getPackageDocBlock(['xyz']), | ||
false, | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider provideSatisfy | ||
*/ | ||
public function testSatisfy(array $configuration, Doc $docBlock, bool $expected): void | ||
{ | ||
$astClassReference = new ClassLikeReference(ClassLikeToken::fromFQCN('foo')); | ||
|
||
$classLike = $this->createMock(Node\Stmt\ClassLike::class); | ||
$classLike->method('getDocComment')->willReturn($docBlock); | ||
|
||
$this->astParser | ||
->method('getNodeForClassLikeReference') | ||
->with($astClassReference) | ||
->willReturn($classLike); | ||
|
||
$actual = $this->collector->satisfy( | ||
$configuration, | ||
$astClassReference, | ||
); | ||
|
||
self::assertSame($expected, $actual); | ||
} | ||
|
||
public function testClassLikeAstNotFoundDoesNotSatisfy(): void | ||
{ | ||
$astClassReference = new ClassLikeReference(ClassLikeToken::fromFQCN('foo')); | ||
$this->astParser | ||
->method('getNodeForClassLikeReference') | ||
->with($astClassReference) | ||
->willReturn(null); | ||
|
||
$actual = $this->collector->satisfy( | ||
['value' => 'abc'], | ||
$astClassReference, | ||
); | ||
|
||
self::assertFalse($actual); | ||
} | ||
|
||
public function testMissingValueThrowsException(): void | ||
{ | ||
$astClassReference = new ClassLikeReference(ClassLikeToken::fromFQCN('foo')); | ||
|
||
$this->expectException(InvalidCollectorDefinitionException::class); | ||
$this->expectExceptionMessage('PackageNameCollector needs the value configuration.'); | ||
|
||
$this->collector->satisfy( | ||
[], | ||
$astClassReference, | ||
); | ||
} | ||
|
||
public function testInvalidRegexParam(): void | ||
{ | ||
$astClassReference = new ClassLikeReference(ClassLikeToken::fromFQCN('foo')); | ||
|
||
$this->expectException(InvalidCollectorDefinitionException::class); | ||
|
||
$this->collector->satisfy( | ||
['value' => '/'], | ||
$astClassReference, | ||
); | ||
} | ||
|
||
private function getPackageDocBlock(array $packageNames): Doc | ||
{ | ||
return new Doc(sprintf( | ||
" /**\n%s */", | ||
implode('', array_map(static fn ($packageName) => ' * @package '.$packageName."\n", $packageNames)) | ||
)); | ||
} | ||
} |