Skip to content

Commit

Permalink
Decopule helper template/mixin services from PHPStanNodeScopeResolver (
Browse files Browse the repository at this point in the history
…#1067)

Co-authored-by: GitHub Action <action@github.com>
  • Loading branch information
TomasVotruba and actions-user authored Oct 26, 2021
1 parent ceccbad commit e060d00
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 197 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-nette": "^0.12.21",
"phpunit/phpunit": "^9.5",
"rector/phpstan-rules": "^0.4.5",
"rector/phpstan-rules": "^0.4.6",
"rector/rector-generator": "^0.4.1",
"spatie/enum": "^3.9",
"symplify/coding-standard": "^9.5",
Expand Down
79 changes: 79 additions & 0 deletions packages/NodeTypeResolver/PHPStan/CollisionGuard/MixinGuard.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace Rector\NodeTypeResolver\PHPStan\CollisionGuard;

use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use Rector\Core\PhpParser\Node\BetterNodeFinder;

final class MixinGuard
{
public function __construct(
private BetterNodeFinder $betterNodeFinder,
private ReflectionProvider $reflectionProvider,
) {
}

/**
* @param Stmt[] $stmts
*/
public function containsMixinPhpDoc(array $stmts): bool
{
return (bool) $this->betterNodeFinder->findFirst($stmts, function (Node $node): bool {
if (! $node instanceof FullyQualified && ! $node instanceof Class_) {
return false;
}

if ($node instanceof Class_ && $node->isAnonymous()) {
return false;
}

$className = $node instanceof FullyQualified ? $node->toString() : $node->namespacedName->toString();

return $this->isCircularMixin($className);
});
}

private function isCircularMixin(string $className): bool
{
// fix error in parallel test
// use function_exists on purpose as using reflectionProvider broke the test in parallel
if (function_exists($className)) {
return false;
}

$hasClass = $this->reflectionProvider->hasClass($className);

if (! $hasClass) {
return false;
}

$classReflection = $this->reflectionProvider->getClass($className);
if ($classReflection->isBuiltIn()) {
return false;
}

foreach ($classReflection->getMixinTags() as $mixinTag) {
$type = $mixinTag->getType();
if (! $type instanceof ObjectType) {
return false;
}

if ($type->getClassName() === $className) {
return true;
}

if ($this->isCircularMixin($type->getClassName())) {
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace Rector\NodeTypeResolver\PHPStan\CollisionGuard;

use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
use Rector\Core\Application\FileProcessor;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use ReflectionClass;
use Symplify\SmartFileSystem\SmartFileSystem;

final class TemplateExtendsGuard
{
public function __construct(
private BetterNodeFinder $betterNodeFinder,
private SmartFileSystem $smartFileSystem,
) {
}

/**
* Thist needs to be checked early before `@mixin` check as
* ReflectionProvider already hang when check class with `@template-extends`
*
* @see https://github.com/phpstan/phpstan/issues/3865 in PHPStan
* @param Stmt[] $nodes
*/
public function containsTemplateExtendsPhpDoc(array $nodes, string $currentFileName): bool
{
return (bool) $this->betterNodeFinder->findFirst($nodes, function (Node $node) use ($currentFileName): bool {
if (! $node instanceof FullyQualified) {
return false;
}

$className = $node->toString();

// fix error in parallel test
// use function_exists on purpose as using reflectionProvider broke the test in parallel
if (function_exists($className)) {
return false;
}

// use class_exists as PHPStan ReflectionProvider hang on check className with `@template-extends`
if (! class_exists($className)) {
return false;
}

// use native ReflectionClass as PHPStan ReflectionProvider hang on check className with `@template-extends`
$reflectionClass = new ReflectionClass($className);
if ($reflectionClass->isInternal()) {
return false;
}

$fileName = (string) $reflectionClass->getFileName();
if (! $this->smartFileSystem->exists($fileName)) {
return false;
}

// already checked in FileProcessor::parseFileInfoToLocalCache()
if ($fileName === $currentFileName) {
return false;
}

$fileContents = $this->smartFileSystem->readFile($fileName);
return (bool) Strings::match($fileContents, FileProcessor::TEMPLATE_EXTENDS_REGEX);
});
}
}
128 changes: 6 additions & 122 deletions packages/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@

use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\NodeTraverser;
use PhpParser\Parser;
use PHPStan\AnalysedCodeException;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
Expand All @@ -23,22 +21,18 @@
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
use PHPStan\Node\UnreachableStatementNode;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use Rector\Caching\Detector\ChangedFilesDetector;
use Rector\Caching\FileSystem\DependencyResolver;
use Rector\Core\Application\FileProcessor;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
use Rector\Core\StaticReflection\SourceLocator\ParentAttributeSourceLocator;
use Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator;
use Rector\Core\Stubs\DummyTraitClass;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PHPStan\CollisionGuard\MixinGuard;
use Rector\NodeTypeResolver\PHPStan\CollisionGuard\TemplateExtendsGuard;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor;
use ReflectionClass;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
use Symplify\SmartFileSystem\SmartFileInfo;
use Symplify\SmartFileSystem\SmartFileSystem;
use Throwable;

/**
Expand Down Expand Up @@ -69,14 +63,8 @@ public function __construct(
private PrivatesAccessor $privatesAccessor,
private RenamedClassesSourceLocator $renamedClassesSourceLocator,
private ParentAttributeSourceLocator $parentAttributeSourceLocator,
private BetterNodeFinder $betterNodeFinder,
/**
* use \PhpParser\Parser on purpose instead of extended \Rector\Core\PhpParser\Parser\Parser
* as detecting `@template-extends` too early on dependent files
*/
private Parser $parser,
private BetterStandardPrinter $betterStandardPrinter,
private SmartFileSystem $smartFileSystem
private TemplateExtendsGuard $templateExtendsGuard,
private MixinGuard $mixinGuard,
) {
}

Expand Down Expand Up @@ -128,60 +116,13 @@ public function processNodes(array $nodes, SmartFileInfo $smartFileInfo): array

// it needs to be checked early before `@mixin` check as
// ReflectionProvider already hang when check class with `@template-extends`
if ($this->isTemplateExtendsInSource($nodes, $smartFileInfo->getFilename())) {
if ($this->templateExtendsGuard->containsTemplateExtendsPhpDoc($nodes, $smartFileInfo->getFilename())) {
return $nodes;
}

return $this->processNodesWithMixinHandling($smartFileInfo, $nodes, $scope, $nodeCallback);
}

/**
* @param Node[] $nodes
*/
private function isTemplateExtendsInSource(array $nodes, string $currentFileName): bool
{
return (bool) $this->betterNodeFinder->findFirst($nodes, function (Node $node) use ($currentFileName): bool {
if (! $node instanceof FullyQualified) {
return false;
}

$className = $node->toString();

// fix error in parallel test
// use function_exists on purpose as using reflectionProvider broke the test in parallel
if (function_exists($className)) {
return false;
}

// use class_exists as PHPStan ReflectionProvider hang on check className with `@template-extends`
if (! class_exists($className)) {
return false;
}

// use native ReflectionClass as PHPStan ReflectionProvider hang on check className with `@template-extends`
$reflectionClass = new ReflectionClass($className);
if ($reflectionClass->isInternal()) {
return false;
}

$fileName = (string) $reflectionClass->getFileName();
if (! $this->smartFileSystem->exists($fileName)) {
return false;
}

// already checked in FileProcessor::parseFileInfoToLocalCache()
if ($fileName === $currentFileName) {
return false;
}

$content = $this->smartFileSystem->readFile($fileName);
$fileNodes = $this->parser->parse($content);

$print = $this->betterStandardPrinter->print($fileNodes);
return (bool) Strings::match($print, FileProcessor::TEMPLATE_EXTENDS_REGEX);
});
}

/**
* @param Stmt[] $nodes
* @return Stmt[]
Expand All @@ -192,7 +133,7 @@ private function processNodesWithMixinHandling(
MutatingScope $mutatingScope,
callable $nodeCallback
): array {
if ($this->isMixinInSource($nodes)) {
if ($this->mixinGuard->containsMixinPhpDoc($nodes)) {
return $nodes;
}

Expand All @@ -213,63 +154,6 @@ private function processNodesWithMixinHandling(
return $nodes;
}

/**
* @param Node[] $nodes
*/
private function isMixinInSource(array $nodes): bool
{
return (bool) $this->betterNodeFinder->findFirst($nodes, function (Node $node): bool {
if (! $node instanceof FullyQualified && ! $node instanceof Class_) {
return false;
}

if ($node instanceof Class_ && $node->isAnonymous()) {
return false;
}

$className = $node instanceof FullyQualified ? $node->toString() : $node->namespacedName->toString();

return $this->isCircularMixin($className);
});
}

private function isCircularMixin(string $className): bool
{
// fix error in parallel test
// use function_exists on purpose as using reflectionProvider broke the test in parallel
if (function_exists($className)) {
return false;
}

$hasClass = $this->reflectionProvider->hasClass($className);

if (! $hasClass) {
return false;
}

$classReflection = $this->reflectionProvider->getClass($className);
if ($classReflection->isBuiltIn()) {
return false;
}

foreach ($classReflection->getMixinTags() as $mixinTag) {
$type = $mixinTag->getType();
if (! $type instanceof ObjectType) {
return false;
}

if ($type->getClassName() === $className) {
return true;
}

if ($this->isCircularMixin($type->getClassName())) {
return true;
}
}

return false;
}

/**
* @param Node[] $nodes
*/
Expand Down
Loading

0 comments on commit e060d00

Please sign in to comment.