mirror of
https://github.com/rectorphp/rector.git
synced 2025-04-21 16:02:23 +02:00
Add basic support for vendor property fetch detection based on phpdoc for CountOnNullRector
This commit is contained in:
parent
f9cb78bb1f
commit
3f6e1499e3
@ -26,6 +26,7 @@ use Rector\BetterPhpDocParser\Contract\PhpDocParserAwareInterface;
|
||||
use Rector\BetterPhpDocParser\Printer\MultilineSpaceFormatPreserver;
|
||||
use Rector\BetterPhpDocParser\ValueObject\StartEndValueObject;
|
||||
use Rector\Configuration\CurrentNodeProvider;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
|
||||
use Symplify\PackageBuilder\Reflection\PrivatesCaller;
|
||||
|
||||
@ -75,6 +76,11 @@ final class BetterPhpDocParser extends PhpDocParser
|
||||
*/
|
||||
private $classAnnotationMatcher;
|
||||
|
||||
/**
|
||||
* @var Lexer
|
||||
*/
|
||||
private $lexer;
|
||||
|
||||
/**
|
||||
* @param PhpDocNodeFactoryInterface[] $phpDocNodeFactories
|
||||
*/
|
||||
@ -85,6 +91,7 @@ final class BetterPhpDocParser extends PhpDocParser
|
||||
MultilineSpaceFormatPreserver $multilineSpaceFormatPreserver,
|
||||
CurrentNodeProvider $currentNodeProvider,
|
||||
ClassAnnotationMatcher $classAnnotationMatcher,
|
||||
Lexer $lexer,
|
||||
array $phpDocNodeFactories = []
|
||||
) {
|
||||
parent::__construct($typeParser, $constExprParser);
|
||||
@ -96,6 +103,15 @@ final class BetterPhpDocParser extends PhpDocParser
|
||||
$this->phpDocNodeFactories = $phpDocNodeFactories;
|
||||
$this->currentNodeProvider = $currentNodeProvider;
|
||||
$this->classAnnotationMatcher = $classAnnotationMatcher;
|
||||
$this->lexer = $lexer;
|
||||
}
|
||||
|
||||
public function parseString(string $docBlock): PhpDocNode
|
||||
{
|
||||
$tokens = $this->lexer->tokenize($docBlock);
|
||||
$tokenIterator = new TokenIterator($tokens);
|
||||
|
||||
return parent::parse($tokenIterator);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,6 +173,10 @@ final class BetterPhpDocParser extends PhpDocParser
|
||||
|
||||
// compare regardless sensitivity
|
||||
$currentPhpNode = $this->currentNodeProvider->getNode();
|
||||
if ($currentPhpNode === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $this->isTagMatchingPhpDocNodeFactory($tag, $phpDocNodeFactory, $currentPhpNode)) {
|
||||
continue;
|
||||
}
|
||||
@ -323,6 +343,10 @@ final class BetterPhpDocParser extends PhpDocParser
|
||||
private function isTagMatchedByFactories(string $tag): bool
|
||||
{
|
||||
$currentPhpNode = $this->currentNodeProvider->getNode();
|
||||
if ($currentPhpNode === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
foreach ($this->phpDocNodeFactories as $phpDocNodeFactory) {
|
||||
if ($this->isTagMatchingPhpDocNodeFactory($tag, $phpDocNodeFactory, $currentPhpNode)) {
|
||||
return true;
|
||||
|
@ -0,0 +1,2 @@
|
||||
parameters:
|
||||
inferPrivatePropertyTypeFromConstructor: true
|
@ -58,6 +58,10 @@ final class PHPStanServicesFactory
|
||||
}
|
||||
|
||||
$additionalConfigFiles[] = __DIR__ . '/../../config/phpstan/type-extensions.neon';
|
||||
|
||||
// enable type inferring from constructor
|
||||
$additionalConfigFiles[] = __DIR__ . '/../../config/phpstan/better-infer.neon';
|
||||
|
||||
$this->container = $containerFactory->create(sys_get_temp_dir(), $additionalConfigFiles, []);
|
||||
|
||||
// clear bleeding edge fallback
|
||||
|
@ -30,11 +30,13 @@ use PhpParser\Node\Stmt\ClassConst;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Interface_;
|
||||
use PhpParser\Node\Stmt\Nop;
|
||||
use PhpParser\Node\Stmt\PropertyProperty;
|
||||
use PhpParser\Node\Stmt\Trait_;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Broker\Broker;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\Accessory\HasOffsetType;
|
||||
use PHPStan\Type\Accessory\NonEmptyArrayType;
|
||||
use PHPStan\Type\ArrayType;
|
||||
@ -50,11 +52,14 @@ use PHPStan\Type\NullType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\ObjectWithoutClassType;
|
||||
use PHPStan\Type\StringType;
|
||||
use PHPStan\Type\ThisType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeUtils;
|
||||
use PHPStan\Type\TypeWithClassName;
|
||||
use PHPStan\Type\UnionType;
|
||||
use Rector\BetterPhpDocParser\PhpDocParser\BetterPhpDocParser;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeContainer\ParsedNodesByType;
|
||||
use Rector\NodeTypeResolver\Contract\NodeTypeResolverAwareInterface;
|
||||
use Rector\NodeTypeResolver\Contract\PerNodeTypeResolver\PerNodeTypeResolverInterface;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
@ -65,6 +70,7 @@ use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
use Rector\PhpParser\Printer\BetterStandardPrinter;
|
||||
use Rector\TypeDeclaration\PHPStan\Type\ObjectTypeSpecifier;
|
||||
use ReflectionProperty;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
|
||||
final class NodeTypeResolver
|
||||
@ -114,16 +120,34 @@ final class NodeTypeResolver
|
||||
*/
|
||||
private $betterNodeFinder;
|
||||
|
||||
/**
|
||||
* @var ParsedNodesByType
|
||||
*/
|
||||
private $parsedNodesByType;
|
||||
|
||||
/**
|
||||
* @var BetterPhpDocParser
|
||||
*/
|
||||
private $betterPhpDocParser;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @param PerNodeTypeResolverInterface[] $perNodeTypeResolvers
|
||||
*/
|
||||
public function __construct(
|
||||
BetterStandardPrinter $betterStandardPrinter,
|
||||
BetterPhpDocParser $betterPhpDocParser,
|
||||
NameResolver $nameResolver,
|
||||
ParsedNodesByType $parsedNodesByType,
|
||||
CallableNodeTraverser $callableNodeTraverser,
|
||||
ClassReflectionTypesResolver $classReflectionTypesResolver,
|
||||
Broker $broker,
|
||||
TypeFactory $typeFactory,
|
||||
StaticTypeMapper $staticTypeMapper,
|
||||
ObjectTypeSpecifier $objectTypeSpecifier,
|
||||
BetterNodeFinder $betterNodeFinder,
|
||||
array $perNodeTypeResolvers
|
||||
@ -141,6 +165,9 @@ final class NodeTypeResolver
|
||||
$this->typeFactory = $typeFactory;
|
||||
$this->objectTypeSpecifier = $objectTypeSpecifier;
|
||||
$this->betterNodeFinder = $betterNodeFinder;
|
||||
$this->parsedNodesByType = $parsedNodesByType;
|
||||
$this->betterPhpDocParser = $betterPhpDocParser;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -314,10 +341,6 @@ final class NodeTypeResolver
|
||||
|
||||
public function getStaticType(Node $node): Type
|
||||
{
|
||||
if ($node instanceof String_) {
|
||||
return new ConstantStringType($node->value);
|
||||
}
|
||||
|
||||
if ($node instanceof Arg) {
|
||||
throw new ShouldNotHappenException('Arg does not have a type, use $arg->value instead');
|
||||
}
|
||||
@ -373,6 +396,14 @@ final class NodeTypeResolver
|
||||
return $staticType;
|
||||
}
|
||||
|
||||
if ($node instanceof PropertyFetch) {
|
||||
// compensate 3rd party non-analysed property reflection
|
||||
$vendorPropertyType = $this->getVendorPropertyFetchType($node);
|
||||
if ($vendorPropertyType !== null) {
|
||||
return $vendorPropertyType;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->objectTypeSpecifier->narrowToFullyQualifiedOrAlaisedObjectType($node, $staticType);
|
||||
}
|
||||
|
||||
@ -715,6 +746,20 @@ final class NodeTypeResolver
|
||||
|
||||
$propertyPropertyNode = $this->getClassNodeProperty($classNode, $propertyName);
|
||||
if ($propertyPropertyNode === null) {
|
||||
// also possible 3rd party vendor
|
||||
if ($node instanceof PropertyFetch) {
|
||||
$propertyOwnerStaticType = $this->getStaticType($node->var);
|
||||
} else {
|
||||
$propertyOwnerStaticType = $this->getStaticType($node->class);
|
||||
}
|
||||
|
||||
if (! $propertyOwnerStaticType instanceof ThisType && $propertyOwnerStaticType instanceof TypeWithClassName) {
|
||||
if ($this->parsedNodesByType->findClass($propertyOwnerStaticType->getClassName()) === null) {
|
||||
// positive assumption about 3rd party code
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -795,4 +840,46 @@ final class NodeTypeResolver
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getVendorPropertyFetchType(PropertyFetch $propertyFetch): ?Type
|
||||
{
|
||||
$varObjectType = $this->getStaticType($propertyFetch->var);
|
||||
if (! $varObjectType instanceof TypeWithClassName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->parsedNodesByType->findClass($varObjectType->getClassName())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3rd party code
|
||||
$propertyName = $this->nameResolver->getName($propertyFetch->name);
|
||||
if ($propertyName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! property_exists($varObjectType->getClassName(), $propertyName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// property is used
|
||||
$propertyReflection = new ReflectionProperty($varObjectType->getClassName(), $propertyName);
|
||||
if (! $propertyReflection->getDocComment()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$phpDocNode = $this->betterPhpDocParser->parseString((string) $propertyReflection->getDocComment());
|
||||
$varTagValues = $phpDocNode->getVarTagValues();
|
||||
|
||||
if (! isset($varTagValues[0])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$typeNode = $varTagValues[0]->type;
|
||||
if (! $typeNode instanceof TypeNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($typeNode, new Nop());
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +110,7 @@ PHP
|
||||
if ($parentNode instanceof Ternary) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! isset($funcCall->args[0]);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use PhpParser\Node;
|
||||
final class CurrentNodeProvider
|
||||
{
|
||||
/**
|
||||
* @var Node
|
||||
* @var Node|null
|
||||
*/
|
||||
private $node;
|
||||
|
||||
@ -18,7 +18,7 @@ final class CurrentNodeProvider
|
||||
$this->node = $node;
|
||||
}
|
||||
|
||||
public function getNode(): Node
|
||||
public function getNode(): ?Node
|
||||
{
|
||||
return $this->node;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user