Skip to content

Commit

Permalink
ClassHierarchy: fix missing parts of vendor hierarchy (#136)
Browse files Browse the repository at this point in the history
  • Loading branch information
janedbal authored Dec 31, 2024
1 parent fe15bef commit d9aa188
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 36 deletions.
51 changes: 19 additions & 32 deletions src/Collector/ClassDefinitionCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use LogicException;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Enum_;
Expand All @@ -14,6 +13,8 @@
use PhpParser\Node\Stmt\TraitUseAdaptation\Precedence;
use PHPStan\Analyser\Scope;
use PHPStan\Collectors\Collector;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use ShipMonk\PHPStan\DeadCode\Enum\ClassLikeKind;
use ShipMonk\PHPStan\DeadCode\Enum\Visibility;
use function array_fill_keys;
Expand All @@ -33,6 +34,13 @@
class ClassDefinitionCollector implements Collector
{

private ReflectionProvider $reflectionProvider;

public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}

public function getNodeType(): string
{
return ClassLike::class;
Expand Down Expand Up @@ -61,6 +69,7 @@ public function processNode(

$kind = $this->getKind($node);
$typeName = $node->namespacedName->toString();
$reflection = $this->reflectionProvider->getClass($typeName);

$methods = [];

Expand All @@ -87,54 +96,32 @@ public function processNode(
'name' => $typeName,
'methods' => $methods,
'constants' => $constants,
'parents' => $this->getParents($node),
'parents' => $this->getParents($reflection),
'traits' => $this->getTraits($node),
'interfaces' => $this->getInterfaces($node),
'interfaces' => $this->getInterfaces($reflection),
];
}

/**
* @return array<string, null>
*/
private function getParents(ClassLike $node): array
private function getParents(ClassReflection $reflection): array
{
if ($node instanceof Class_) {
if ($node->extends === null) {
return [];
}
$parents = [];

return [$node->extends->toString() => null];
foreach ($reflection->getParentClassesNames() as $parent) {
$parents[$parent] = null;
}

if ($node instanceof Interface_) {
return array_fill_keys(
array_map(
static fn(Name $name) => $name->toString(),
$node->extends,
),
null,
);
}

return [];
return $parents;
}

/**
* @return array<string, null>
*/
private function getInterfaces(ClassLike $node): array
private function getInterfaces(ClassReflection $reflection): array
{
if ($node instanceof Class_ || $node instanceof Enum_) {
return array_fill_keys(
array_map(
static fn(Name $name) => $name->toString(),
$node->implements,
),
null,
);
}

return [];
return array_fill_keys(array_map(static fn (ClassReflection $reflection) => $reflection->getName(), $reflection->getInterfaces()), null);
}

/**
Expand Down
3 changes: 0 additions & 3 deletions src/Rule/DeadCodeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,6 @@ private function fillClassHierarchy(string $typeName, array $ancestorNames): voi
{
foreach ($ancestorNames as $ancestorName) {
$this->classHierarchy->registerClassPair($ancestorName, $typeName);

$this->fillClassHierarchy($typeName, $this->getAncestorNames($ancestorName));
}
}

Expand Down Expand Up @@ -554,7 +552,6 @@ private function getAncestorNames(string $typeName): array
{
return array_merge(
array_keys($this->typeDefinitions[$typeName]['parents'] ?? []),
array_keys($this->typeDefinitions[$typeName]['traits'] ?? []),
array_keys($this->typeDefinitions[$typeName]['interfaces'] ?? []),
);
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Rule/DeadCodeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ protected function getCollectors(): array
$reflectionProvider,
$this->getMemberUsageProviders(),
),
new ClassDefinitionCollector(),
new ClassDefinitionCollector(self::createReflectionProvider()),
new MethodCallCollector($this->createUsageOriginDetector(), $this->trackMixedAccess),
new ConstantFetchCollector($this->createUsageOriginDetector(), $reflectionProvider, $this->trackMixedAccess),
];
Expand Down Expand Up @@ -272,6 +272,7 @@ public static function provideFiles(): iterable
yield 'method-mixed' => [__DIR__ . '/data/methods/mixed/tracked.php'];
yield 'method-new-in-initializers' => [__DIR__ . '/data/methods/new-in-initializers.php'];
yield 'method-first-class-callable' => [__DIR__ . '/data/methods/first-class-callable.php'];
yield 'method-hierarchy-in-vendor' => [__DIR__ . '/data/methods/hierarchy-in-vendor.php'];
yield 'method-overwriting-1' => [__DIR__ . '/data/methods/overwriting-methods-1.php'];
yield 'method-overwriting-2' => [__DIR__ . '/data/methods/overwriting-methods-2.php'];
yield 'method-overwriting-3' => [__DIR__ . '/data/methods/overwriting-methods-3.php'];
Expand Down
18 changes: 18 additions & 0 deletions tests/Rule/data/methods/hierarchy-in-vendor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace HierachyInVendor;

use PHPUnit\Framework\TestCase;
use ShipMonk\PHPStan\DeadCode\Rule\RuleTestCase;

class SomeTest extends RuleTestCase {

public static function someMethod(): void {}
}

/**
* @param class-string<TestCase> $class
*/
function test(string $class): void {
$class::someMethod();
}
2 changes: 2 additions & 0 deletions tests/Rule/data/providers/reflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Reflection;

use PHPUnit\Framework\TestCase;
use ShipMonk\PHPStan\DeadCode\Rule\RuleTestCase;

interface MyParent
{
Expand Down

0 comments on commit d9aa188

Please sign in to comment.