Skip to content

Commit

Permalink
[Core] Add ExternalFullyQualifiedAnalyzer to check external FullyQual…
Browse files Browse the repository at this point in the history
…ified extends/interface/traits (#164)

Co-authored-by: Tomas Votruba <tomas.vot@gmail.com>
  • Loading branch information
samsonasik and TomasVotruba committed Jun 8, 2021
1 parent 1d2b324 commit ca8f75f
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 44 deletions.
5 changes: 5 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Rector\Restoration\Rector\ClassMethod\InferParamFromClassMethodReturnRector;
use Rector\Restoration\ValueObject\InferParamFromClassMethodReturn;
use Rector\Set\ValueObject\SetList;
use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector;
use Rector\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector;
use Rector\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector;
Expand Down Expand Up @@ -117,6 +118,10 @@
__DIR__ . '/src/DependencyInjection/Loader/ConfigurableCallValuesCollectingPhpFileLoader.php',
],

AddVoidReturnTypeWhereNoReturnRector::class => [
__DIR__ . '/src/DependencyInjection/Loader/ConfigurableCallValuesCollectingPhpFileLoader.php',
],

// test paths
'*/Fixture/*',
'*/Fixture*/*',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Rector\Tests\DowngradePhp72\Rector\Class_\DowngradeParameterTypeWideningRector\Fixture;

use Rector\Tests\DowngradePhp72\Rector\Class_\DowngradeParameterTypeWideningRector\Source\NullableStringTrait;

