Skip to content

Commit

Permalink
[NodeAnalyzer] Use PHPStan ClassReflection to detect anonymous class …
Browse files Browse the repository at this point in the history
…on ClassAnalyzer (#3543)

* [NodeAnalyzer] Use PHPStan ClassReflection to detect anonymous class on ClassAnalyzer

* final touch: use nativeReflectionClass::isAnonymous() when possible

* final touch: check direct ClassReflection

* Final touch: clean up

* Final touch: clean up
  • Loading branch information
samsonasik committed Mar 31, 2023
1 parent 58d862b commit f7e1b1f
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 42 deletions.
26 changes: 13 additions & 13 deletions rules/CodingStyle/ClassNameImport/ShortNameResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
use PhpParser\Node\Stmt\Namespace_;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\CodingStyle\NodeAnalyzer\UseImportNameMatcher;
use Rector\Core\NodeAnalyzer\ClassAnalyzer;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\Util\StringUtils;
use Rector\Core\ValueObject\Application\File;
Expand Down Expand Up @@ -51,8 +51,7 @@ public function __construct(
private readonly NodeNameResolver $nodeNameResolver,
private readonly ReflectionProvider $reflectionProvider,
private readonly BetterNodeFinder $betterNodeFinder,
private readonly UseImportNameMatcher $useImportNameMatcher,
private readonly ClassAnalyzer $classAnalyzer
private readonly UseImportNameMatcher $useImportNameMatcher
) {
}

Expand Down Expand Up @@ -157,7 +156,7 @@ private function resolveForStmts(array $stmts): array
*/
private function resolveFromStmtsDocBlocks(array $stmts): array
{
$reflectionClass = $this->resolveNativeClassReflection($stmts);
$classReflection = $this->resolveClassReflection($stmts);

$shortNames = [];
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($stmts, function (Node $node) use (
Expand Down Expand Up @@ -194,13 +193,13 @@ static function ($node) use (&$shortNames) {
return null;
});

return $this->fqnizeShortNames($shortNames, $reflectionClass, $stmts);
return $this->fqnizeShortNames($shortNames, $classReflection, $stmts);
}

/**
* @param Node[] $stmts
*/
private function resolveNativeClassReflection(array $stmts): ?ReflectionClass
private function resolveClassReflection(array $stmts): ?ClassReflection
{
$firstClassLike = $this->betterNodeFinder->findFirstInstanceOf($stmts, ClassLike::class);
if (! $firstClassLike instanceof ClassLike) {
Expand All @@ -212,26 +211,27 @@ private function resolveNativeClassReflection(array $stmts): ?ReflectionClass
return null;
}

$classReflection = $this->reflectionProvider->getClass($className);
return $classReflection->getNativeReflection();
return $this->reflectionProvider->getClass($className);
}

/**
* @param string[] $shortNames
* @param Stmt[] $stmts
* @return array<string, string>
*/
private function fqnizeShortNames(array $shortNames, ?ReflectionClass $reflectionClass, array $stmts): array
private function fqnizeShortNames(array $shortNames, ?ClassReflection $classReflection, array $stmts): array
{
$shortNamesToFullyQualifiedNames = [];

$nativeReflectionClass = $classReflection instanceof ClassReflection && ! $classReflection->isAnonymous()
? $classReflection->getNativeReflection()
: null;

foreach ($shortNames as $shortName) {
$stmtsMatchedName = $this->useImportNameMatcher->matchNameWithStmts($shortName, $stmts);

if ($reflectionClass instanceof ReflectionClass && ! $this->classAnalyzer->isAnonymousClassName(
$reflectionClass->getShortName()
)) {
$fullyQualifiedName = Reflection::expandClassName($shortName, $reflectionClass);
if ($nativeReflectionClass instanceof ReflectionClass) {
$fullyQualifiedName = Reflection::expandClassName($shortName, $nativeReflectionClass);
} elseif (is_string($stmtsMatchedName)) {
$fullyQualifiedName = $stmtsMatchedName;
} else {
Expand Down
40 changes: 11 additions & 29 deletions src/NodeAnalyzer/ClassAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,33 @@
namespace Rector\Core\NodeAnalyzer;

use PhpParser\Node;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Stmt\Class_;
use Rector\Core\Util\StringUtils;
use Rector\NodeNameResolver\NodeNameResolver;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use Rector\NodeTypeResolver\Node\AttributeKey;

final class ClassAnalyzer
{
/**
* @var string
* @see https://regex101.com/r/FQH6RT/2
*/
private const ANONYMOUS_CLASS_REGEX = '#^AnonymousClass\w+$#';

public function __construct(
private readonly NodeNameResolver $nodeNameResolver
) {
}

public function isAnonymousClassName(string $className): bool
{
return StringUtils::isMatch($className, self::ANONYMOUS_CLASS_REGEX);
}

public function isAnonymousClass(Node $node): bool
{
if (! $node instanceof Class_) {
return false;
}

$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if (! $parentNode instanceof New_) {
return false;
}

if ($node->isAnonymous()) {
return true;
}

$className = $this->nodeNameResolver->getName($node);
if ($className === null) {
return true;
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return false;
}

$classReflection = $scope->getClassReflection();
if ($classReflection instanceof ClassReflection) {
return $classReflection->isAnonymous();
}

// match PHPStan pattern for anonymous classes
return StringUtils::isMatch($className, self::ANONYMOUS_CLASS_REGEX);
return false;
}
}

0 comments on commit f7e1b1f

Please sign in to comment.