add ResolvedNameReturnTypeExtension and apply

This commit is contained in:
Tomas Votruba 2019-01-25 15:32:42 +01:00
parent 2c878c7541
commit 7c8a88cf73
27 changed files with 225 additions and 69 deletions

View File

@ -123,8 +123,11 @@ CODE_SAMPLE
continue;
}
/** @var Node $parentNode */
$parentNode = $nameNode->getAttribute(Attribute::PARENT_NODE);
if ($parentNode === null) {
continue;
}
$usedNameNodes[$originalName->toString()][] = [$nameNode, $parentNode];
}
}

View File

@ -7,7 +7,6 @@ use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Expression;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
@ -42,7 +41,6 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
/** @var Expression|null $previousExpression */
$previousExpression = $node->getAttribute(Attribute::PREVIOUS_EXPRESSION);
if ($previousExpression === null) {
return null;

View File

@ -58,7 +58,8 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
if (! $node->getAttribute(Attribute::CLASS_NODE) instanceof Class_) {
$classNode = $node->getAttribute(Attribute::CLASS_NODE);
if (! $classNode instanceof Class_) {
return null;
}

View File

@ -70,7 +70,8 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
if (! $node->getAttribute(Attribute::CLASS_NODE) instanceof Class_) {
$classNode = $node->getAttribute(Attribute::CLASS_NODE);
if (! $classNode instanceof Class_) {
return null;
}
@ -78,13 +79,12 @@ CODE_SAMPLE
return null;
}
/** @var string $class */
$class = $node->getAttribute(Attribute::CLASS_NAME);
$methodName = $this->getName($node);
if ($methodName === null) {
$class = $this->getName($classNode);
if ($class === null) {
return null;
}
$methodName = $this->getName($node);
if ($this->classMaintainer->hasParentMethodOrInterface($class, $methodName)) {
return null;
}

View File

@ -7,7 +7,6 @@ use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\NullableType;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\RectorDefinition\ConfiguredCodeSample;
@ -130,8 +129,10 @@ CODE_SAMPLE
private function processParamNode(NullableType $nullableTypeNode, Param $paramNode, string $newType): void
{
/** @var ClassMethod $classMethodNode */
$classMethodNode = $paramNode->getAttribute(Attribute::PARENT_NODE);
if ($classMethodNode === null) {
return;
}
$oldType = $this->namespaceAnalyzer->resolveTypeToFullyQualified(
(string) $nullableTypeNode->type,

View File

@ -7,6 +7,7 @@ use PhpParser\Node\Expr\Variable;
use PHPStan\Analyser\Scope;
use PHPStan\TrinaryLogic;
use PHPStan\Type\ThisType;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Contract\PerNodeTypeResolver\PerNodeTypeResolverInterface;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockAnalyzer;
@ -54,11 +55,15 @@ final class VariableTypeResolver implements PerNodeTypeResolverInterface
*/
public function resolve(Node $variableNode): array
{
/** @var Scope $nodeScope */
$nodeScope = $variableNode->getAttribute(Attribute::SCOPE);
if ($nodeScope === null) {
throw new ShouldNotHappenException();
}
/** @var string $variableName */
$variableName = $this->nameResolver->resolve($variableNode);
if ($variableName === null) {
return [];
}
if ($nodeScope->hasVariableType($variableName) === TrinaryLogic::createYes()) {
$type = $nodeScope->getVariableType($variableName);

View File

@ -7,8 +7,8 @@ use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Nop;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
@ -49,10 +49,11 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
/** @var Expression $expression */
$expression = $node->getAttribute(Attribute::CURRENT_EXPRESSION);
if ($expression === null) {
throw new ShouldNotHappenException();
}
/** @var Node|null $nextNode */
$nextNode = $expression->getAttribute(Attribute::NEXT_NODE);
if ($nextNode === null) {
return null;

View File

@ -3,7 +3,6 @@
namespace Rector\PHPStan\Rector\Cast;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast\Array_;
use PhpParser\Node\Expr\Cast\Bool_;
@ -11,15 +10,12 @@ use PhpParser\Node\Expr\Cast\Double;
use PhpParser\Node\Expr\Cast\Int_;
use PhpParser\Node\Expr\Cast\Object_;
use PhpParser\Node\Expr\Cast\String_;
use PHPStan\Analyser\Scope;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
@ -79,21 +75,16 @@ CODE_SAMPLE
return null;
}
$nodeType = $this->getNodeType($node->expr);
$sameNodeType = $this->castClassToNodeType[$nodeClass];
$nodeType = $this->getStaticType($node->expr);
if ($nodeType === null) {
return null;
}
$sameNodeType = $this->castClassToNodeType[$nodeClass];
if (! is_a($nodeType, $sameNodeType, true)) {
return null;
}
return $node->expr;
}
private function getNodeType(Expr $node): Type
{
/** @var Scope $nodeScope */
$nodeScope = $node->getAttribute(Attribute::SCOPE);
return $nodeScope->getType($node);
}
}

View File

@ -173,7 +173,11 @@ CODE_SAMPLE
private function processClassConstFetch(Expr $expr): void
{
$className = (string) $expr->getAttribute(Attribute::CLASS_NAME);
$className = $expr->getAttribute(Attribute::CLASS_NAME);
if (! is_string($className)) {
return;
}
$constantName = $this->getName($expr);
if ($constantName === null) {
return;

View File

@ -7,6 +7,7 @@ use PhpParser\Node\Expr\Cast\String_;
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Type\Constant\ConstantStringType;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
@ -70,8 +71,11 @@ CODE_SAMPLE
// is argument string?
$needleArgNode = $node->args[1]->value;
/** @var Scope $nodeScope */
$nodeScope = $needleArgNode->getAttribute(Attribute::SCOPE);
if ($nodeScope === null) {
throw new ShouldNotHappenException();
}
if ($nodeScope->getType($needleArgNode) instanceof ConstantStringType) {
return null;
}

View File

@ -83,9 +83,7 @@ abstract class AbstractTypeDeclarationRector extends AbstractRector
}
$methodName = $this->getName($classMethodNode);
if ($methodName === null) {
return false;
}
// @todo extract to some "inherited parent method" service
/** @var string|null $parentClassName */

View File

@ -79,8 +79,10 @@ CODE_SAMPLE
return null;
}
/** @var Class_ $classNode */
$classNode = $node->getAttribute(Attribute::CLASS_NODE);
if (! $classNode instanceof Class_) {
return null;
}
// anonymous class → skip
if ($classNode->name === null || $node->isAbstract() || $node->isStatic()) {
@ -148,7 +150,7 @@ CODE_SAMPLE
/** @var ClassMethod[] $classMethodNodes */
$classMethodNodes = $this->betterNodeFinder->findInstanceOf($classNode->stmts, ClassMethod::class);
foreach ($classMethodNodes as $classMethodNode) {
$classMethodNames[] = (string) $classMethodNode->name;
$classMethodNames[] = $this->getName($classMethodNode);
}
return $classMethodNames;

View File

@ -8,6 +8,7 @@ use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\Php\ReturnTypeInfo;
use Rector\RectorDefinition\CodeSample;
@ -149,11 +150,15 @@ CODE_SAMPLE
return;
}
/** @var string $methodName */
$methodName = $this->getName($node);
if ($methodName === null) {
throw new ShouldNotHappenException();
}
/** @var string $className */
$className = $node->getAttribute(Attribute::CLASS_NAME);
if (! is_string($className)) {
throw new ShouldNotHappenException();
}
$childrenClassLikes = $this->classLikeNodeCollector->findClassesAndInterfacesByType($className);

View File

@ -79,8 +79,10 @@ CODE_SAMPLE
return null;
}
/** @var string $className */
$className = $node->getAttribute(Attribute::CLASS_NAME);
if (! is_string($className)) {
return null;
}
$methodName = $this->getName($node);
if ($methodName === null) {

View File

@ -104,7 +104,9 @@ CODE_SAMPLE
return null;
}
if ($this->getName($node->class) === $node->getAttribute(Attribute::PARENT_CLASS_NAME)) {
$className = $this->getName($node->class);
$parentClassName = $node->getAttribute(Attribute::PARENT_CLASS_NAME);
if ($className === $parentClassName) {
return null;
}

View File

@ -6,15 +6,37 @@ use Nette\Utils\Strings;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\PhpParser\Node\Resolver\NameResolver;
use function Safe\sprintf;
final class TemplateGuesser
{
/**
* @var NameResolver
*/
private $nameResolver;
public function __construct(NameResolver $nameResolver)
{
$this->nameResolver = $nameResolver;
}
public function resolveFromClassMethodNode(ClassMethod $classMethodNode, int $version = 5): string
{
$namespace = (string) $classMethodNode->getAttribute(Attribute::NAMESPACE_NAME);
$class = (string) $classMethodNode->getAttribute(Attribute::CLASS_NAME);
$method = (string) $classMethodNode->name;
$namespace = $classMethodNode->getAttribute(Attribute::NAMESPACE_NAME);
if (! is_string($namespace)) {
throw new ShouldNotHappenException();
}
$class = $classMethodNode->getAttribute(Attribute::CLASS_NAME);
if (! is_string($class)) {
throw new ShouldNotHappenException();
}
$method = $this->nameResolver->resolve($classMethodNode);
if ($method === null) {
throw new ShouldNotHappenException();
}
if ($version === 3) {
return $this->resolveForVersion3($namespace, $class, $method);

View File

@ -8,6 +8,7 @@ use PhpParser\Node\Arg;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Type\Constant\ConstantStringType;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
@ -72,8 +73,11 @@ final class ParseFileRector extends AbstractRector
}
// try to detect current value
/** @var Scope $nodeScope */
$nodeScope = $possibleFileNode->getAttribute(Attribute::SCOPE);
if ($nodeScope === null) {
throw new ShouldNotHappenException();
}
$nodeType = $nodeScope->getType($possibleFileNode);
if ($nodeType instanceof ConstantStringType) {

View File

@ -1,4 +1,5 @@
includes:
- 'utils/phpstan/config/phpstan-extensions.neon'
- 'vendor/symplify/phpstan-extensions/config/config.neon'
- 'vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon'
- 'vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon'
@ -70,7 +71,6 @@ parameters:
- '#Method Rector\\CodeQuality\\Rector\\Foreach_\\SimplifyForeachToCoalescingRector\:\:matchReturnOrAssignNode\(\) should return PhpParser\\Node\\Expr\\Assign\|PhpParser\\Node\\Stmt\\Return_\|null but returns PhpParser\\Node\|null#'
- '#Access to an undefined property PhpParser\\Node::\$(\w+)#'
- '#Parameter \#2 \$boolConstFetchNode of method Rector\\CodeQuality\\Rector\\Identical\\SimplifyArraySearchRector::resolveIsNot\(\) expects PhpParser\\Node\\Expr\\ConstFetch, PhpParser\\Node given#'
- '#Method Rector\\PhpParser\\Node\\BetterNodeFinder::findFirstAncestorInstanceOf\(\) should return PhpParser\\Node\|null but returns object#'
# false positive, resolved in previous method
- '#Parameter (.*?) of method Rector\\PhpParser\\Node\\Maintainer\\IdentifierMaintainer\:\:(.*?)\(\) expects PhpParser\\Node\\Expr\\ClassConstFetch\|PhpParser\\Node\\Expr\\MethodCall\|PhpParser\\Node\\Expr\\PropertyFetch\|PhpParser\\Node\\Expr\\StaticCall\|PhpParser\\Node\\Stmt\\ClassMethod, PhpParser\\Node given#'
@ -98,12 +98,3 @@ parameters:
# known values
- '#Parameter \#(1|2) \$(left|right) of class PhpParser\\Node\\Expr\\BinaryOp\\Coalesce constructor expects PhpParser\\Node\\Expr, PhpParser\\Node\\Expr\|null given#'
- '#Strict comparison using === between PhpParser\\Node\\Expr and null will always evaluate to false#'
- '#Cannot cast array<string>\|string\|null to string#'
# false positive on anonymous classes
- '#http\:\/\/bit\.ly\/typehintarray#'
services:
- { 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] }

View File

@ -7,6 +7,7 @@ use PhpParser\Node\Expr\PropertyFetch;
use PHPStan\Analyser\Scope;
use PHPStan\Broker\Broker;
use PHPStan\Type\ObjectType;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\PhpParser\Node\Resolver\NameResolver;
@ -60,8 +61,10 @@ final class PropertyFetchMaintainer
private function hasPublicProperty(PropertyFetch $node, string $propertyName): bool
{
/** @var Scope $nodeScope */
$nodeScope = $node->getAttribute(Attribute::SCOPE);
if ($nodeScope === null) {
throw new ShouldNotHappenException();
}
$propertyFetchType = $nodeScope->getType($node->var);
if ($propertyFetchType instanceof ObjectType) {

View File

@ -104,12 +104,11 @@ CODE_SAMPLE
}
$paramNodeTypes = $this->getTypes($paramNode);
$paramName = $this->getName($paramNode->var);
if ($paramName === null) {
continue;
}
$paramNodeType = $paramNodeTypes[0] ?? 'mixed';
$this->addPropertyToClass($classNode, $paramNodeTypes[0], $paramName);
/** @var string $paramName */
$paramName = $this->getName($paramNode->var);
$this->addPropertyToClass($classNode, $paramNodeType, $paramName);
// remove arguments
unset($classMethodNode->params[$key]);

View File

@ -109,12 +109,8 @@ CODE_SAMPLE
return;
}
$mainPropertyType = $this->getTypes($propertyNode)[0];
$mainPropertyType = $this->getTypes($propertyNode)[0] ?? 'mixed';
$propertyName = $this->getName($propertyNode);
if ($propertyName === null) {
return;
}
$this->addPropertyToClass($classNode, $mainPropertyType, $propertyName);
}
}

View File

@ -7,6 +7,7 @@ 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;
@ -108,7 +109,12 @@ CODE_SAMPLE
private function isInControllerActionMethod(Node $node): bool
{
if (! Strings::endsWith((string) $node->getAttribute(Attribute::CLASS_NAME), 'Controller')) {
$className = $node->getAttribute(Attribute::CLASS_NAME);
if ($className === null) {
throw new ShouldNotHappenException();
}
if (! Strings::endsWith($className, 'Controller')) {
return false;
}

View File

@ -4,8 +4,8 @@ namespace Rector\Rector\MethodBody;
use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Return_;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockAnalyzer;
use Rector\Rector\AbstractRector;
@ -97,8 +97,11 @@ CODE_SAMPLE
$this->removeNode($node);
/** @var ClassMethod $methodNode */
$methodNode = $node->getAttribute(Attribute::METHOD_NODE);
if ($methodNode === null) {
throw new ShouldNotHappenException();
}
$this->docBlockAnalyzer->removeTagFromNode($methodNode, 'return');
return null;

View File

@ -0,0 +1,9 @@
services:
- { 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] }
# $nameResolver->resolve() => in some cases always string
- { 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] }

View File

@ -0,0 +1,64 @@
<?php declare(strict_types=1);
namespace Rector\PHPStanExtensions\Rector\Type;
use PhpParser\Node\Const_ as NodeConst;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Const_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\Node\Stmt\Static_;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\Rector\NameResolverTrait;
/**
* @see NameResolver::resolve()
* @see NameResolverTrait::getName()
*
* These returns always strings for nodes with required names, e.g. for @see ClassMethod
*/
abstract class AbstractResolvedNameReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
/**
* @var string[]
*/
private $alwaysNamedTypes = [
ClassMethod::class,
Trait_::class,
Interface_::class,
Property::class,
PropertyProperty::class,
Const_::class,
NodeConst::class,
Param::class,
Name::class,
];
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
$argumentValueType = $scope->getType($methodCall->args[0]->value);
if ( ! $argumentValueType instanceof ObjectType) {
return $returnType;
}
if (in_array($argumentValueType->getClassName(), $this->alwaysNamedTypes, true)) {
return new StringType();
}
return $returnType;
}
}

View File

@ -0,0 +1,19 @@
<?php declare(strict_types=1);
namespace Rector\PHPStanExtensions\Rector\Type;
use PHPStan\Reflection\MethodReflection;
use Rector\PhpParser\Node\Resolver\NameResolver;
final class NameResolverReturnTypeExtension extends AbstractResolvedNameReturnTypeExtension
{
public function getClass(): string
{
return NameResolver::class;
}
public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'resolve';
}
}

View File

@ -0,0 +1,23 @@
<?php declare(strict_types=1);
namespace Rector\PHPStanExtensions\Rector\Type;
use PHPStan\Reflection\MethodReflection;
use Rector\Rector\AbstractRector;
use Rector\Rector\NameResolverTrait;
final class NameResolverTraitReturnTypeExtension extends AbstractResolvedNameReturnTypeExtension
{
/**
* Original scope @see NameResolverTrait
*/
public function getClass(): string
{
return AbstractRector::class;
}
public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'getName';
}
}