use templates (#5116)

This commit is contained in:
Tomas Votruba 2021-01-08 23:30:33 +01:00 committed by GitHub
parent bd62164c85
commit bc0113dbd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 235 additions and 64 deletions

View File

@ -88,7 +88,9 @@ abstract class AbstractPhpDocInfoTest extends AbstractKernelTestCase
}
/**
* @param class-string $nodeType
* @template T as Node
* @param class-string<T> $nodeType
* @return T
*/
private function parseFileAndGetFirstNodeOfType(SmartFileInfo $fileInfo, string $nodeType): Node
{

View File

@ -31,7 +31,7 @@ final class ScopeNestingComparator
public function isNodeConditionallyScoped(Node $node): bool
{
$foundParentType = $this->betterNodeFinder->findFirstParentInstanceOf(
$foundParentType = $this->betterNodeFinder->findParentTypes(
$node,
ControlStructure::CONDITIONAL_NODE_SCOPE_TYPES + [FunctionLike::class]
);
@ -45,6 +45,6 @@ final class ScopeNestingComparator
private function findParentControlStructure(Node $node): ?Node
{
return $this->betterNodeFinder->findFirstParentInstanceOf($node, ControlStructure::BREAKING_SCOPE_NODE_TYPES);
return $this->betterNodeFinder->findParentTypes($node, ControlStructure::BREAKING_SCOPE_NODE_TYPES);
}
}

View File

@ -38,7 +38,9 @@ abstract class AbstractNodeTypeResolverTest extends AbstractKernelTestCase
}
/**
* @return Node[]
* @template T as Node
* @param class-string<T> $type
* @return T[]
*/
protected function getNodesForFileOfType(string $file, string $type): array
{

View File

@ -136,12 +136,12 @@ final class NodesToRemoveCollector implements NodeCollectorInterface
return false;
}
$hasArgParent = (bool) $this->betterNodeFinder->findFirstParentInstanceOf($variable, Arg::class);
$hasArgParent = (bool) $this->betterNodeFinder->findParentType($variable, Arg::class);
if (! $hasArgParent) {
return false;
}
return ! (bool) $this->betterNodeFinder->findFirstParentInstanceOf($variable, [StaticCall::class]);
return ! (bool) $this->betterNodeFinder->findParentType($variable, StaticCall::class);
});
}

View File

@ -86,10 +86,7 @@ final class ReadWritePropertyAnalyzer
private function isNotInsideIssetUnset(ArrayDimFetch $arrayDimFetch): bool
{
return ! (bool) $this->betterNodeFinder->findFirstParentInstanceOf(
$arrayDimFetch,
[Isset_::class, Unset_::class]
);
return ! (bool) $this->betterNodeFinder->findParentTypes($arrayDimFetch, [Isset_::class, Unset_::class]);
}
private function unwrapPostPreIncDec(Node $node): Node

View File

@ -5,10 +5,12 @@ declare(strict_types=1);
namespace Rector\StaticTypeMapper\Naming;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use PHPStan\Analyser\NameScope;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -87,16 +89,34 @@ final class NameScopeFactory
private function templateTemplateTypeMap(Node $node): TemplateTypeMap
{
$phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
$nodeTemplateTypes = $this->resolveTemplateTypesFromNode($node);
$templateTypes = [];
if ($phpDocInfo instanceof PhpDocInfo) {
foreach ($phpDocInfo->getTemplateTagValueNodes() as $templateTagValueNode) {
$phpstanType = $this->staticTypeMapper->mapPHPStanPhpDocTypeToPHPStanType($templateTagValueNode, $node);
$templateTypes[$templateTagValueNode->name] = $phpstanType;
}
$class = $node->getAttribute(AttributeKey::CLASS_NODE);
$classTemplateTypes = [];
if ($class instanceof ClassLike) {
$classTemplateTypes = $this->resolveTemplateTypesFromNode($class);
}
$templateTypes = array_merge($nodeTemplateTypes, $classTemplateTypes);
return new TemplateTypeMap($templateTypes);
}
/**
* @return Type[]
*/
private function resolveTemplateTypesFromNode(Node $node): array
{
$phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
if (! $phpDocInfo instanceof PhpDocInfo) {
return [];
}
$templateTypes = [];
foreach ($phpDocInfo->getTemplateTagValueNodes() as $templateTagValueNode) {
$phpstanType = $this->staticTypeMapper->mapPHPStanPhpDocTypeToPHPStanType($templateTagValueNode, $node);
$templateTypes[$templateTagValueNode->name] = $phpstanType;
}
return $templateTypes;
}
}

