2020-02-10 00:42:15 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
2020-02-10 08:58:04 +00:00
|
|
|
namespace Rector\NodeTypeResolver\NodeTypeResolver;
|
2020-02-10 00:42:15 +00:00
|
|
|
|
|
|
|
use PhpParser\Node;
|
|
|
|
use PhpParser\Node\Expr\PropertyFetch;
|
2020-03-28 23:06:05 +00:00
|
|
|
use PhpParser\Node\Stmt\ClassLike;
|
2020-02-10 00:42:15 +00:00
|
|
|
use PhpParser\Node\Stmt\Nop;
|
2020-03-28 23:06:05 +00:00
|
|
|
use PhpParser\Node\Stmt\Trait_;
|
2020-02-10 00:42:15 +00:00
|
|
|
use PHPStan\Analyser\Scope;
|
|
|
|
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
|
|
|
use PHPStan\Type\MixedType;
|
|
|
|
use PHPStan\Type\Type;
|
|
|
|
use PHPStan\Type\TypeWithClassName;
|
|
|
|
use Rector\BetterPhpDocParser\PhpDocParser\BetterPhpDocParser;
|
2020-02-10 09:05:15 +00:00
|
|
|
use Rector\NodeCollector\NodeCollector\ParsedNodeCollector;
|
2020-02-10 00:42:15 +00:00
|
|
|
use Rector\NodeNameResolver\NodeNameResolver;
|
2020-02-10 08:58:04 +00:00
|
|
|
use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface;
|
2020-02-10 00:42:15 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
|
|
|
use Rector\NodeTypeResolver\NodeTypeResolver;
|
2020-03-28 23:06:05 +00:00
|
|
|
use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector;
|
2020-02-10 09:17:05 +00:00
|
|
|
use Rector\StaticTypeMapper\StaticTypeMapper;
|
2020-02-10 00:42:15 +00:00
|
|
|
use ReflectionProperty;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @see \Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\NameTypeResolver\NameTypeResolverTest
|
|
|
|
*/
|
2020-02-10 08:58:04 +00:00
|
|
|
final class PropertyFetchTypeResolver implements NodeTypeResolverInterface
|
2020-02-10 00:42:15 +00:00
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var ParsedNodeCollector
|
|
|
|
*/
|
|
|
|
private $parsedNodeCollector;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var NodeTypeResolver
|
|
|
|
*/
|
|
|
|
private $nodeTypeResolver;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var NodeNameResolver
|
|
|
|
*/
|
|
|
|
private $nodeNameResolver;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var BetterPhpDocParser
|
|
|
|
*/
|
|
|
|
private $betterPhpDocParser;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var StaticTypeMapper
|
|
|
|
*/
|
|
|
|
private $staticTypeMapper;
|
|
|
|
|
2020-03-28 23:06:05 +00:00
|
|
|
/**
|
|
|
|
* @var TraitNodeScopeCollector
|
|
|
|
*/
|
|
|
|
private $traitNodeScopeCollector;
|
|
|
|
|
2020-02-10 00:42:15 +00:00
|
|
|
public function __construct(
|
|
|
|
BetterPhpDocParser $betterPhpDocParser,
|
2020-07-26 07:49:22 +00:00
|
|
|
NodeNameResolver $nodeNameResolver,
|
|
|
|
ParsedNodeCollector $parsedNodeCollector,
|
2020-03-28 23:06:05 +00:00
|
|
|
StaticTypeMapper $staticTypeMapper,
|
|
|
|
TraitNodeScopeCollector $traitNodeScopeCollector
|
2020-02-10 00:42:15 +00:00
|
|
|
) {
|
|
|
|
$this->parsedNodeCollector = $parsedNodeCollector;
|
|
|
|
$this->nodeNameResolver = $nodeNameResolver;
|
|
|
|
$this->betterPhpDocParser = $betterPhpDocParser;
|
|
|
|
$this->staticTypeMapper = $staticTypeMapper;
|
2020-03-28 23:06:05 +00:00
|
|
|
$this->traitNodeScopeCollector = $traitNodeScopeCollector;
|
2020-02-10 00:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @required
|
|
|
|
*/
|
|
|
|
public function autowirePropertyTypeResolver(NodeTypeResolver $nodeTypeResolver): void
|
|
|
|
{
|
|
|
|
$this->nodeTypeResolver = $nodeTypeResolver;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
public function getNodeClasses(): array
|
|
|
|
{
|
|
|
|
return [PropertyFetch::class];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param PropertyFetch $node
|
|
|
|
*/
|
|
|
|
public function resolve(Node $node): Type
|
|
|
|
{
|
|
|
|
// compensate 3rd party non-analysed property reflection
|
|
|
|
$vendorPropertyType = $this->getVendorPropertyFetchType($node);
|
2020-03-28 23:06:05 +00:00
|
|
|
if (! $vendorPropertyType instanceof MixedType) {
|
2020-02-10 00:42:15 +00:00
|
|
|
return $vendorPropertyType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var Scope|null $scope */
|
|
|
|
$scope = $node->getAttribute(AttributeKey::SCOPE);
|
2020-03-28 23:06:05 +00:00
|
|
|
|
2020-02-10 00:42:15 +00:00
|
|
|
if ($scope === null) {
|
2020-03-28 23:06:05 +00:00
|
|
|
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
|
|
|
|
if ($classNode instanceof Trait_) {
|
|
|
|
/** @var string $traitName */
|
|
|
|
$traitName = $classNode->getAttribute(AttributeKey::CLASS_NAME);
|
|
|
|
|
|
|
|
/** @var Scope|null $scope */
|
|
|
|
$scope = $this->traitNodeScopeCollector->getScopeForTraitAndNode($traitName, $node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($scope === null) {
|
|
|
|
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
|
|
|
|
// fallback to class, since property fetches are not scoped by PHPStan
|
|
|
|
if ($classNode instanceof ClassLike) {
|
|
|
|
$scope = $classNode->getAttribute(AttributeKey::SCOPE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($scope === null) {
|
|
|
|
return new MixedType();
|
|
|
|
}
|
2020-02-10 00:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $scope->getType($node);
|
|
|
|
}
|
|
|
|
|
2020-03-28 23:06:05 +00:00
|
|
|
private function getVendorPropertyFetchType(PropertyFetch $propertyFetch): Type
|
2020-02-10 00:42:15 +00:00
|
|
|
{
|
|
|
|
$varObjectType = $this->nodeTypeResolver->resolve($propertyFetch->var);
|
|
|
|
if (! $varObjectType instanceof TypeWithClassName) {
|
2020-03-28 23:06:05 +00:00
|
|
|
return new MixedType();
|
2020-02-10 00:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$class = $this->parsedNodeCollector->findClass($varObjectType->getClassName());
|
|
|
|
if ($class !== null) {
|
2020-03-28 23:06:05 +00:00
|
|
|
return new MixedType();
|
2020-02-10 00:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 3rd party code
|
|
|
|
$propertyName = $this->nodeNameResolver->getName($propertyFetch->name);
|
|
|
|
if ($propertyName === null) {
|
2020-03-28 23:06:05 +00:00
|
|
|
return new MixedType();
|
2020-02-10 00:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (! property_exists($varObjectType->getClassName(), $propertyName)) {
|
2020-03-28 23:06:05 +00:00
|
|
|
return new MixedType();
|
2020-02-10 00:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// property is used
|
2020-06-29 21:19:37 +00:00
|
|
|
$reflectionProperty = new ReflectionProperty($varObjectType->getClassName(), $propertyName);
|
|
|
|
if (! $reflectionProperty->getDocComment()) {
|
2020-03-28 23:06:05 +00:00
|
|
|
return new MixedType();
|
2020-02-10 00:42:15 +00:00
|
|
|
}
|
|
|
|
|
2020-06-29 21:19:37 +00:00
|
|
|
$phpDocNode = $this->betterPhpDocParser->parseString((string) $reflectionProperty->getDocComment());
|
2020-02-10 00:42:15 +00:00
|
|
|
$varTagValues = $phpDocNode->getVarTagValues();
|
|
|
|
|
|
|
|
if (! isset($varTagValues[0])) {
|
2020-03-28 23:06:05 +00:00
|
|
|
return new MixedType();
|
2020-02-10 00:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$typeNode = $varTagValues[0]->type;
|
|
|
|
if (! $typeNode instanceof TypeNode) {
|
2020-03-28 23:06:05 +00:00
|
|
|
return new MixedType();
|
2020-02-10 00:42:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($typeNode, new Nop());
|
|
|
|
}
|
|
|
|
}
|