Add BetterNodeFinderReturnTypeExtension

This commit is contained in:
Tomas Votruba 2019-01-25 15:59:40 +01:00
parent 7c8a88cf73
commit def4db7a8f
9 changed files with 173 additions and 56 deletions

View File

@ -159,7 +159,6 @@ CODE_SAMPLE
$paramNames[] = $this->getName($paramNode);
}
/** @var Variable[] $variableNodes */
$variableNodes = $this->betterNodeFinder->findInstanceOf($nodes, Variable::class);
$filteredVariables = [];
@ -169,11 +168,12 @@ CODE_SAMPLE
continue;
}
if (in_array($this->getName($variableNode), $paramNames, true)) {
$variableName = $this->getName($variableNode);
if (in_array($variableName, $paramNames, true)) {
continue;
}
$filteredVariables[$this->getName($variableNode)] = $variableNode;
$filteredVariables[$variableName] = $variableNode;
}
return $filteredVariables;

View File

@ -12,7 +12,7 @@ use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\PhpParser\Node\Maintainer\ClassMaintainer;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
@ -23,13 +23,13 @@ use Rector\RectorDefinition\RectorDefinition;
final class Php4ConstructorRector extends AbstractRector
{
/**
* @var BetterNodeFinder
* @var ClassMaintainer
*/
private $betterNodeFinder;
private $classMaintainer;
public function __construct(BetterNodeFinder $betterNodeFinder)
public function __construct(ClassMaintainer $classMaintainer)
{
$this->betterNodeFinder = $betterNodeFinder;
$this->classMaintainer = $classMaintainer;
}
public function getDefinition(): RectorDefinition
@ -73,9 +73,7 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
$namespace = $node->getAttribute(Attribute::NAMESPACE_NAME);
// catch only classes without namespace
if ($namespace) {
if ($this->shouldSkip($node)) {
return null;
}
@ -84,21 +82,16 @@ CODE_SAMPLE
return null;
}
// anonymous class → skip
if ($classNode->name === null || $node->isAbstract() || $node->isStatic()) {
return null;
}
// process parent call references first
$this->processClassMethodStatementsForParentConstructorCalls($node);
// not PSR-4 constructor
if (! $this->isNameInsensitive($classNode, (string) $node->name)) {
if (! $this->isNameInsensitive($classNode, $this->getName($node))) {
return null;
}
// does it already have a __construct method?
if (! in_array('__construct', $this->getClassMethodNames($classNode), true)) {
if (! $this->classMaintainer->hasClassMethod($classNode, '__construct')) {
$node->name = new Identifier('__construct');
}
@ -120,6 +113,26 @@ CODE_SAMPLE
return $node;
}
private function shouldSkip(ClassMethod $classMethod): bool
{
$namespace = $classMethod->getAttribute(Attribute::NAMESPACE_NAME);
// catch only classes without namespace
if ($namespace !== null) {
return true;
}
if ($classMethod->isAbstract() || $classMethod->isStatic()) {
return true;
}
$classNode = $classMethod->getAttribute(Attribute::CLASS_NODE);
if ($classNode instanceof Class_ && $classNode->name === null) {
return true;
}
return false;
}
private function processClassMethodStatementsForParentConstructorCalls(ClassMethod $classMethodNode): void
{
if (! is_iterable($classMethodNode->stmts)) {
@ -140,22 +153,6 @@ CODE_SAMPLE
}
}
/**
* @return string[]
*/
private function getClassMethodNames(Class_ $classNode): array
{
$classMethodNames = [];
/** @var ClassMethod[] $classMethodNodes */
$classMethodNodes = $this->betterNodeFinder->findInstanceOf($classNode->stmts, ClassMethod::class);
foreach ($classMethodNodes as $classMethodNode) {
$classMethodNames[] = $this->getName($classMethodNode);
}
return $classMethodNames;
}
private function isThisConstructCall(Node $node): bool
{
if (! $node instanceof MethodCall) {
@ -191,16 +188,16 @@ CODE_SAMPLE
}
// rename ParentClass
if ((string) $node->class === $parentClassName) {
if ($this->isName($node->class, $parentClassName)) {
$node->class = new Name('parent');
}
if ((string) $node->class !== 'parent') {
if ($this->isName($node->class, 'parent') === false) {
return;
}
// it's not a parent PHP 4 constructor call
if (! $this->isNameInsensitive($node, $parentClassName)) {
if ($this->isNameInsensitive($node, $parentClassName) === false) {
return;
}

View File

@ -10,6 +10,7 @@ use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TraitUse;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\PhpParser\Node\NodeFactory;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\PhpParser\Node\VariableInfo;
@ -32,14 +33,21 @@ final class ClassMaintainer
*/
private $childAndParentClassMaintainer;
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
public function __construct(
NameResolver $nameResolver,
NodeFactory $nodeFactory,
ChildAndParentClassMaintainer $childAndParentClassMaintainer
ChildAndParentClassMaintainer $childAndParentClassMaintainer,
BetterNodeFinder $betterNodeFinder
) {
$this->nodeFactory = $nodeFactory;
$this->nameResolver = $nameResolver;
$this->childAndParentClassMaintainer = $childAndParentClassMaintainer;
$this->betterNodeFinder = $betterNodeFinder;
}
public function addConstructorDependency(Class_ $classNode, VariableInfo $variableInfo): void
@ -208,6 +216,13 @@ final class ClassMaintainer
return false;
}
public function hasClassMethod(Class_ $classNode, string $methodName): bool
{
$methodNames = $this->getClassMethodNames($classNode);
return in_array($methodName, $methodNames, true);
}
private function tryInsertBeforeFirstMethod(Class_ $classNode, Stmt $node): bool
{
foreach ($classNode->stmts as $key => $classElementNode) {
@ -283,6 +298,21 @@ final class ClassMaintainer
$classMethodNode->stmts[] = new Expression($propertyAssignNode);
}
/**
* @return string[]
*/
private function getClassMethodNames(Class_ $classNode): array
{
$classMethodNames = [];
$classMethodNodes = $this->betterNodeFinder->findInstanceOf($classNode->stmts, ClassMethod::class);
foreach ($classMethodNodes as $classMethodNode) {
$classMethodNames[] = $this->nameResolver->resolve($classMethodNode);
}
return $classMethodNames;
}
private function hasMethodParameter(ClassMethod $classMethodNode, VariableInfo $variableInfo): bool
{
foreach ($classMethodNode->params as $constructorParameter) {

View File

@ -7,7 +7,6 @@ use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Configuration\Rector\Architecture\DependencyInjection\VariablesToPropertyFetchCollection;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
@ -107,11 +106,12 @@ CODE_SAMPLE
return null;
}
private function isInControllerActionMethod(Node $node): bool
private function isInControllerActionMethod(Variable $node): bool
{
$className = $node->getAttribute(Attribute::CLASS_NAME);
if ($className === null) {
throw new ShouldNotHappenException();
return false;
}
if (! Strings::endsWith($className, 'Controller')) {

View File

@ -122,7 +122,6 @@ CODE_SAMPLE
$smartFileInfo->getRealPath()
);
/** @var Namespace_[] $namespaceNodes */
$namespaceNodes = $this->betterNodeFinder->findInstanceOf($newStmts, Namespace_::class);
if ($this->shouldSkip($smartFileInfo, $newStmts, $namespaceNodes)) {

View File

@ -1,5 +1,8 @@
services:
- Rector\PHPStanExtensions\Utils\ValueResolver
- { class: Symplify\PHPStanExtensions\Type\SplFileInfoTolerantDynamicMethodReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] }
# $node->geAttribute($1) => Type|null by $1
- { class: Rector\PHPStanExtensions\Rector\Type\GetAttributeReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] }
@ -7,3 +10,6 @@ services:
- { class: Rector\PHPStanExtensions\Rector\Type\NameResolverReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] }
# $nameResolverTrait->getName() => in some cases always string
- { class: Rector\PHPStanExtensions\Rector\Type\NameResolverTraitReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] }
# $betterNodeFinder->findByInstance(..., $1) => $1[]
- { class: Rector\PHPStanExtensions\Rector\Type\BetterNodeFinderReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] }

View File

@ -0,0 +1,58 @@
<?php declare(strict_types=1);
namespace Rector\PHPStanExtensions\Rector\Type;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ArrayType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\PHPStanExtensions\Utils\ValueResolver;
final class BetterNodeFinderReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
/**
* @var ValueResolver
*/
private $valueResolver;
public function __construct(ValueResolver $valueResolver)
{
$this->valueResolver = $valueResolver;
}
public function getClass(): string
{
return BetterNodeFinder::class;
}
public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'findInstanceOf';
}
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
$secondArgumentNode = $methodCall->args[1]->value;
if (! $secondArgumentNode instanceof ClassConstFetch) {
return $returnType;
}
if (! $secondArgumentNode->class instanceof Name) {
return $returnType;
}
$class = $secondArgumentNode->class->toString();
return new ArrayType(new MixedType(), new ObjectType($class));
}
}

View File

@ -26,6 +26,7 @@ use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Php\PhpTypeSupport;
use Rector\Php\TypeAnalyzer;
use Rector\PHPStanExtensions\Utils\ValueResolver;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
final class GetAttributeReturnTypeExtension implements DynamicMethodReturnTypeExtension
@ -53,6 +54,15 @@ final class GetAttributeReturnTypeExtension implements DynamicMethodReturnTypeEx
'Rector\NodeTypeResolver\Node\Attribute::CLASS_NAME' => 'string',
'Rector\NodeTypeResolver\Node\Attribute::METHOD_NAME' => 'string',
];
/**
* @var ValueResolver
*/
private $valueResolver;
public function __construct(ValueResolver $valueResolver)
{
$this->valueResolver = $valueResolver;
}
public function getClass(): string
{
@ -92,22 +102,10 @@ final class GetAttributeReturnTypeExtension implements DynamicMethodReturnTypeEx
private function resolveArgumentValue(Expr $node): ?string
{
$value = null;
if ($node instanceof ClassConstFetch) {
if ($node->class instanceof Name) {
$value = $node->class->toString();
} else {
return null;
}
if ($node->name instanceof Identifier) {
$value .= '::' . $node->name->toString();
} else {
return null;
}
return $this->valueResolver->resolveClassConstFetch($node);
}
return $value;
return null;
}
}

View File

@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace Rector\PHPStanExtensions\Utils;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
final class ValueResolver
{
public function resolveClassConstFetch(ClassConstFetch $classConstFetch): ?string
{
$value = null;
if ($classConstFetch->class instanceof Name) {
$value = $classConstFetch->class->toString();
} else {
return null;
}
if ($classConstFetch->name instanceof Identifier) {
$value .= '::' . $classConstFetch->name->toString();
} else {
return null;
}
return $value;
}
}