View File

@ -62,6 +62,8 @@ final class TestingParser
}
/**
* @template T of Node
* @param class-string<T> $nodeClass
* @return Node[]
*/
public function parseFileToDecoratedNodesAndFindNodesByType(string $file, string $nodeClass): array

View File

@ -596,3 +596,16 @@ parameters:
- '#File "ClassInCorrectNamespaceRector\.php" should have prefix "Skip" prefix#'
- '#File "HaveSameStarts\.php" should have prefix "Skip" prefix#'
- '#File "AbstractSkip\.php" should have prefix "Skip" prefix#'
# generics nullable bugs
- '#Method (.*?) should return T of PhpParser\\Node\|null but returns PhpParser\\Node\|null#'
- '#Method (.*?) should return T of PhpParser\\Node\|null but returns PhpParser\\Node#'
- '#Method (.*?) should return (.*?)\|null but returns PhpParser\\Node\|null#'
- '#Method (.*?) should return array<T of PhpParser\\Node\> but returns array<PhpParser\\Node\>#'
- '#Method (.*?) should return array<T of PhpParser\\Node\> but returns array<T of PhpParser\\Node\>#'
- '#Parameter \#1 \$node of method Rector\\Core\\PhpParser\\Node\\BetterNodeFinder<PhpParser\\Node\>\:\:findFirstAncestorInstanceOf\(\) expects PhpParser\\Node, PhpParser\\Node\\Expr\\Variable\|null given#'
- '#Parameter \#1 \$nodes of method Rector\\Core\\PhpParser\\Node\\BetterNodeFinder<PhpParser\\Node\>\:\:findFirst\(\) expects array<PhpParser\\Node\>\|PhpParser\\Node, array<PhpParser\\Node\\Stmt\>\|null given#'
- '#Parameter \#2 \$type of method Rector\\Core\\PhpParser\\Node\\BetterNodeFinder<T of PhpParser\\Node\>\:\:findInstanceOfName\(\) expects class\-string<T of PhpParser\\Node\>, string given#'
- '#Method Rector\\Core\\PhpParser\\Node\\BetterNodeFinder\:\:findVariableOfName\(\) should return PhpParser\\Node\\Expr\\Variable\|null but returns T of PhpParser\\Node\|null#'
- '#Method Rector\\BetterPhpDocParser\\Tests\\PhpDocParser\\AbstractPhpDocInfoTest\:\:parseFileAndGetFirstNodeOfType\(\) should return T of PhpParser\\Node but returns PhpParser\\Node\|null#'

View File