final class SkipNullableStringFromExternalTrait
{
use NullableStringTrait;

public function load(string $value = null)
{
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\DowngradePhp72\Rector\Class_\DowngradeParameterTypeWideningRector\Source;

trait NullableStringTrait
{
public function load(string $value = null)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@

namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;

use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Source\ExternalForVoid;

final class SkipFromExternalVoid extends PhpFileLoader
final class SkipFromExternalVoid extends ExternalForVoid
{
public function import(
$resource,
string $type = null,
$ignoreErrors = false,
string $sourceResource = null,
$exclude = null
)
public function run()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Source\ExternalForVoidInterface;

final class SkipFromExternalVoidInterface implements ExternalForVoidInterface
{
public function run()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Source\ExternalForVoidTrait;

final class SkipFromExternalVoidTrait
{
use ExternalForVoidTrait;

public function run()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Source;

class ExternalForVoid
{
public function run()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Source;

interface ExternalForVoidInterface
{
public function run();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\Source;

trait ExternalForVoidTrait
{
public function run()
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Rector\DowngradePhp72\Rector\Class_;

use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
Expand All @@ -14,6 +13,7 @@
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\NodeAnalyzer\ExternalFullyQualifiedAnalyzer;
use Rector\Core\Rector\AbstractRector;
use Rector\DowngradePhp72\NodeAnalyzer\ClassLikeWithTraitsClassMethodResolver;
use Rector\DowngradePhp72\NodeAnalyzer\ParamContravariantDetector;
Expand All @@ -37,7 +37,8 @@ public function __construct(
private ParentChildClassMethodTypeResolver $parentChildClassMethodTypeResolver,
private NativeParamToPhpDocDecorator $nativeParamToPhpDocDecorator,
private ParamContravariantDetector $paramContravariantDetector,
private TypeFactory $typeFactory
private TypeFactory $typeFactory,
private ExternalFullyQualifiedAnalyzer $externalFullyQualifiedAnalyzer
) {
}

Expand Down Expand Up @@ -106,7 +107,7 @@ public function refactor(Node $node): ?Node
return null;
}

if ($this->hasExtendExternal($node)) {
if ($this->externalFullyQualifiedAnalyzer->hasExternalFullyQualifieds($node)) {
return null;
}

Expand Down Expand Up @@ -135,22 +136,6 @@ public function refactor(Node $node): ?Node
return null;
}

/**
* @param Class_|Interface_ $node
*/
private function hasExtendExternal(Node $node): bool
{
if ($node->extends instanceof FullyQualified) {
$className = (string) $this->getName($node->extends);
$parentFound = (bool) $this->nodeRepository->findClass($className);
if (! $parentFound) {
return true;
}
}

return false;
}

/**
* The topmost class is the source of truth, so we go only down to avoid up/down collission
* @param ClassReflection[] $ancestors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
Expand All @@ -16,6 +15,7 @@
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Core\NodeAnalyzer\ExternalFullyQualifiedAnalyzer;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\NodeTypeResolver\Node\AttributeKey;
Expand Down Expand Up @@ -47,7 +47,8 @@ public function __construct(
private ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard,
private VendorLockResolver $vendorLockResolver,
private PhpParserTypeAnalyzer $phpParserTypeAnalyzer,
private ObjectTypeComparator $objectTypeComparator
private ObjectTypeComparator $objectTypeComparator,
private ExternalFullyQualifiedAnalyzer $externalFullyQualifiedAnalyzer
) {
}

Expand Down Expand Up @@ -242,14 +243,13 @@ private function isExternalVoid(FunctionLike $functionLike, Node $inferredReturn
return false;
}

if (! $classLike->extends instanceof FullyQualified) {
return false;
}

$className = (string) $this->getName($classLike->extends);
$parentFound = (bool) $this->nodeRepository->findClass($className);

return $functionLike->returnType === null && ! $parentFound && $this->isName($inferredReturnNode, 'void');
$hasExternalClassOrInterfaceOrTrait = $this->externalFullyQualifiedAnalyzer->hasExternalFullyQualifieds(
$classLike
);
return $functionLike->returnType === null && $hasExternalClassOrInterfaceOrTrait && $this->isName(
$inferredReturnNode,
'void'
);
}

private function isNullableTypeSubType(Type $currentType, Type $inferedType): bool
Expand Down
11 changes: 5 additions & 6 deletions rules/TypeDeclaration/TypeInferer/SilentVoidResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\Yield_;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
Expand All @@ -18,13 +17,15 @@
use PhpParser\Node\Stmt\Throw_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\Stmt\TryCatch;
use Rector\Core\NodeAnalyzer\ExternalFullyQualifiedAnalyzer;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeTypeResolver\Node\AttributeKey;

final class SilentVoidResolver
{
public function __construct(
private BetterNodeFinder $betterNodeFinder
private BetterNodeFinder $betterNodeFinder,
private ExternalFullyQualifiedAnalyzer $externalFullyQualifiedAnalyzer
) {
}

Expand All @@ -46,10 +47,8 @@ public function hasExclusiveVoid(ClassMethod | Closure | Function_ $functionLike
if ($this->betterNodeFinder->hasInstancesOf((array) $functionLike->stmts, [Yield_::class])) {
return false;
}
if ($classLike->extends instanceof FullyQualified) {
return false;
}
if ($classLike->getTraitUses() !== []) {

if ($this->externalFullyQualifiedAnalyzer->hasExternalFullyQualifieds($classLike)) {
return false;
}

Expand Down
106 changes: 106 additions & 0 deletions src/NodeAnalyzer/ExternalFullyQualifiedAnalyzer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

declare(strict_types=1);

namespace Rector\Core\NodeAnalyzer;

use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\TraitUse;
use Rector\NodeCollector\NodeCollector\NodeRepository;
use Rector\NodeNameResolver\NodeNameResolver;

final class ExternalFullyQualifiedAnalyzer
{
public function __construct(
private NodeNameResolver $nodeNameResolver,
private NodeRepository $nodeRepository
) {
}

public function hasExternalFullyQualifieds(ClassLike $classLike): bool
{
if ($classLike instanceof Class_ || $classLike instanceof Interface_) {
$extends = $classLike->extends ?? [];
} else {
$extends = [];
}

/** @var FullyQualified[] $extends */
$extends = $extends instanceof FullyQualified
? [$extends]
: $extends;

/** @var FullyQualified[] $implements */
$implements = $classLike instanceof Class_ ? $classLike->implements : [];

$parentClassesAndInterfaces = array_merge($extends, $implements);
$hasExternalClassOrInterface = $this->hasExternalClassOrInterface($parentClassesAndInterfaces);
if ($hasExternalClassOrInterface) {
return true;
}

/** @var TraitUse[] $traitUses */
$traitUses = $classLike->getTraitUses();
return $this->hasExternalTrait($traitUses);
}

/**
* @param FullyQualified[] $fullyQualifiedClassLikes
*/
private function hasExternalClassOrInterface(array $fullyQualifiedClassLikes): bool
{
if ($fullyQualifiedClassLikes === []) {
return false;
}

foreach ($fullyQualifiedClassLikes as $fullyQualifiedClassLike) {
/** @var string $className */
$className = $this->nodeNameResolver->getName($fullyQualifiedClassLike);
$isClassFound = (bool) $this->nodeRepository->findClass($className);
$isInterfaceFound = (bool) $this->nodeRepository->findInterface($className);
if ($isClassFound) {
continue;
}
if ($isInterfaceFound) {
continue;
}

return true;
}

return false;
}

/**
* @param TraitUse[] $traitUses
*/
private function hasExternalTrait(array $traitUses): bool
{
if ($traitUses === []) {
return false;
}

foreach ($traitUses as $traitUse) {
$traits = $traitUse->traits;

foreach ($traits as $trait) {
if (! $trait instanceof FullyQualified) {
return false;
}

/** @var string $traitName */
$traitName = $this->nodeNameResolver->getName($trait);
$isTraitFound = (bool) $this->nodeRepository->findTrait($traitName);

if (! $isTraitFound) {
return true;
}
}
}

return false;
}
}

0 comments on commit ca8f75f

Please sign in to comment.