From 238816572872f7d376134c0be449f20f2e9f5a0e Mon Sep 17 00:00:00 2001 From: Andrew Riddlestone Date: Wed, 12 Jul 2023 15:28:53 +0100 Subject: [PATCH] Moved doc parsing to FileReferenceVisitor --- src/Contract/Ast/TokenReferenceInterface.php | 2 +- ...p => TokenReferenceMetaDatumInterface.php} | 2 +- .../AstMap/ClassLike/ClassLikeReference.php | 4 +- src/Core/Ast/AstMap/File/FileReference.php | 4 +- .../Ast/AstMap/Function/FunctionReference.php | 4 +- src/Core/Ast/AstMap/ReferenceBuilder.php | 8 +- .../Ast/AstMap/Variable/VariableReference.php | 4 +- src/Core/Ast/MetaData/PackageName.php | 18 +++ .../NikicPhpParser/FileReferenceVisitor.php | 8 ++ .../Layer/Collector/PackageNameCollector.php | 68 ++-------- .../NikicPhpParser/Fixtures/PackageNames.php | 14 ++ .../NikicPhpParser/NikicPhpParserTest.php | 29 ++++ .../Collector/PackageNameCollectorTest.php | 127 +++++------------- 13 files changed, 123 insertions(+), 169 deletions(-) rename src/Contract/Ast/{TokenReferenceMetaInterface.php => TokenReferenceMetaDatumInterface.php} (54%) create mode 100644 src/Core/Ast/MetaData/PackageName.php create mode 100644 tests/Core/Ast/Parser/NikicPhpParser/Fixtures/PackageNames.php diff --git a/src/Contract/Ast/TokenReferenceInterface.php b/src/Contract/Ast/TokenReferenceInterface.php index 71a7f5d3f..15d324ddf 100644 --- a/src/Contract/Ast/TokenReferenceInterface.php +++ b/src/Contract/Ast/TokenReferenceInterface.php @@ -14,7 +14,7 @@ public function getFilepath(): ?string; public function getToken(): TokenInterface; /** - * @return TokenReferenceMetaInterface[] + * @return TokenReferenceMetaDatumInterface[] */ public function getMetaData(): array; } diff --git a/src/Contract/Ast/TokenReferenceMetaInterface.php b/src/Contract/Ast/TokenReferenceMetaDatumInterface.php similarity index 54% rename from src/Contract/Ast/TokenReferenceMetaInterface.php rename to src/Contract/Ast/TokenReferenceMetaDatumInterface.php index 65a872017..b93845a6f 100644 --- a/src/Contract/Ast/TokenReferenceMetaInterface.php +++ b/src/Contract/Ast/TokenReferenceMetaDatumInterface.php @@ -2,6 +2,6 @@ namespace Qossmic\Deptrac\Contract\Ast; -interface TokenReferenceMetaInterface +interface TokenReferenceMetaDatumInterface { } diff --git a/src/Core/Ast/AstMap/ClassLike/ClassLikeReference.php b/src/Core/Ast/AstMap/ClassLike/ClassLikeReference.php index 48d1394ec..d8a330885 100644 --- a/src/Core/Ast/AstMap/ClassLike/ClassLikeReference.php +++ b/src/Core/Ast/AstMap/ClassLike/ClassLikeReference.php @@ -5,7 +5,7 @@ namespace Qossmic\Deptrac\Core\Ast\AstMap\ClassLike; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; -use Qossmic\Deptrac\Contract\Ast\TokenReferenceMetaInterface; +use Qossmic\Deptrac\Contract\Ast\TokenReferenceMetaDatumInterface; use Qossmic\Deptrac\Core\Ast\AstMap\AstInherit; use Qossmic\Deptrac\Core\Ast\AstMap\DependencyToken; use Qossmic\Deptrac\Core\Ast\AstMap\File\FileReference; @@ -19,7 +19,7 @@ class ClassLikeReference implements TokenReferenceInterface /** * @param AstInherit[] $inherits - * @param TokenReferenceMetaInterface[] $metaData + * @param TokenReferenceMetaDatumInterface[] $metaData * @param DependencyToken[] $dependencies */ public function __construct( diff --git a/src/Core/Ast/AstMap/File/FileReference.php b/src/Core/Ast/AstMap/File/FileReference.php index f41f7d6ba..b3caa065c 100644 --- a/src/Core/Ast/AstMap/File/FileReference.php +++ b/src/Core/Ast/AstMap/File/FileReference.php @@ -6,7 +6,7 @@ use Qossmic\Deptrac\Contract\Ast\TokenInterface; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; -use Qossmic\Deptrac\Contract\Ast\TokenReferenceMetaInterface; +use Qossmic\Deptrac\Contract\Ast\TokenReferenceMetaDatumInterface; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\DependencyToken; use Qossmic\Deptrac\Core\Ast\AstMap\Function\FunctionReference; @@ -25,7 +25,7 @@ class FileReference implements TokenReferenceInterface /** * @param ClassLikeReference[] $classLikeReferences * @param FunctionReference[] $functionReferences - * @param TokenReferenceMetaInterface[] $metaData + * @param TokenReferenceMetaDatumInterface[] $metaData * @param DependencyToken[] $dependencies */ public function __construct( diff --git a/src/Core/Ast/AstMap/Function/FunctionReference.php b/src/Core/Ast/AstMap/Function/FunctionReference.php index 0d896a824..55e5a1bc5 100644 --- a/src/Core/Ast/AstMap/Function/FunctionReference.php +++ b/src/Core/Ast/AstMap/Function/FunctionReference.php @@ -6,7 +6,7 @@ use Qossmic\Deptrac\Contract\Ast\TokenInterface; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; -use Qossmic\Deptrac\Contract\Ast\TokenReferenceMetaInterface; +use Qossmic\Deptrac\Contract\Ast\TokenReferenceMetaDatumInterface; use Qossmic\Deptrac\Core\Ast\AstMap\DependencyToken; use Qossmic\Deptrac\Core\Ast\AstMap\File\FileReference; @@ -16,7 +16,7 @@ class FunctionReference implements TokenReferenceInterface { /** - * @param TokenReferenceMetaInterface[] $metaData + * @param TokenReferenceMetaDatumInterface[] $metaData * @param DependencyToken[] $dependencies */ public function __construct( diff --git a/src/Core/Ast/AstMap/ReferenceBuilder.php b/src/Core/Ast/AstMap/ReferenceBuilder.php index a9453adb2..a713b0432 100644 --- a/src/Core/Ast/AstMap/ReferenceBuilder.php +++ b/src/Core/Ast/AstMap/ReferenceBuilder.php @@ -6,7 +6,7 @@ use Qossmic\Deptrac\Contract\Ast\DependencyType; use Qossmic\Deptrac\Contract\Ast\FileOccurrence; -use Qossmic\Deptrac\Contract\Ast\TokenReferenceMetaInterface; +use Qossmic\Deptrac\Contract\Ast\TokenReferenceMetaDatumInterface; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; use Qossmic\Deptrac\Core\Ast\AstMap\Function\FunctionToken; use Qossmic\Deptrac\Core\Ast\AstMap\Variable\SuperGlobalToken; @@ -14,7 +14,7 @@ abstract class ReferenceBuilder { /** - * @var TokenReferenceMetaInterface[] + * @var TokenReferenceMetaDatumInterface[] */ protected array $metaData = []; @@ -34,9 +34,9 @@ final public function getTokenTemplates(): array return $this->tokenTemplates; } - public function addMetaData(TokenReferenceMetaInterface $metaDataItem): void + public function addMetaDatum(TokenReferenceMetaDatumInterface $metaDatum): void { - $this->metaData[] = $metaDataItem; + $this->metaData[] = $metaDatum; } /** diff --git a/src/Core/Ast/AstMap/Variable/VariableReference.php b/src/Core/Ast/AstMap/Variable/VariableReference.php index 2ee8f27b4..dd7afd438 100644 --- a/src/Core/Ast/AstMap/Variable/VariableReference.php +++ b/src/Core/Ast/AstMap/Variable/VariableReference.php @@ -6,7 +6,7 @@ use Qossmic\Deptrac\Contract\Ast\TokenInterface; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; -use Qossmic\Deptrac\Contract\Ast\TokenReferenceMetaInterface; +use Qossmic\Deptrac\Contract\Ast\TokenReferenceMetaDatumInterface; /** * @psalm-immutable @@ -14,7 +14,7 @@ class VariableReference implements TokenReferenceInterface { /** - * @param TokenReferenceMetaInterface[] $metaData + * @param TokenReferenceMetaDatumInterface[] $metaData */ public function __construct( private readonly SuperGlobalToken $tokenName, diff --git a/src/Core/Ast/MetaData/PackageName.php b/src/Core/Ast/MetaData/PackageName.php new file mode 100644 index 000000000..006ec9ca5 --- /dev/null +++ b/src/Core/Ast/MetaData/PackageName.php @@ -0,0 +1,18 @@ +packageName; + } +} diff --git a/src/Core/Ast/Parser/NikicPhpParser/FileReferenceVisitor.php b/src/Core/Ast/Parser/NikicPhpParser/FileReferenceVisitor.php index 0b0dfab80..e29368d4e 100644 --- a/src/Core/Ast/Parser/NikicPhpParser/FileReferenceVisitor.php +++ b/src/Core/Ast/Parser/NikicPhpParser/FileReferenceVisitor.php @@ -16,6 +16,7 @@ use PhpParser\Node\Stmt\Use_; use PhpParser\NodeVisitorAbstract; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; use PHPStan\PhpDocParser\Lexer\Lexer; @@ -23,8 +24,10 @@ use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; +use Qossmic\Deptrac\Contract\Ast\TokenReferenceMetaDatumInterface; use Qossmic\Deptrac\Core\Ast\AstMap\File\FileReferenceBuilder; use Qossmic\Deptrac\Core\Ast\AstMap\ReferenceBuilder; +use Qossmic\Deptrac\Core\Ast\MetaData\PackageName; use Qossmic\Deptrac\Core\Ast\Parser\Extractors\ReferenceExtractorInterface; use Qossmic\Deptrac\Core\Ast\Parser\TypeResolver; use Qossmic\Deptrac\Core\Ast\Parser\TypeScope; @@ -290,5 +293,10 @@ private function processClassLikeDocs(array $docNodeCrate): void $this->currentReference->variable($type, $line); } } + + $packageNames = $docNode->getTagsByName('@package'); + foreach ($packageNames as $packageName) { + $this->currentReference->addMetaDatum(new PackageName((string) $packageName->value)); + } } } diff --git a/src/Core/Layer/Collector/PackageNameCollector.php b/src/Core/Layer/Collector/PackageNameCollector.php index 30c088f80..983d83808 100644 --- a/src/Core/Layer/Collector/PackageNameCollector.php +++ b/src/Core/Layer/Collector/PackageNameCollector.php @@ -2,36 +2,15 @@ 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\Ast\TokenReferenceMetaDatumInterface; use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; -use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; -use Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\NikicPhpParser; +use Qossmic\Deptrac\Core\Ast\MetaData\PackageName; 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) { @@ -53,44 +32,17 @@ protected function getPattern(array $config): string } /** - * @return array - * - * @throws CouldNotParseFileException + * @return string[] */ - private function getPackages(ClassLikeReference $reference): array + private function getPackages(TokenReferenceInterface $reference): array { - $docBlock = $this->getCommentDoc($reference); - - if (!$docBlock) { - return []; - } - - $tokens = new TokenIterator($this->lexer->tokenize($docBlock)); - $docNode = $this->docParser->parse($tokens); - + $packageNameMetaData = array_filter( + $reference->getMetaData(), + fn (TokenReferenceMetaDatumInterface $metaData) => $metaData instanceof PackageName + ); return array_map( - static fn (PhpDocTagNode $node) => (string) $node->value, - $docNode->getTagsByName('@package') + fn (PackageName $packageName) => $packageName->getPackageName(), + $packageNameMetaData ); } - - /** - * @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(); - } } diff --git a/tests/Core/Ast/Parser/NikicPhpParser/Fixtures/PackageNames.php b/tests/Core/Ast/Parser/NikicPhpParser/Fixtures/PackageNames.php new file mode 100644 index 000000000..35a941e1f --- /dev/null +++ b/tests/Core/Ast/Parser/NikicPhpParser/Fixtures/PackageNames.php @@ -0,0 +1,14 @@ +classLikeReferences; self::assertCount(0, $astClassReferences[0]->dependencies); } + + public function testParsePackageNames(): void + { + $filterPackageNames = function (TokenReferenceMetaDatumInterface $metaDatum) { + return $metaDatum instanceof PackageName; + }; + + $parser = new NikicPhpParser( + (new ParserFactory())->create(ParserFactory::ONLY_PHP7, new Lexer()), + new AstFileReferenceInMemoryCache(), + new TypeResolver(), + [] + ); + + $filePath = __DIR__ . '/Fixtures/PackageNames.php'; + $astFileReference = $parser->parseFile($filePath); + + $astClassReferences = $astFileReference->classLikeReferences; + self::assertCount(2, $astClassReferences); + + $packageNames = array_filter($astClassReferences[0]->getMetaData(), $filterPackageNames); + self::assertCount(1, $packageNames); + $this->assertSame('PackageA', $packageNames[0]->getPackageName()); + + $packageNames = array_filter($astClassReferences[1]->getMetaData(), $filterPackageNames); + self::assertCount(0, $packageNames); + } } diff --git a/tests/Core/Layer/Collector/PackageNameCollectorTest.php b/tests/Core/Layer/Collector/PackageNameCollectorTest.php index 1f5b82d62..75db53f38 100644 --- a/tests/Core/Layer/Collector/PackageNameCollectorTest.php +++ b/tests/Core/Layer/Collector/PackageNameCollectorTest.php @@ -1,122 +1,55 @@ astParser = $this->createMock(NikicPhpParser::class); - - $this->collector = new PackageNameCollector($this->astParser); + + $this->collector = new PackageNameCollector(); } - - public function provideSatisfy(): iterable + + public function dataProviderSatisfy(): iterable { - yield [ - ['value' => 'abc'], - $this->getPackageDocBlock(['abc', 'abcdef', 'xyz']), + yield 'matches package name with only partial name' => [ + ['value' => 'Package'], + ['MyPackage'], true, ]; - - yield [ - ['value' => 'abc'], - $this->getPackageDocBlock(['abc', 'xyz']), - true, + yield 'does not match partial name with full-string regex' => [ + ['value' => '^Package$'], + ['MyPackage'], + false, ]; - - yield [ - ['value' => 'abc'], - $this->getPackageDocBlock(['xyz']), + yield 'does not match missing package name' => [ + ['value' => 'Package'], + [], false, ]; } /** - * @dataProvider provideSatisfy + * @dataProvider dataProviderSatisfy */ - 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 + public function testSatisfy(array $config, array $packageNames, bool $expected): void { - return new Doc(sprintf( - " /**\n%s */", - implode('', array_map(static fn ($packageName) => ' * @package '.$packageName."\n", $packageNames)) - )); + $metaData = array_map(function ($packageName) { + return new PackageName($packageName); + }, $packageNames); + + $reference = $this->createMock(TokenReferenceInterface::class); + $reference->method('getMetaData') + ->willReturn($metaData); + + $this->assertSame($expected, $this->collector->satisfy($config, $reference)); } }