@ -204,7 +204,7 @@ CODE_SAMPLE
private function isInsideLoopStmts(Node $node): bool
{
$loopNode = $this->betterNodeFinder->findFirstParentInstanceOf(
$loopNode = $this->betterNodeFinder->findParentTypes(
$node,
[For_::class, While_::class, Foreach_::class, Do_::class]
);

View File

@ -199,7 +199,7 @@ final class LocalPropertyAnalyzer
*/
private function isPartOfClosureBind(PropertyFetch $propertyFetch): bool
{
$parentStaticCall = $this->betterNodeFinder->findFirstParentInstanceOf($propertyFetch, StaticCall::class);
$parentStaticCall = $this->betterNodeFinder->findParentType($propertyFetch, StaticCall::class);
if (! $parentStaticCall instanceof StaticCall) {
return false;
}
@ -213,7 +213,7 @@ final class LocalPropertyAnalyzer
private function isPartOfClosureBindTo(PropertyFetch $propertyFetch): bool
{
$parentMethodCall = $this->betterNodeFinder->findFirstParentInstanceOf($propertyFetch, MethodCall::class);
$parentMethodCall = $this->betterNodeFinder->findParentType($propertyFetch, MethodCall::class);
if (! $parentMethodCall instanceof MethodCall) {
return false;
}

View File

@ -146,7 +146,7 @@ CODE_SAMPLE
private function isInsideProperty(Array_ $array): bool
{
$parentProperty = $this->betterNodeFinder->findFirstParentInstanceOf($array, [Property::class]);
$parentProperty = $this->betterNodeFinder->findParentType($array, Property::class);
return $parentProperty !== null;
}

View File

@ -114,7 +114,7 @@ CODE_SAMPLE
private function shouldSkipAsPartOfNestedForeach(Foreach_ $foreach): bool
{
$foreachParent = $this->betterNodeFinder->findFirstParentInstanceOf($foreach, Foreach_::class);
$foreachParent = $this->betterNodeFinder->findParentType($foreach, Foreach_::class);
return $foreachParent !== null;
}
}

View File

@ -245,7 +245,7 @@ CODE_SAMPLE
private function isIfInLoop(If_ $if): bool
{
$parentLoop = $this->betterNodeFinder->findFirstParentInstanceOf($if, self::LOOP_TYPES);
$parentLoop = $this->betterNodeFinder->findParentTypes($if, self::LOOP_TYPES);
return $parentLoop !== null;
}
@ -272,7 +272,7 @@ CODE_SAMPLE
private function isFunctionLikeReturnsVoid(If_ $if): bool
{
/** @var FunctionLike|null $functionLike */
$functionLike = $this->betterNodeFinder->findFirstParentInstanceOf($if, FunctionLike::class);
$functionLike = $this->betterNodeFinder->findParentType($if, FunctionLike::class);
if ($functionLike === null) {
return true;
}
@ -298,10 +298,7 @@ CODE_SAMPLE
if (! $this->isIfInLoop($if)) {
return false;
}
return (bool) $this->betterNodeFinder->findFirstParentInstanceOf(
$if,
[If_::class, Else_::class, ElseIf_::class]
);
return (bool) $this->betterNodeFinder->findParentTypes($if, [If_::class, Else_::class, ElseIf_::class]);
}
private function isLastIfOrBeforeLastReturn(If_ $if): bool

View File

@ -131,7 +131,7 @@ CODE_SAMPLE
private function isFoundInParentNode(Variable $variable): bool
{
/** @var ClassMethod|Function_|null $classMethodOrFunction */
$classMethodOrFunction = $this->betterNodeFinder->findFirstParentInstanceOf(
$classMethodOrFunction = $this->betterNodeFinder->findParentTypes(
$variable,
[ClassMethod::class, Function_::class]
);

View File

@ -42,7 +42,7 @@ final class ParamRenameFactory
}
/** @var ClassMethod|Function_|Closure|ArrowFunction|null $functionLike */
$functionLike = $this->betterNodeFinder->findFirstParentInstanceOf($param, FunctionLike::class);
$functionLike = $this->betterNodeFinder->findParentType($param, FunctionLike::class);
if ($functionLike === null) {
throw new ShouldNotHappenException("There shouldn't be a param outside of FunctionLike");
}

View File

@ -82,12 +82,11 @@ CODE_SAMPLE
return null;
}
if ($classLike->extends === null) {
$this->makePrivate($node);
return $node;
if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByParent($node, $classLike)) {
return null;
}
if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByParent($node, $classLike)) {
if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByTrait($node, $classLike)) {
return null;
}

View File

@ -26,16 +26,33 @@ final class ClassMethodVisibilityGuard
return false;
}
$methodName = $this->nodeNameResolver->getName($classMethod);
$parentClasses = $this->getParentClasses($class);
$propertyName = $this->nodeNameResolver->getName($classMethod);
return $this->methodExistsInClasses($parentClasses, $methodName);
}
foreach ($parentClasses as $parentClass) {
if (method_exists($parentClass, $propertyName)) {
return true;
}
public function isClassMethodVisibilityGuardedByTrait(ClassMethod $classMethod, Class_ $class): bool
{
$traits = $this->getParentTraits($class);
$methodName = $this->nodeNameResolver->getName($classMethod);
return $this->methodExistsInClasses($traits, $methodName);
}
/**
* @return string[]
*/
public function getParentTraits(Class_ $class): array
{
/** @var string $className */
$className = $this->nodeNameResolver->getName($class);
$traits = class_uses($className);
if ($traits === false) {
return [];
}
return false;
return $traits;
}
/**
@ -53,4 +70,18 @@ final class ClassMethodVisibilityGuard
return $classParents;
}
/**
* @param string[] $classes
*/
private function methodExistsInClasses(array $classes, string $method): bool
{
foreach ($classes as $class) {
if (method_exists($class, $method)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Rector\Privatization\Tests\Rector\ClassMethod\PrivatizeFinalClassMethodRector\Fixture;
use Rector\Privatization\Tests\Rector\ClassMethod\PrivatizeFinalClassMethodRector\Source\MethodSomeTrait;
final class SkipTraitCalled
{
use MethodSomeTrait;
protected function configureRoutes()
{
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Rector\Privatization\Tests\Rector\ClassMethod\PrivatizeFinalClassMethodRector\Source;
trait MethodSomeTrait
{
abstract protected function configureRoutes();
public function run()
{
$this->configureRoutes();
}
}

View File

@ -91,12 +91,12 @@ final class BundleClassResolver
$this->addFullyQualifiedNamesToNodes($nodes);
$class = $this->betterNodeFinder->findFirstNonAnonymousClass($nodes);
if ($class === null) {
$classLike = $this->betterNodeFinder->findFirstNonAnonymousClass($nodes);
if ($classLike === null) {
return null;
}
return $this->nodeNameResolver->getName($class);
return $this->nodeNameResolver->getName($classLike);
}
/**

View File

@ -0,0 +1,30 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use PhpParser\Node\Scalar\String_;
use PhpParser\NodeFinder;
/**
* @template T as Node
*/
final class SkipReturnTypeTemplate
{
/**
* @var NodeFinder
*/
private $nodeFinder;
public function __construct(NodeFinder $nodeFinder)
{
$this->nodeFinder = $nodeFinder;
}
/**
* @return T[]
*/
public function isValidDataProvider($nodes): array
{
return $this->nodeFinder->findInstanceOf($nodes, String_::class);
}
}

View File

@ -42,7 +42,7 @@ final class ContextAnalyzer
{
$stopNodes = array_merge(self::LOOP_NODES, self::BREAK_NODES);
$firstParent = $this->betterNodeFinder->findFirstParentInstanceOf($node, $stopNodes);
$firstParent = $this->betterNodeFinder->findParentTypes($node, $stopNodes);
if ($firstParent === null) {
return false;
}
@ -54,7 +54,7 @@ final class ContextAnalyzer
{
$breakNodes = array_merge([If_::class], self::BREAK_NODES);
$previousNode = $this->betterNodeFinder->findFirstParentInstanceOf($node, $breakNodes);
$previousNode = $this->betterNodeFinder->findParentTypes($node, $breakNodes);
if ($previousNode === null) {
return false;

View File

@ -19,6 +19,7 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
use Webmozart\Assert\Assert;
/**
* @template T of Node
* @see \Rector\Core\Tests\PhpParser\Node\BetterNodeFinder\BetterNodeFinderTest
*/
final class BetterNodeFinder
@ -49,32 +50,63 @@ final class BetterNodeFinder
}
/**
* @param string|string[] $type
* @param class-string<T> $type
* @return T|null
*/
public function findFirstParentInstanceOf(Node $node, $type): ?Node
public function findParentType(Node $node, string $type): ?Node
{
$types = is_array($type) ? $type : [$type];
Assert::allIsAOf($types, Node::class);
Assert::isAOf($type, Node::class);
/** @var Node|null $parentNode */
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode === null) {
/** @var Node|null $parent */
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parent === null) {
return null;
}
do {
if ($this->isTypes($parentNode, $types)) {
return $parentNode;
if (is_a($parent, $type, true)) {
return $parent;
}
if ($parentNode === null) {
if ($parent === null) {
return null;
}
} while ($parentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE));
} while ($parent = $parent->getAttribute(AttributeKey::PARENT_NODE));
return null;
}
/**
* @param class-string<T>[] $types
* @return T|null
*/
public function findParentTypes(Node $node, array $types): ?Node
{
Assert::allIsAOf($types, Node::class);
/** @var Node|null $parent */
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parent === null) {
return null;
}
do {
if ($this->isTypes($parent, $types)) {
return $parent;
}
if ($parent === null) {
return null;
}
} while ($parent = $parent->getAttribute(AttributeKey::PARENT_NODE));
return null;
}
/**
* @param class-string<T> $type
* @return T|null
*/
public function findFirstAncestorInstanceOf(Node $node, string $type): ?Node
{
$currentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
@ -90,7 +122,8 @@ final class BetterNodeFinder
}
/**
* @param string[] $types
* @param array<class-string<T>> $types
* @return T|null
*/
public function findFirstAncestorInstancesOf(Node $node, array $types): ?Node
{
@ -109,8 +142,9 @@ final class BetterNodeFinder
}
/**
* @param class-string<T> $type
* @param Node|Node[]|Stmt[] $nodes
* @return Node[]
* @return T[]
*/
public function findInstanceOf($nodes, string $type): array
{
@ -120,7 +154,9 @@ final class BetterNodeFinder
}
/**
* @param class-string<T> $type
* @param Node|Node[] $nodes
* @return T|null
*/
public function findFirstInstanceOf($nodes, string $type): ?Node
{
@ -130,6 +166,7 @@ final class BetterNodeFinder
}
/**
* @param class-string<T> $type
* @param Node|Node[] $nodes
*/
public function hasInstanceOfName($nodes, string $type, string $name): bool
@ -149,6 +186,7 @@ final class BetterNodeFinder
/**
* @param Node|Node[] $nodes
* @return Variable|null
*/
public function findVariableOfName($nodes, string $name): ?Node
{
@ -157,7 +195,7 @@ final class BetterNodeFinder
/**
* @param Node|Node[] $nodes
* @param class-string[] $types
* @param class-string<T>[] $types
*/
public function hasInstancesOf($nodes, array $types): bool
{
@ -177,7 +215,9 @@ final class BetterNodeFinder
}
/**
* @param class-string<T> $type
* @param Node|Node[] $nodes
* @return T|null
*/
public function findLastInstanceOf($nodes, string $type): ?Node
{
@ -188,7 +228,8 @@ final class BetterNodeFinder
return null;
}
return array_pop($foundInstances);
$lastItemKey = array_key_last($foundInstances);
return $foundInstances[$lastItemKey];
}
/**
@ -219,6 +260,7 @@ final class BetterNodeFinder
/**
* @param Node[] $nodes
* @return ClassLike|null
*/
public function findFirstNonAnonymousClass(array $nodes): ?Node
{
@ -240,6 +282,9 @@ final class BetterNodeFinder
return $this->nodeFinder->findFirst($nodes, $filter);
}
/**
* @return Assign|null
*/
public function findPreviousAssignToExpr(Expr $expr): ?Node
{
return $this->findFirstPrevious($expr, function (Node $node) use ($expr): bool {
@ -279,7 +324,8 @@ final class BetterNodeFinder
}
/**
* @param class-string[] $types
* @param class-string<T>[] $types
* @return T|null
*/
public function findFirstPreviousOfTypes(Node $mainNode, array $types): ?Node
{
@ -314,17 +360,20 @@ final class BetterNodeFinder
/**
* @param Node|Node[] $nodes
* @param class-string<T> $type
* @return T|null
*/
private function findInstanceOfName($nodes, string $type, string $name): ?Node
{
Assert::isAOf($type, Node::class);
$foundInstances = $this->nodeFinder->findInstanceOf($nodes, $type);
foreach ($foundInstances as $foundInstance) {
if ($this->nodeNameResolver->isName($foundInstance, $name)) {
return $foundInstance;
if (! $this->nodeNameResolver->isName($foundInstance, $name)) {
continue;
}
return $foundInstance;
}
return null;