diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 719e0070..5bc736db 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,9 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Property Qossmic\\\\Deptrac\\\\Contract\\\\Analyser\\\\EventHelper\\:\\:\\$unmatchedSkippedViolation \\(array\\\\>\\) does not accept array\\, string\\>\\>\\.$#" - count: 1 - path: src/Contract/Analyser/EventHelper.php - message: "#^Method Qossmic\\\\Deptrac\\\\Contract\\\\Result\\\\OutputResult\\:\\:allOf\\(\\) should return list\\ but returns list\\\\.$#" @@ -20,11 +16,6 @@ parameters: count: 1 path: src/Core/Ast/AstMap/ReferenceBuilder.php - - - message: "#^Property Qossmic\\\\Deptrac\\\\Core\\\\Ast\\\\AstMap\\\\ReferenceBuilder\\:\\:\\$tokenTemplates \\(list\\\\) does not accept array\\, string\\>\\.$#" - count: 1 - path: src/Core/Ast/AstMap/ReferenceBuilder.php - - message: "#^Calling PHPStan\\\\Analyser\\\\ScopeContext\\:\\:enterClass\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#" count: 1 diff --git a/src/Core/Ast/Parser/Extractors/ClassLikeExtractor.php b/src/Core/Ast/Parser/Extractors/ClassLikeExtractor.php index 358d36ec..78bc0363 100644 --- a/src/Core/Ast/Parser/Extractors/ClassLikeExtractor.php +++ b/src/Core/Ast/Parser/Extractors/ClassLikeExtractor.php @@ -18,6 +18,8 @@ use Qossmic\Deptrac\Core\Ast\AstMap\ReferenceBuilder; use Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\NikicTypeResolver; use Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\TypeScope; +use Qossmic\Deptrac\Core\Ast\Parser\PhpStanParser\PhpStanContainerDecorator; +use Qossmic\Deptrac\Core\Ast\Parser\PhpStanParser\PhpStanTypeResolver; /** * @implements ReferenceExtractorInterface @@ -28,6 +30,8 @@ class ClassLikeExtractor implements ReferenceExtractorInterface private readonly PhpDocParser $docParser; public function __construct( + private readonly PhpStanContainerDecorator $phpStanContainer, + private readonly PhpStanTypeResolver $phpStanTypeResolver, private readonly NikicTypeResolver $typeResolver ) { $this->lexer = new Lexer(); @@ -92,7 +96,52 @@ public function processNodeWithClassicScope(Node $node, ReferenceBuilder $refere public function processNodeWithPhpStanScope(Node $node, ReferenceBuilder $referenceBuilder, Scope $scope): void { - // TODO: @Incomplete (Patrick Kusebauch @ 04.03.24) + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attribute) { + foreach ($this->phpStanTypeResolver->resolveType($attribute->name, $scope) as $classLikeName) { + $referenceBuilder->attribute($classLikeName, $attribute->getLine()); + } + } + } + + $docComment = $node->getDocComment(); + if (!$docComment instanceof Doc) { + return; + } + + $fileTypeMapper = $this->phpStanContainer->createFileTypeMapper(); + $classReflection = $scope->getClassReflection(); + assert(null !== $classReflection); + + $resolvedPhpDoc = $fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $classReflection->getName(), + $scope->getTraitReflection()?->getName(), + $scope->getFunction()?->getName(), + $docComment->getText(), + ); + + foreach ($resolvedPhpDoc->getMethodTags() as $methodTag) { + foreach ($methodTag->getParameters() as $methodTagParameter) { + foreach ($methodTagParameter->getType()->getReferencedClasses() as $referencedClass) { + $referenceBuilder->parameter($referencedClass, $node->getStartLine()); + } + } + foreach ($methodTag->getReturnType()->getReferencedClasses() as $referencedClass) { + $referenceBuilder->returnType($referencedClass, $node->getStartLine()); + } + } + + foreach ($resolvedPhpDoc->getPropertyTags() as $propertyTag) { + + $referencedClasses = array_merge( + $propertyTag->getReadableType()?->getReferencedClasses() ?? [], + $propertyTag->getWritableType()?->getReferencedClasses() ?? [], + ); + foreach (array_unique($referencedClasses) as $referencedClass) { + $referenceBuilder->variable($referencedClass, $node->getStartLine()); + } + } } public function getNodeType(): string diff --git a/src/Core/Ast/Parser/PhpStanParser/FileReferenceVisitor.php b/src/Core/Ast/Parser/PhpStanParser/FileReferenceVisitor.php index 505cdece..b91734a5 100644 --- a/src/Core/Ast/Parser/PhpStanParser/FileReferenceVisitor.php +++ b/src/Core/Ast/Parser/PhpStanParser/FileReferenceVisitor.php @@ -14,6 +14,11 @@ use PHPStan\Analyser\Scope; use PHPStan\Analyser\ScopeContext; use PHPStan\Analyser\ScopeFactory; +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 PHPStan\Reflection\ReflectionProvider; use Qossmic\Deptrac\Core\Ast\AstMap\File\FileReferenceBuilder; use Qossmic\Deptrac\Core\Ast\AstMap\ReferenceBuilder; @@ -28,6 +33,10 @@ class FileReferenceVisitor extends NodeVisitorAbstract private Scope $scope; + private Lexer $lexer; + + private PhpDocParser $docParser; + /** * @param ReferenceExtractorInterface<\PhpParser\Node> ...$dependencyResolvers */ @@ -41,6 +50,8 @@ public function __construct( $this->dependencyResolvers = $dependencyResolvers; $this->currentReference = $fileReferenceBuilder; $this->scope = $this->scopeFactory->create(ScopeContext::create($this->file)); + $this->lexer = new Lexer(); + $this->docParser = new PhpDocParser(new TypeParser(), new ConstExprParser()); } public function enterNode(Node $node) @@ -77,12 +88,13 @@ private function enterClassLike(ClassLike $node): void assert(null !== $name); $context = ScopeContext::create($this->file)->enterClass($this->reflectionProvider->getClass($name)); $this->scope = $this->scopeFactory->create($context); + $tags = $this->getTags($node); $this->currentReference = match (true) { - $node instanceof Interface_ => $this->fileReferenceBuilder->newInterface($name, [], []), - $node instanceof Class_ => $this->fileReferenceBuilder->newClass($name, [], []), - $node instanceof Trait_ => $this->fileReferenceBuilder->newTrait($name, [], []), - default => $this->fileReferenceBuilder->newClassLike($name, [], []) + $node instanceof Interface_ => $this->fileReferenceBuilder->newInterface($name, [], $tags), + $node instanceof Class_ => $this->fileReferenceBuilder->newClass($name, [], $tags), + $node instanceof Trait_ => $this->fileReferenceBuilder->newTrait($name, [], $tags), + default => $this->fileReferenceBuilder->newClassLike($name, [], $tags) }; } @@ -91,7 +103,7 @@ private function enterFunction(Node\Stmt\Function_ $node): void $name = $this->getReferenceName($node); assert(null !== $name); - $this->currentReference = $this->fileReferenceBuilder->newFunction($name); + $this->currentReference = $this->fileReferenceBuilder->newFunction($name, [], $this->getTags($node)); } private function getReferenceName(Node\Stmt\Function_|ClassLike $node): ?string @@ -106,4 +118,25 @@ private function getReferenceName(Node\Stmt\Function_|ClassLike $node): ?string return null; } + + /** + * @return array> + */ + private function getTags(ClassLike|Node\Stmt\Function_ $node): array + { + $docComment = $node->getDocComment(); + if (null === $docComment) { + return []; + } + + $tokens = new TokenIterator($this->lexer->tokenize($docComment->getText())); + $docNodeCrate = $this->docParser->parse($tokens); + + $tags = []; + foreach ($docNodeCrate->getTags() as $tag) { + $tags[$tag->name][] = (string) $tag->value; + } + + return $tags; + } } diff --git a/tests/Core/Ast/AstMapFlattenGeneratorTest.php b/tests/Core/Ast/AstMapFlattenGeneratorTest.php index f2d99704..bffcfba0 100644 --- a/tests/Core/Ast/AstMapFlattenGeneratorTest.php +++ b/tests/Core/Ast/AstMapFlattenGeneratorTest.php @@ -4,6 +4,7 @@ namespace Tests\Qossmic\Deptrac\Core\Ast; +use Closure; use LogicException; use PhpParser\Lexer; use PhpParser\ParserFactory; @@ -47,76 +48,10 @@ final class AstMapFlattenGeneratorTest extends TestCase { - private TraceableEventDispatcher $eventDispatcher; - private AstLoader $astLoader; - - protected function setUp(): void - { - parent::setUp(); - $parser = $this->providedData()[0]; - - $this->eventDispatcher = new TraceableEventDispatcher( - new EventDispatcher(), - new Stopwatch() - ); - $this->astLoader = new AstLoader( - $parser, - $this->eventDispatcher - ); - } - - /** - * @return list - */ - public static function createParser(): array - { - $phpStanContainer = new PhpStanContainerDecorator('', []); - $extractors = [ - new ClassExtractor(), - new InterfaceExtractor(), - ]; - $cache = new AstFileReferenceInMemoryCache(); - $parser = new NikicPhpParser( - (new ParserFactory())->create(ParserFactory::ONLY_PHP7, new Lexer()), $cache, $extractors - ); - - $phpstanParser = new PhpStanParser($phpStanContainer, $cache, $extractors); - - return [ - 'Nikic Parser' => [$parser], - 'PHPStan Parser' => [$phpstanParser], - ]; - } - - protected function tearDown(): void - { - parent::tearDown(); - - unset($this->astLoader); - unset($this->eventDispatcher); - } - - private function getAstMap(string $fixture): AstMap - { - return $this->astLoader->createAstMap([__DIR__.'/Fixtures/BasicInheritance/'.$fixture.'.php']); - } - - private function getInheritedInherits(string $class, AstMap $astMap): array - { - $inherits = []; - foreach ($astMap->getClassInherits(ClassLikeToken::fromFQCN($class)) as $v) { - if (count($v->getPath()) > 0) { - $inherits[] = (string) $v; - } - } - - return $inherits; - } - /** * @dataProvider createParser */ - public function testBasicInheritance(): void + public function testBasicInheritance(Closure $parserBuilder): void { $expectedEvents = [ PreCreateAstMapEvent::class, @@ -124,24 +59,35 @@ public function testBasicInheritance(): void PostCreateAstMapEvent::class, ]; - $astMap = $this->getAstMap('FixtureBasicInheritance'); + $filePath = __DIR__.'/Fixtures/BasicInheritance/FixtureBasicInheritance.php'; + $parser = $parserBuilder($filePath); + $eventDispatcher = new TraceableEventDispatcher( + new EventDispatcher(), + new Stopwatch() + ); + $astLoader = new AstLoader( + $parser, + $eventDispatcher + ); + + $astMap = $astLoader->createAstMap([$filePath]); - $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $dispatchedEvents = $eventDispatcher->getOrphanedEvents(); self::assertSame($expectedEvents, $dispatchedEvents); self::assertEqualsCanonicalizing( [], - $this->getInheritedInherits(FixtureBasicInheritanceA::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceA::class, $astMap) ); self::assertEqualsCanonicalizing( [], - $this->getInheritedInherits(FixtureBasicInheritanceB::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceB::class, $astMap) ); self::assertEqualsCanonicalizing( ['Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceA::6 (Extends) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceB::7 (Extends))'], - $this->getInheritedInherits(FixtureBasicInheritanceC::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceC::class, $astMap) ); self::assertEqualsCanonicalizing( @@ -149,7 +95,7 @@ public function testBasicInheritance(): void 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceA::6 (Extends) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceC::8 (Extends) -> Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceB::7 (Extends))', 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceB::7 (Extends) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceC::8 (Extends))', ], - $this->getInheritedInherits(FixtureBasicInheritanceD::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceD::class, $astMap) ); self::assertEqualsCanonicalizing( @@ -158,14 +104,14 @@ public function testBasicInheritance(): void 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceB::7 (Extends) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceD::9 (Extends) -> Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceC::8 (Extends))', 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceC::8 (Extends) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceD::9 (Extends))', ], - $this->getInheritedInherits(FixtureBasicInheritanceE::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceE::class, $astMap) ); } /** * @dataProvider createParser */ - public function testBasicInheritanceInterfaces(): void + public function testBasicInheritanceInterfaces(Closure $parserBuilder): void { $expectedEvents = [ PreCreateAstMapEvent::class, @@ -173,24 +119,34 @@ public function testBasicInheritanceInterfaces(): void PostCreateAstMapEvent::class, ]; - $astMap = $this->getAstMap('FixtureBasicInheritanceInterfaces'); + $filePath = __DIR__.'/Fixtures/BasicInheritance/FixtureBasicInheritanceInterfaces.php'; + $parser = $parserBuilder($filePath); + $eventDispatcher = new TraceableEventDispatcher( + new EventDispatcher(), + new Stopwatch() + ); + $astLoader = new AstLoader( + $parser, + $eventDispatcher + ); + $astMap = $astLoader->createAstMap([$filePath]); - $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $dispatchedEvents = $eventDispatcher->getOrphanedEvents(); self::assertSame($expectedEvents, $dispatchedEvents); self::assertEqualsCanonicalizing( [], - $this->getInheritedInherits(FixtureBasicInheritanceInterfaceA::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceInterfaceA::class, $astMap) ); self::assertEqualsCanonicalizing( [], - $this->getInheritedInherits(FixtureBasicInheritanceInterfaceB::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceInterfaceB::class, $astMap) ); self::assertEqualsCanonicalizing( ['Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceA::6 (Implements) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceB::7 (Implements))'], - $this->getInheritedInherits(FixtureBasicInheritanceInterfaceC::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceInterfaceC::class, $astMap) ); self::assertEqualsCanonicalizing( @@ -198,7 +154,7 @@ public function testBasicInheritanceInterfaces(): void 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceA::6 (Implements) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceC::8 (Implements) -> Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceB::7 (Implements))', 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceB::7 (Implements) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceC::8 (Implements))', ], - $this->getInheritedInherits(FixtureBasicInheritanceInterfaceD::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceInterfaceD::class, $astMap) ); self::assertEqualsCanonicalizing( @@ -207,14 +163,14 @@ public function testBasicInheritanceInterfaces(): void 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceB::7 (Implements) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceD::9 (Implements) -> Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceC::8 (Implements))', 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceC::8 (Implements) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\FixtureBasicInheritanceInterfaceD::9 (Implements))', ], - $this->getInheritedInherits(FixtureBasicInheritanceInterfaceE::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceInterfaceE::class, $astMap) ); } /** * @dataProvider createParser */ - public function testBasicMultipleInheritanceInterfaces(): void + public function testBasicMultipleInheritanceInterfaces(Closure $parserBuilder): void { $expectedEvents = [ PreCreateAstMapEvent::class, @@ -222,24 +178,34 @@ public function testBasicMultipleInheritanceInterfaces(): void PostCreateAstMapEvent::class, ]; - $astMap = $this->getAstMap('MultipleInheritanceInterfaces'); + $filePath = __DIR__.'/Fixtures/BasicInheritance/MultipleInheritanceInterfaces.php'; + $parser = $parserBuilder($filePath); + $eventDispatcher = new TraceableEventDispatcher( + new EventDispatcher(), + new Stopwatch() + ); + $astLoader = new AstLoader( + $parser, + $eventDispatcher + ); + $astMap = $astLoader->createAstMap([$filePath]); - $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $dispatchedEvents = $eventDispatcher->getOrphanedEvents(); self::assertSame($expectedEvents, $dispatchedEvents); self::assertEqualsCanonicalizing( [], - $this->getInheritedInherits(MultipleInteritanceA1::class, $astMap) + self::getInheritedInherits(MultipleInteritanceA1::class, $astMap) ); self::assertEqualsCanonicalizing( [], - $this->getInheritedInherits(MultipleInteritanceA2::class, $astMap) + self::getInheritedInherits(MultipleInteritanceA2::class, $astMap) ); self::assertEqualsCanonicalizing( [], - $this->getInheritedInherits(MultipleInteritanceA::class, $astMap) + self::getInheritedInherits(MultipleInteritanceA::class, $astMap) ); self::assertEqualsCanonicalizing( @@ -247,7 +213,7 @@ public function testBasicMultipleInheritanceInterfaces(): void 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\MultipleInteritanceA1::7 (Implements) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\MultipleInteritanceA::8 (Implements))', 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\MultipleInteritanceA2::7 (Implements) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\MultipleInteritanceA::8 (Implements))', ], - $this->getInheritedInherits(MultipleInteritanceB::class, $astMap) + self::getInheritedInherits(MultipleInteritanceB::class, $astMap) ); self::assertEqualsCanonicalizing( @@ -257,14 +223,14 @@ public function testBasicMultipleInheritanceInterfaces(): void 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\MultipleInteritanceA2::7 (Implements) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\MultipleInteritanceB::9 (Implements) -> Tests\Qossmic\Deptrac\Core\Ast\Fixtures\MultipleInteritanceA::8 (Implements))', 'Tests\Qossmic\Deptrac\Core\Ast\Fixtures\MultipleInteritanceA::8 (Implements) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\MultipleInteritanceB::9 (Implements))', ], - $this->getInheritedInherits(MultipleInteritanceC::class, $astMap) + self::getInheritedInherits(MultipleInteritanceC::class, $astMap) ); } /** * @dataProvider createParser */ - public function testBasicMultipleInheritanceWithNoise(): void + public function testBasicMultipleInheritanceWithNoise(Closure $parserBuilder): void { $expectedEvents = [ PreCreateAstMapEvent::class, @@ -272,30 +238,37 @@ public function testBasicMultipleInheritanceWithNoise(): void PostCreateAstMapEvent::class, ]; - $astMap = $this->getAstMap('FixtureBasicInheritanceWithNoise'); + $filePath = __DIR__.'/Fixtures/BasicInheritance/FixtureBasicInheritanceWithNoise.php'; + $parser = $parserBuilder($filePath); + $eventDispatcher = new TraceableEventDispatcher( + new EventDispatcher(), + new Stopwatch() + ); + $astLoader = new AstLoader( + $parser, + $eventDispatcher + ); + $astMap = $astLoader->createAstMap([$filePath]); - $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $dispatchedEvents = $eventDispatcher->getOrphanedEvents(); self::assertSame($expectedEvents, $dispatchedEvents); self::assertEqualsCanonicalizing( [], - $this->getInheritedInherits(FixtureBasicInheritanceWithNoiseA::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceWithNoiseA::class, $astMap) ); self::assertEqualsCanonicalizing( [], - $this->getInheritedInherits(FixtureBasicInheritanceWithNoiseB::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceWithNoiseB::class, $astMap) ); self::assertEqualsCanonicalizing( ['Tests\Qossmic\Deptrac\Core\Ast\Fixtures\BasicInheritance\FixtureBasicInheritanceWithNoiseA::18 (Extends) (path: Tests\Qossmic\Deptrac\Core\Ast\Fixtures\BasicInheritance\FixtureBasicInheritanceWithNoiseB::19 (Extends))'], - $this->getInheritedInherits(FixtureBasicInheritanceWithNoiseC::class, $astMap) + self::getInheritedInherits(FixtureBasicInheritanceWithNoiseC::class, $astMap) ); } - /** - * @dataProvider createParser - */ public function testSkipsErrorsAndDispatchesErrorEventAndReturnsEmptyAstMap(): void { $expectedEvents = [ @@ -304,7 +277,11 @@ public function testSkipsErrorsAndDispatchesErrorEventAndReturnsEmptyAstMap(): v PostCreateAstMapEvent::class, ]; $parser = $this->createMock(ParserInterface::class); - $astLoader = new AstLoader($parser, $this->eventDispatcher); + $eventDispatcher = new TraceableEventDispatcher( + new EventDispatcher(), + new Stopwatch() + ); + $astLoader = new AstLoader($parser, $eventDispatcher); $parser ->expects(self::atLeastOnce()) @@ -314,17 +291,18 @@ public function testSkipsErrorsAndDispatchesErrorEventAndReturnsEmptyAstMap(): v $astLoader->createAstMap([__DIR__.'/Fixtures/BasicInheritance/FixtureBasicInheritanceWithNoise.php']); - $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $dispatchedEvents = $eventDispatcher->getOrphanedEvents(); self::assertSame($expectedEvents, $dispatchedEvents); } - /** - * @dataProvider createParser - */ public function testThrowsOtherExceptions(): void { $parser = $this->createMock(ParserInterface::class); - $astLoader = new AstLoader($parser, $this->eventDispatcher); + $eventDispatcher = new TraceableEventDispatcher( + new EventDispatcher(), + new Stopwatch() + ); + $astLoader = new AstLoader($parser, $eventDispatcher); $parser ->expects(self::atLeastOnce()) @@ -337,4 +315,59 @@ public function testThrowsOtherExceptions(): void $astLoader->createAstMap([__DIR__.'/Fixtures/BasicInheritance/FixtureBasicInheritanceWithNoise.php']); } + + /** + * @return list + */ + public static function createParser(): array + { + return [ + 'Nikic Parser' => [self::createNikicParser(...)], + 'PHPStan Parser' => [self::createPhpStanParser(...)], + ]; + } + + public static function createPhpStanParser(string $filePath): PhpStanParser + { + $phpStanContainer = new PhpStanContainerDecorator(__DIR__, [$filePath]); + + $cache = new AstFileReferenceInMemoryCache(); + $extractors = [ + new ClassExtractor(), + new InterfaceExtractor(), + ]; + + return new PhpStanParser($phpStanContainer, $cache, $extractors); + } + + public static function createNikicParser(string $filePath): NikicPhpParser + { + $cache = new AstFileReferenceInMemoryCache(); + $extractors = [ + new ClassExtractor(), + new InterfaceExtractor(), + ]; + + return new NikicPhpParser( + (new ParserFactory())->create( + ParserFactory::ONLY_PHP7, + new Lexer() + ), $cache, $extractors + ); + } + + /** + * @return list + */ + private static function getInheritedInherits(string $class, AstMap $astMap): array + { + $inherits = []; + foreach ($astMap->getClassInherits(ClassLikeToken::fromFQCN($class)) as $v) { + if (count($v->getPath()) > 0) { + $inherits[] = (string) $v; + } + } + + return $inherits; + } } diff --git a/tests/Core/Ast/Parser/AnonymousClassExtractorTest.php b/tests/Core/Ast/Parser/AnonymousClassExtractorTest.php index 3d59afe8..d92f5dcf 100644 --- a/tests/Core/Ast/Parser/AnonymousClassExtractorTest.php +++ b/tests/Core/Ast/Parser/AnonymousClassExtractorTest.php @@ -4,6 +4,7 @@ namespace Tests\Qossmic\Deptrac\Core\Ast\Parser; +use Closure; use PhpParser\Lexer; use PhpParser\ParserFactory; use PHPUnit\Framework\TestCase; @@ -16,33 +17,13 @@ final class AnonymousClassExtractorTest extends TestCase { - /** - * @return list - */ - public static function createParser(): array - { - $phpStanContainer = new PhpStanContainerDecorator('', []); - $cache = new AstFileReferenceInMemoryCache(); - $extractors = [ - new AnonymousClassExtractor(), - ]; - $nikicPhpParser = new NikicPhpParser( - (new ParserFactory())->create(ParserFactory::ONLY_PHP7, new Lexer()), $cache, $extractors - ); - $phpstanParser = new PhpStanParser($phpStanContainer, $cache, $extractors); - - return [ - 'Nikic Parser' => [$nikicPhpParser], - 'PHPStan Parser' => [$phpstanParser], - ]; - } - /** * @dataProvider createParser */ - public function testPropertyDependencyResolving(ParserInterface $parser): void + public function testPropertyDependencyResolving(Closure $parserBuilder): void { $filePath = __DIR__.'/Fixtures/AnonymousClass.php'; + $parser = $parserBuilder($filePath); $astFileReference = $parser->parseFile($filePath); $astClassReferences = $astFileReference->classLikeReferences; @@ -70,4 +51,42 @@ public function testPropertyDependencyResolving(ParserInterface $parser): void self::assertSame(19, $dependencies[1]->fileOccurrence->line); self::assertSame('anonymous_class_implements', $dependencies[1]->type->value); } + + /** + * @return list + */ + public static function createParser(): array + { + return [ + 'Nikic Parser' => [self::createNikicParser(...)], + 'PHPStan Parser' => [self::createPhpStanParser(...)], + ]; + } + + public static function createPhpStanParser(string $filePath): PhpStanParser + { + $phpStanContainer = new PhpStanContainerDecorator(__DIR__, [$filePath]); + + $cache = new AstFileReferenceInMemoryCache(); + $extractors = [ + new AnonymousClassExtractor(), + ]; + + return new PhpStanParser($phpStanContainer, $cache, $extractors); + } + + public static function createNikicParser(string $filePath): NikicPhpParser + { + $cache = new AstFileReferenceInMemoryCache(); + $extractors = [ + new AnonymousClassExtractor(), + ]; + + return new NikicPhpParser( + (new ParserFactory())->create( + ParserFactory::ONLY_PHP7, + new Lexer() + ), $cache, $extractors + ); + } } diff --git a/tests/Core/Ast/Parser/ClassConstantExtractorTest.php b/tests/Core/Ast/Parser/ClassConstantExtractorTest.php index 41f2eb8e..62b70800 100644 --- a/tests/Core/Ast/Parser/ClassConstantExtractorTest.php +++ b/tests/Core/Ast/Parser/ClassConstantExtractorTest.php @@ -4,6 +4,7 @@ namespace Tests\Qossmic\Deptrac\Core\Ast\Parser; +use Closure; use PhpParser\Lexer; use PhpParser\ParserFactory; use PHPUnit\Framework\TestCase; @@ -19,9 +20,10 @@ final class ClassConstantExtractorTest extends TestCase /** * @dataProvider createParser */ - public function testPropertyDependencyResolving(ParserInterface $parser): void + public function testPropertyDependencyResolving(Closure $parserBuilder): void { $filePath = __DIR__.'/Fixtures/ClassConst.php'; + $parser = $parserBuilder($filePath); $astFileReference = $parser->parseFile($filePath); $astClassReferences = $astFileReference->classLikeReferences; @@ -45,19 +47,36 @@ public function testPropertyDependencyResolving(ParserInterface $parser): void */ public static function createParser(): array { - $phpStanContainer = new PhpStanContainerDecorator('', []); + return [ + 'Nikic Parser' => [self::createNikicParser(...)], + 'PHPStan Parser' => [self::createPhpStanParser(...)], + ]; + } + + public static function createPhpStanParser(string $filePath): PhpStanParser + { + $phpStanContainer = new PhpStanContainerDecorator(__DIR__, [$filePath]); + + $cache = new AstFileReferenceInMemoryCache(); $extractors = [ new ClassConstantExtractor(), ]; - $cache = new AstFileReferenceInMemoryCache(); - $parser = new NikicPhpParser( - (new ParserFactory())->create(ParserFactory::ONLY_PHP7, new Lexer()), $cache, $extractors - ); - $phpstanParser = new PhpStanParser($phpStanContainer, $cache, $extractors); - return [ - 'Nikic Parser' => [$parser], - 'PHPStan Parser' => [$phpstanParser], + return new PhpStanParser($phpStanContainer, $cache, $extractors); + } + + public static function createNikicParser(string $filePath): NikicPhpParser + { + $cache = new AstFileReferenceInMemoryCache(); + $extractors = [ + new ClassConstantExtractor(), ]; + + return new NikicPhpParser( + (new ParserFactory())->create( + ParserFactory::ONLY_PHP7, + new Lexer() + ), $cache, $extractors + ); } } diff --git a/tests/Core/Ast/Parser/ClassDocBlockExtractorTest.php b/tests/Core/Ast/Parser/ClassDocBlockExtractorTest.php index 317fef8d..05311204 100644 --- a/tests/Core/Ast/Parser/ClassDocBlockExtractorTest.php +++ b/tests/Core/Ast/Parser/ClassDocBlockExtractorTest.php @@ -15,6 +15,7 @@ use Qossmic\Deptrac\Core\Ast\Parser\ParserInterface; use Qossmic\Deptrac\Core\Ast\Parser\PhpStanParser\PhpStanContainerDecorator; use Qossmic\Deptrac\Core\Ast\Parser\PhpStanParser\PhpStanParser; +use Qossmic\Deptrac\Core\Ast\Parser\PhpStanParser\PhpStanTypeResolver; final class ClassDocBlockExtractorTest extends TestCase { @@ -52,7 +53,7 @@ public static function createParser(): array $typeResolver = new NikicTypeResolver(); $phpStanContainer = new PhpStanContainerDecorator('', []); $extractors = [ - new ClassLikeExtractor($typeResolver), + new ClassLikeExtractor($phpStanContainer, new PhpStanTypeResolver(), $typeResolver), ]; $cache = new AstFileReferenceInMemoryCache(); $parser = new NikicPhpParser( diff --git a/tests/Core/Ast/Parser/FunctionLikeExtractorTest.php b/tests/Core/Ast/Parser/FunctionLikeExtractorTest.php index e068af1f..d0b228e0 100644 --- a/tests/Core/Ast/Parser/FunctionLikeExtractorTest.php +++ b/tests/Core/Ast/Parser/FunctionLikeExtractorTest.php @@ -10,7 +10,9 @@ use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\DependencyToken; use Qossmic\Deptrac\Core\Ast\Parser\Cache\AstFileReferenceInMemoryCache; +use Qossmic\Deptrac\Core\Ast\Parser\Extractors\ClassLikeExtractor; use Qossmic\Deptrac\Core\Ast\Parser\Extractors\FunctionLikeExtractor; +use Qossmic\Deptrac\Core\Ast\Parser\Extractors\UseExtractor; use Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\NikicPhpParser; use Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\NikicTypeResolver; use Qossmic\Deptrac\Core\Ast\Parser\ParserInterface; @@ -23,9 +25,10 @@ final class FunctionLikeExtractorTest extends TestCase /** * @dataProvider createParser */ - public function testPropertyDependencyResolving(ParserInterface $parser): void + public function testPropertyDependencyResolving(\Closure $parserBuilder): void { $filePath = __DIR__.'/Fixtures/MethodSignatures.php'; + $parser = $parserBuilder($filePath); $astFileReference = $parser->parseFile($filePath); $astClassReferences = $astFileReference->classLikeReferences; @@ -76,21 +79,40 @@ static function (DependencyToken $dependency) { * @return list */ public static function createParser(): array + { + return [ + 'Nikic Parser' => [self::createNikicParser(...)], + 'PHPStan Parser' => [self::createPhpStanParser(...)], + ]; + } + + public static function createPhpStanParser(string $filePath): PhpStanParser { $typeResolver = new NikicTypeResolver(); - $phpStanContainer = new PhpStanContainerDecorator('', []); + $phpStanContainer = new PhpStanContainerDecorator(__DIR__, [$filePath]); + + $cache = new AstFileReferenceInMemoryCache(); $extractors = [ new FunctionLikeExtractor($typeResolver, new PhpStanTypeResolver()), ]; - $cache = new AstFileReferenceInMemoryCache(); - $parser = new NikicPhpParser( - (new ParserFactory())->create(ParserFactory::ONLY_PHP7, new Lexer()), $cache, $extractors - ); - $phpstanParser = new PhpStanParser($phpStanContainer, $cache, $extractors); - return [ - 'Nikic Parser' => [$parser], - 'PHPStan Parser' => [$phpstanParser], + return new PhpStanParser($phpStanContainer, $cache, $extractors); + } + + public static function createNikicParser(string $filePath): NikicPhpParser + { + $typeResolver = new NikicTypeResolver(); + + $cache = new AstFileReferenceInMemoryCache(); + $extractors = [ + new FunctionLikeExtractor($typeResolver, new PhpStanTypeResolver()), ]; + + return new NikicPhpParser( + (new ParserFactory())->create( + ParserFactory::ONLY_PHP7, + new Lexer() + ), $cache, $extractors + ); } } diff --git a/tests/Core/Ast/Parser/ParserTest.php b/tests/Core/Ast/Parser/ParserTest.php index 18407159..98e75f27 100644 --- a/tests/Core/Ast/Parser/ParserTest.php +++ b/tests/Core/Ast/Parser/ParserTest.php @@ -4,6 +4,7 @@ namespace Tests\Qossmic\Deptrac\Core\Ast\Parser; +use Closure; use PhpParser\Lexer; use PhpParser\ParserFactory; use PHPUnit\Framework\TestCase; @@ -15,6 +16,7 @@ use Qossmic\Deptrac\Core\Ast\Parser\ParserInterface; use Qossmic\Deptrac\Core\Ast\Parser\PhpStanParser\PhpStanContainerDecorator; use Qossmic\Deptrac\Core\Ast\Parser\PhpStanParser\PhpStanParser; +use Qossmic\Deptrac\Core\Ast\Parser\PhpStanParser\PhpStanTypeResolver; use stdClass; use TypeError; @@ -23,8 +25,9 @@ final class ParserTest extends TestCase /** * @dataProvider createParser */ - public function testParseWithInvalidData(ParserInterface $parser): void + public function testParseWithInvalidData(Closure $parserBuilder): void { + $parser = $parserBuilder(''); $this->expectException(TypeError::class); $parser->parseFile(new stdClass()); } @@ -32,9 +35,10 @@ public function testParseWithInvalidData(ParserInterface $parser): void /** * @dataProvider createParser */ - public function testParseDoesNotIgnoreUsesByDefault(ParserInterface $parser): void + public function testParseDoesNotIgnoreUsesByDefault(Closure $parserBuilder): void { $filePath = __DIR__.'/Fixtures/CountingUseStatements.php'; + $parser = $parserBuilder($filePath); self::assertCount(1, $parser->parseFile($filePath)->dependencies); } @@ -43,9 +47,10 @@ public function testParseDoesNotIgnoreUsesByDefault(ParserInterface $parser): vo * * @dataProvider createParser */ - public function testParseAttributes(ParserInterface $parser): void + public function testParseAttributes(Closure $parserBuilder): void { $filePath = __DIR__.'/Fixtures/Attributes.php'; + $parser = $parserBuilder($filePath); $astFileReference = $parser->parseFile($filePath); $astClassReferences = $astFileReference->classLikeReferences; self::assertCount(7, $astClassReferences[0]->dependencies); @@ -56,9 +61,10 @@ public function testParseAttributes(ParserInterface $parser): void /** * @dataProvider createParser */ - public function testParseTemplateTypes(ParserInterface $parser): void + public function testParseTemplateTypes(Closure $parserBuilder): void { $filePath = __DIR__.'/Fixtures/TemplateTypes.php'; + $parser = $parserBuilder($filePath); $astFileReference = $parser->parseFile($filePath); $astClassReferences = $astFileReference->classLikeReferences; self::assertCount(0, $astClassReferences[0]->dependencies); @@ -67,9 +73,10 @@ public function testParseTemplateTypes(ParserInterface $parser): void /** * @dataProvider createParser */ - public function testParseClassDocTags(ParserInterface $parser): void + public function testParseClassDocTags(Closure $parserBuilder): void { $filePath = __DIR__.'/Fixtures/DocTags.php'; + $parser = $parserBuilder($filePath); $astFileReference = $parser->parseFile($filePath); self::assertCount(2, $astFileReference->classLikeReferences); @@ -88,9 +95,10 @@ public function testParseClassDocTags(ParserInterface $parser): void /** * @dataProvider createParser */ - public function testParseFunctionDocTags(ParserInterface $parser): void + public function testParseFunctionDocTags(Closure $parserBuilder): void { $filePath = __DIR__.'/Fixtures/Functions.php'; + $parser = $parserBuilder($filePath); $astFileReference = $parser->parseFile($filePath); self::assertCount(2, $astFileReference->functionReferences); @@ -119,27 +127,43 @@ private function refsByName(array $refs): array * @return list */ public static function createParser(): array + { + return [ + 'Nikic Parser' => [self::createNikicParser(...)], + 'PHPStan Parser' => [self::createPhpStanParser(...)], + ]; + } + + public static function createPhpStanParser(string $filePath): PhpStanParser + { + $typeResolver = new NikicTypeResolver(); + $phpStanContainer = new PhpStanContainerDecorator(__DIR__, [$filePath]); + + $cache = new AstFileReferenceInMemoryCache(); + $extractors = [ + new UseExtractor(), + new ClassLikeExtractor($phpStanContainer, new PhpStanTypeResolver(), $typeResolver), + ]; + + return new PhpStanParser($phpStanContainer, $cache, $extractors); + } + + public static function createNikicParser(string $filePath): NikicPhpParser { $typeResolver = new NikicTypeResolver(); - $phpStanContainer = new PhpStanContainerDecorator('', []); + $phpStanContainer = new PhpStanContainerDecorator(__DIR__, [$filePath]); $cache = new AstFileReferenceInMemoryCache(); $extractors = [ new UseExtractor(), - new ClassLikeExtractor($typeResolver), + new ClassLikeExtractor($phpStanContainer, new PhpStanTypeResolver(), $typeResolver), ]; - $nikicPhpParser = new NikicPhpParser( + + return new NikicPhpParser( (new ParserFactory())->create( ParserFactory::ONLY_PHP7, new Lexer() ), $cache, $extractors ); - - $phpstanParser = new PhpStanParser($phpStanContainer, $cache, $extractors); - - return [ - 'Nikic Parser' => [$nikicPhpParser], - 'PHPStan Parser' => [$phpstanParser], - ]; } }