mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-17 13:28:18 +01:00
Cover ArrayShape type and other doc nodes (#4967)
Co-authored-by: rector-bot <tomas@getrector.org>
This commit is contained in:
parent
8a648216ca
commit
61e1ceaf5c
@ -20,9 +20,9 @@
|
|||||||
"nette/robot-loader": "^3.2",
|
"nette/robot-loader": "^3.2",
|
||||||
"nette/utils": "^3.2",
|
"nette/utils": "^3.2",
|
||||||
"nikic/php-parser": "^4.10.4",
|
"nikic/php-parser": "^4.10.4",
|
||||||
"phpstan/phpdoc-parser": "^0.4.9",
|
"phpstan/phpdoc-parser": "^0.4.10",
|
||||||
"phpstan/phpstan": "^0.12.63",
|
"phpstan/phpstan": "^0.12.64",
|
||||||
"phpstan/phpstan-phpunit": "^0.12.16",
|
"phpstan/phpstan-phpunit": "^0.12.17",
|
||||||
"psr/simple-cache": "^1.0",
|
"psr/simple-cache": "^1.0",
|
||||||
"sebastian/diff": "^4.0",
|
"sebastian/diff": "^4.0",
|
||||||
"symfony/cache": "^4.4.8|^5.1",
|
"symfony/cache": "^4.4.8|^5.1",
|
||||||
@ -243,6 +243,7 @@
|
|||||||
"Rector\\RuleDocGenerator\\": "utils/rule-doc-generator/src",
|
"Rector\\RuleDocGenerator\\": "utils/rule-doc-generator/src",
|
||||||
"Rector\\PHPStanExtensions\\Tests\\": "utils/phpstan-extensions/tests",
|
"Rector\\PHPStanExtensions\\Tests\\": "utils/phpstan-extensions/tests",
|
||||||
"Rector\\PHPStanStaticTypeMapper\\Tests\\": "packages/phpstan-static-type-mapper/tests",
|
"Rector\\PHPStanStaticTypeMapper\\Tests\\": "packages/phpstan-static-type-mapper/tests",
|
||||||
|
"Rector\\StaticTypeMapper\\Tests\\": "packages/static-type-mapper/tests",
|
||||||
"Rector\\PHPStan\\Tests\\": "rules/phpstan/tests",
|
"Rector\\PHPStan\\Tests\\": "rules/phpstan/tests",
|
||||||
"Rector\\PHPUnitSymfony\\Tests\\": "rules/phpunit-symfony/tests",
|
"Rector\\PHPUnitSymfony\\Tests\\": "rules/phpunit-symfony/tests",
|
||||||
"Rector\\PHPUnit\\Tests\\": "rules/phpunit/tests",
|
"Rector\\PHPUnit\\Tests\\": "rules/phpunit/tests",
|
||||||
@ -286,7 +287,7 @@
|
|||||||
"Rector\\Utils\\NodeDocumentationGenerator\\": "utils/node-documentation-generator/src",
|
"Rector\\Utils\\NodeDocumentationGenerator\\": "utils/node-documentation-generator/src",
|
||||||
"Rector\\Utils\\NodeDocumentationGenerator\\Tests\\": "utils/node-documentation-generator/tests",
|
"Rector\\Utils\\NodeDocumentationGenerator\\Tests\\": "utils/node-documentation-generator/tests",
|
||||||
"Rector\\Utils\\PHPStanAttributeTypeSyncer\\": "utils/phpstan-attribute-type-syncer/src",
|
"Rector\\Utils\\PHPStanAttributeTypeSyncer\\": "utils/phpstan-attribute-type-syncer/src",
|
||||||
"Rector\\Utils\\PHPStanStaticTypeMapperChecker\\": "utils/phpstan-static-type-mapper-checker/src",
|
"Rector\\Utils\\PHPStanTypeMapperChecker\\": "utils/phpstan-type-mapper-checker/src",
|
||||||
"Rector\\Utils\\ProjectValidator\\": "utils/project-validator/src",
|
"Rector\\Utils\\ProjectValidator\\": "utils/project-validator/src",
|
||||||
"Rector\\Carbon\\Tests\\": "rules/carbon/tests"
|
"Rector\\Carbon\\Tests\\": "rules/carbon/tests"
|
||||||
}
|
}
|
||||||
|
@ -158,7 +158,7 @@ final class PhpDocInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return PhpDocTagNode[]&AttributeAwareNodeInterface[]
|
* @return PhpDocTagNode[]|AttributeAwareNodeInterface[]
|
||||||
*/
|
*/
|
||||||
public function getTagsByName(string $name): array
|
public function getTagsByName(string $name): array
|
||||||
{
|
{
|
||||||
|
@ -6,11 +6,14 @@ namespace Rector\StaticTypeMapper\PhpDoc;
|
|||||||
|
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
use PHPStan\Analyser\NameScope;
|
use PHPStan\Analyser\NameScope;
|
||||||
|
use PHPStan\PhpDoc\TypeNodeResolver;
|
||||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||||
use PHPStan\Type\Type;
|
use PHPStan\Type\Type;
|
||||||
use Rector\Core\Exception\NotImplementedException;
|
|
||||||
use Rector\StaticTypeMapper\Contract\PhpDocParser\PhpDocTypeMapperInterface;
|
use Rector\StaticTypeMapper\Contract\PhpDocParser\PhpDocTypeMapperInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see \Rector\StaticTypeMapper\Tests\PhpDoc\PhpDocTypeMapperTest
|
||||||
|
*/
|
||||||
final class PhpDocTypeMapper
|
final class PhpDocTypeMapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -18,12 +21,18 @@ final class PhpDocTypeMapper
|
|||||||
*/
|
*/
|
||||||
private $phpDocTypeMappers = [];
|
private $phpDocTypeMappers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TypeNodeResolver
|
||||||
|
*/
|
||||||
|
private $typeNodeResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param PhpDocTypeMapperInterface[] $phpDocTypeMappers
|
* @param PhpDocTypeMapperInterface[] $phpDocTypeMappers
|
||||||
*/
|
*/
|
||||||
public function __construct(array $phpDocTypeMappers)
|
public function __construct(array $phpDocTypeMappers, TypeNodeResolver $typeNodeResolver)
|
||||||
{
|
{
|
||||||
$this->phpDocTypeMappers = $phpDocTypeMappers;
|
$this->phpDocTypeMappers = $phpDocTypeMappers;
|
||||||
|
$this->typeNodeResolver = $typeNodeResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
|
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
|
||||||
@ -36,6 +45,8 @@ final class PhpDocTypeMapper
|
|||||||
return $phpDocTypeMapper->mapToPHPStanType($typeNode, $node, $nameScope);
|
return $phpDocTypeMapper->mapToPHPStanType($typeNode, $node, $nameScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($typeNode));
|
// fallback to PHPStan resolver
|
||||||
|
|
||||||
|
return $this->typeNodeResolver->resolve($typeNode, $nameScope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Rector\StaticTypeMapper\PhpDocParser;
|
|
||||||
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PHPStan\Analyser\NameScope;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
|
||||||
use PHPStan\Type\ArrayType;
|
|
||||||
use PHPStan\Type\MixedType;
|
|
||||||
use PHPStan\Type\Type;
|
|
||||||
use Rector\StaticTypeMapper\Contract\PhpDocParser\PhpDocTypeMapperInterface;
|
|
||||||
use Rector\StaticTypeMapper\PhpDoc\PhpDocTypeMapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see \Rector\PHPStanStaticTypeMapper\Tests\TypeMapper\ArrayTypeMapperTest
|
|
||||||
*/
|
|
||||||
final class ArrayTypeMapper implements PhpDocTypeMapperInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var PhpDocTypeMapper
|
|
||||||
*/
|
|
||||||
private $phpDocTypeMapper;
|
|
||||||
|
|
||||||
public function getNodeType(): string
|
|
||||||
{
|
|
||||||
return ArrayTypeNode::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @required
|
|
||||||
*/
|
|
||||||
public function autowireArrayTypeMapper(PhpDocTypeMapper $phpDocTypeMapper): void
|
|
||||||
{
|
|
||||||
$this->phpDocTypeMapper = $phpDocTypeMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ArrayTypeNode $typeNode
|
|
||||||
*/
|
|
||||||
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
|
|
||||||
{
|
|
||||||
$nestedType = $this->phpDocTypeMapper->mapToPHPStanType($typeNode->type, $node, $nameScope);
|
|
||||||
|
|
||||||
return new ArrayType(new MixedType(), $nestedType);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Rector\StaticTypeMapper\PhpDocParser;
|
|
||||||
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PHPStan\Analyser\NameScope;
|
|
||||||
use PHPStan\PhpDoc\TypeNodeResolver;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
|
||||||
use PHPStan\Type\Type;
|
|
||||||
use Rector\StaticTypeMapper\Contract\PhpDocParser\PhpDocTypeMapperInterface;
|
|
||||||
|
|
||||||
final class GenericTypeMapper implements PhpDocTypeMapperInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var TypeNodeResolver
|
|
||||||
*/
|
|
||||||
private $typeNodeResolver;
|
|
||||||
|
|
||||||
public function __construct(TypeNodeResolver $typeNodeResolver)
|
|
||||||
{
|
|
||||||
$this->typeNodeResolver = $typeNodeResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNodeType(): string
|
|
||||||
{
|
|
||||||
return GenericTypeNode::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
|
|
||||||
{
|
|
||||||
return $this->typeNodeResolver->resolve($typeNode, $nameScope);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Rector\StaticTypeMapper\PhpDocParser;
|
|
||||||
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PHPStan\Analyser\NameScope;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
|
||||||
use PHPStan\Type\Type;
|
|
||||||
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
|
|
||||||
use Rector\StaticTypeMapper\Contract\PhpDocParser\PhpDocTypeMapperInterface;
|
|
||||||
use Rector\StaticTypeMapper\PhpDoc\PhpDocTypeMapper;
|
|
||||||
|
|
||||||
final class IntersectionTypeMapper implements PhpDocTypeMapperInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var PhpDocTypeMapper
|
|
||||||
*/
|
|
||||||
private $phpDocTypeMapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var TypeFactory
|
|
||||||
*/
|
|
||||||
private $typeFactory;
|
|
||||||
|
|
||||||
public function __construct(TypeFactory $typeFactory)
|
|
||||||
{
|
|
||||||
$this->typeFactory = $typeFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNodeType(): string
|
|
||||||
{
|
|
||||||
return IntersectionTypeNode::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @required
|
|
||||||
*/
|
|
||||||
public function autowireIntersectionTypeMapper(PhpDocTypeMapper $phpDocTypeMapper): void
|
|
||||||
{
|
|
||||||
$this->phpDocTypeMapper = $phpDocTypeMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param IntersectionTypeNode $typeNode
|
|
||||||
*/
|
|
||||||
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
|
|
||||||
{
|
|
||||||
$unionedTypes = [];
|
|
||||||
foreach ($typeNode->types as $unionedTypeNode) {
|
|
||||||
$unionedTypes[] = $this->phpDocTypeMapper->mapToPHPStanType($unionedTypeNode, $node, $nameScope);
|
|
||||||
}
|
|
||||||
|
|
||||||
// to prevent missing class error, e.g. in tests
|
|
||||||
return $this->typeFactory->createMixedPassedOrUnionType($unionedTypes);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Rector\StaticTypeMapper\PhpDocParser;
|
|
||||||
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PHPStan\Analyser\NameScope;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
|
||||||
use PHPStan\Type\NullType;
|
|
||||||
use PHPStan\Type\Type;
|
|
||||||
use PHPStan\Type\UnionType;
|
|
||||||
use Rector\StaticTypeMapper\Contract\PhpDocParser\PhpDocTypeMapperInterface;
|
|
||||||
use Rector\StaticTypeMapper\PhpDoc\PhpDocTypeMapper;
|
|
||||||
|
|
||||||
final class NullableTypeMapper implements PhpDocTypeMapperInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var PhpDocTypeMapper
|
|
||||||
*/
|
|
||||||
private $phpDocTypeMapper;
|
|
||||||
|
|
||||||
public function getNodeType(): string
|
|
||||||
{
|
|
||||||
return NullableTypeNode::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @required
|
|
||||||
*/
|
|
||||||
public function autowireNullableTypeMapper(PhpDocTypeMapper $phpDocTypeMapper): void
|
|
||||||
{
|
|
||||||
$this->phpDocTypeMapper = $phpDocTypeMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param NullableTypeNode $typeNode
|
|
||||||
*/
|
|
||||||
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
|
|
||||||
{
|
|
||||||
$nestedType = $this->phpDocTypeMapper->mapToPHPStanType($typeNode->type, $node, $nameScope);
|
|
||||||
|
|
||||||
return new UnionType([new NullType(), $nestedType]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Rector\StaticTypeMapper\PhpDocParser;
|
|
||||||
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PHPStan\Analyser\NameScope;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
|
||||||
use PHPStan\Type\ThisType;
|
|
||||||
use PHPStan\Type\Type;
|
|
||||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
|
||||||
use Rector\StaticTypeMapper\Contract\PhpDocParser\PhpDocTypeMapperInterface;
|
|
||||||
|
|
||||||
final class ThisTypeMapper implements PhpDocTypeMapperInterface
|
|
||||||
{
|
|
||||||
public function getNodeType(): string
|
|
||||||
{
|
|
||||||
return ThisTypeNode::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
|
|
||||||
{
|
|
||||||
/** @var string $className */
|
|
||||||
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
|
|
||||||
|
|
||||||
return new ThisType($className);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\StaticTypeMapper\Tests\PhpDoc;
|
||||||
|
|
||||||
|
use Iterator;
|
||||||
|
use PhpParser\Node\Stmt\Nop;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||||
|
use PHPStan\Type\ArrayType;
|
||||||
|
use Rector\Core\HttpKernel\RectorKernel;
|
||||||
|
use Rector\StaticTypeMapper\PhpDoc\PhpDocTypeMapper;
|
||||||
|
use Rector\StaticTypeMapper\PHPStan\NameScopeFactory;
|
||||||
|
use Symplify\PackageBuilder\Testing\AbstractKernelTestCase;
|
||||||
|
|
||||||
|
final class PhpDocTypeMapperTest extends AbstractKernelTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var PhpDocTypeMapper
|
||||||
|
*/
|
||||||
|
private $phpDocTypeMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var NameScopeFactory
|
||||||
|
*/
|
||||||
|
private $nameScopeFactory;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->bootKernel(RectorKernel::class);
|
||||||
|
$this->phpDocTypeMapper = $this->getService(PhpDocTypeMapper::class);
|
||||||
|
$this->nameScopeFactory = $this->getService(NameScopeFactory::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideData()
|
||||||
|
*/
|
||||||
|
public function test(TypeNode $typeNode, string $expectedPHPStanType): void
|
||||||
|
{
|
||||||
|
$nop = new Nop();
|
||||||
|
$nameScope = $this->nameScopeFactory->createNameScopeFromNode($nop);
|
||||||
|
|
||||||
|
$phpStanType = $this->phpDocTypeMapper->mapToPHPStanType($typeNode, $nop, $nameScope);
|
||||||
|
|
||||||
|
$this->assertInstanceOf($expectedPHPStanType, $phpStanType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideData(): Iterator
|
||||||
|
{
|
||||||
|
$arrayShapeNode = new ArrayShapeNode([new ArrayShapeItemNode(null, true, new IdentifierTypeNode('string'))]);
|
||||||
|
|
||||||
|
yield [$arrayShapeNode, ArrayType::class];
|
||||||
|
}
|
||||||
|
}
|
@ -688,3 +688,6 @@ parameters:
|
|||||||
- rules/naming/src/PropertyRenamer/AbstractPropertyRenamer.php
|
- rules/naming/src/PropertyRenamer/AbstractPropertyRenamer.php
|
||||||
|
|
||||||
- '#Cannot call method getParentNode\(\) on Rector\\DeadCode\\ValueObject\\VariableNodeUse\|null#'
|
- '#Cannot call method getParentNode\(\) on Rector\\DeadCode\\ValueObject\\VariableNodeUse\|null#'
|
||||||
|
|
||||||
|
# @todo resolve later
|
||||||
|
- '#Content of method "endsWith\(\)" is duplicated with method in "Rector\\Naming\\ExpectedNameResolver\\AbstractExpectedNameResolver" class\. Use unique content or abstract service instead#'
|
||||||
|
@ -15,8 +15,9 @@ use PhpParser\Node\Expr\PropertyFetch;
|
|||||||
use PhpParser\Node\Stmt\Class_;
|
use PhpParser\Node\Stmt\Class_;
|
||||||
use PhpParser\Node\Stmt\Property;
|
use PhpParser\Node\Stmt\Property;
|
||||||
use PhpParser\Node\Stmt\PropertyProperty;
|
use PhpParser\Node\Stmt\PropertyProperty;
|
||||||
use PHPStan\Type\ArrayType;
|
use PHPStan\Type\Type;
|
||||||
use PHPStan\Type\IterableType;
|
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||||
|
use Rector\CodingStyle\TypeAnalyzer\IterableTypeAnalyzer;
|
||||||
use Rector\Core\PhpParser\Node\Manipulator\PropertyFetchManipulator;
|
use Rector\Core\PhpParser\Node\Manipulator\PropertyFetchManipulator;
|
||||||
use Rector\Core\Rector\AbstractRector;
|
use Rector\Core\Rector\AbstractRector;
|
||||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||||
@ -36,9 +37,17 @@ final class AddArrayDefaultToArrayPropertyRector extends AbstractRector
|
|||||||
*/
|
*/
|
||||||
private $propertyFetchManipulator;
|
private $propertyFetchManipulator;
|
||||||
|
|
||||||
public function __construct(PropertyFetchManipulator $propertyFetchManipulator)
|
/**
|
||||||
{
|
* @var IterableTypeAnalyzer
|
||||||
|
*/
|
||||||
|
private $iterableTypeAnalyzer;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
PropertyFetchManipulator $propertyFetchManipulator,
|
||||||
|
IterableTypeAnalyzer $iterableTypeAnalyzer
|
||||||
|
) {
|
||||||
$this->propertyFetchManipulator = $propertyFetchManipulator;
|
$this->propertyFetchManipulator = $propertyFetchManipulator;
|
||||||
|
$this->iterableTypeAnalyzer = $iterableTypeAnalyzer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRuleDefinition(): RuleDefinition
|
public function getRuleDefinition(): RuleDefinition
|
||||||
@ -125,17 +134,12 @@ CODE_SAMPLE
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Property $property */
|
$varType = $this->resolveVarType($node);
|
||||||
$property = $node->getAttribute(AttributeKey::PARENT_NODE);
|
if ($varType === null) {
|
||||||
|
|
||||||
// we need docblock
|
|
||||||
$propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO);
|
|
||||||
if ($propertyPhpDocInfo === null) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$varType = $propertyPhpDocInfo->getVarType();
|
if (! $this->iterableTypeAnalyzer->detect($varType)) {
|
||||||
if (! $varType instanceof ArrayType && ! $varType instanceof IterableType) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,6 +244,20 @@ CODE_SAMPLE
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resolveVarType(PropertyProperty $propertyProperty): ?Type
|
||||||
|
{
|
||||||
|
/** @var Property $property */
|
||||||
|
$property = $propertyProperty->getAttribute(AttributeKey::PARENT_NODE);
|
||||||
|
|
||||||
|
// we need docblock
|
||||||
|
$propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO);
|
||||||
|
if (! $propertyPhpDocInfo instanceof PhpDocInfo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $propertyPhpDocInfo->getVarType();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[] $propertyNames
|
* @param string[] $propertyNames
|
||||||
*/
|
*/
|
||||||
|
36
rules/coding-style/src/TypeAnalyzer/IterableTypeAnalyzer.php
Normal file
36
rules/coding-style/src/TypeAnalyzer/IterableTypeAnalyzer.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\CodingStyle\TypeAnalyzer;
|
||||||
|
|
||||||
|
use PHPStan\Type\ArrayType;
|
||||||
|
use PHPStan\Type\IterableType;
|
||||||
|
use PHPStan\Type\Type;
|
||||||
|
use PHPStan\Type\UnionType;
|
||||||
|
|
||||||
|
final class IterableTypeAnalyzer
|
||||||
|
{
|
||||||
|
public function detect(Type $type): bool
|
||||||
|
{
|
||||||
|
if ($type instanceof ArrayType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type instanceof IterableType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type instanceof UnionType) {
|
||||||
|
foreach ($type->getTypes() as $unionedType) {
|
||||||
|
if (! $this->detect($unionedType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Rector\CodingStyle\Tests\Rector\Class_\AddArrayDefaultToArrayPropertyRector\Fixture;
|
||||||
|
|
||||||
|
class ArrayAndType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array|SomeEntity[]
|
||||||
|
*/
|
||||||
|
public $entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Rector\CodingStyle\Tests\Rector\Class_\AddArrayDefaultToArrayPropertyRector\Fixture;
|
||||||
|
|
||||||
|
class ArrayAndType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array|SomeEntity[]
|
||||||
|
*/
|
||||||
|
public $entities = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
@ -2,23 +2,13 @@
|
|||||||
|
|
||||||
namespace Rector\CodingStyle\Tests\Rector\Class_\AddArrayDefaultToArrayPropertyRector\Fixture;
|
namespace Rector\CodingStyle\Tests\Rector\Class_\AddArrayDefaultToArrayPropertyRector\Fixture;
|
||||||
|
|
||||||
class Fixture2
|
class ScalarInteger
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var int[]
|
* @var int[]
|
||||||
*/
|
*/
|
||||||
private $items;
|
private $items;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array|SomeEntity[]
|
|
||||||
*/
|
|
||||||
public $entities;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Bar[]|Foo[]
|
|
||||||
*/
|
|
||||||
private $combined;
|
|
||||||
|
|
||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
@ -32,23 +22,13 @@ class Fixture2
|
|||||||
|
|
||||||
namespace Rector\CodingStyle\Tests\Rector\Class_\AddArrayDefaultToArrayPropertyRector\Fixture;
|
namespace Rector\CodingStyle\Tests\Rector\Class_\AddArrayDefaultToArrayPropertyRector\Fixture;
|
||||||
|
|
||||||
class Fixture2
|
class ScalarInteger
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var int[]
|
* @var int[]
|
||||||
*/
|
*/
|
||||||
private $items = [];
|
private $items = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array|SomeEntity[]
|
|
||||||
*/
|
|
||||||
public $entities = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Bar[]|Foo[]
|
|
||||||
*/
|
|
||||||
private $combined = [];
|
|
||||||
|
|
||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Rector\CodingStyle\Tests\Rector\Class_\AddArrayDefaultToArrayPropertyRector\Fixture;
|
||||||
|
|
||||||
|
class TwoTypes
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Bar[]|Foo[]
|
||||||
|
*/
|
||||||
|
private $combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Rector\CodingStyle\Tests\Rector\Class_\AddArrayDefaultToArrayPropertyRector\Fixture;
|
||||||
|
|
||||||
|
class TwoTypes
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Bar[]|Foo[]
|
||||||
|
*/
|
||||||
|
private $combined = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
@ -20,7 +20,6 @@ final class PropertyNamingTest extends AbstractKernelTestCase
|
|||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$this->bootKernel(RectorKernel::class);
|
$this->bootKernel(RectorKernel::class);
|
||||||
|
|
||||||
$this->propertyNaming = $this->getService(PropertyNaming::class);
|
$this->propertyNaming = $this->getService(PropertyNaming::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Rector\Utils\PHPStanStaticTypeMapperChecker\Command;
|
|
||||||
|
|
||||||
use PHPStan\Type\NonexistentParentClassType;
|
|
||||||
use PHPStan\Type\ParserNodeTypeToPHPStanType;
|
|
||||||
use Rector\Core\Console\Command\AbstractCommand;
|
|
||||||
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
|
|
||||||
use Rector\Utils\PHPStanStaticTypeMapperChecker\Finder\PHPStanTypeClassFinder;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
use Symplify\PackageBuilder\Console\ShellCode;
|
|
||||||
|
|
||||||
final class CheckStaticTypeMappersCommand extends AbstractCommand
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var TypeMapperInterface[]
|
|
||||||
*/
|
|
||||||
private $typeMappers = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var SymfonyStyle
|
|
||||||
*/
|
|
||||||
private $symfonyStyle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var PHPStanTypeClassFinder
|
|
||||||
*/
|
|
||||||
private $phpStanTypeClassFinder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TypeMapperInterface[] $typeMappers
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
array $typeMappers,
|
|
||||||
SymfonyStyle $symfonyStyle,
|
|
||||||
PHPStanTypeClassFinder $phpStanTypeClassFinder
|
|
||||||
) {
|
|
||||||
$this->typeMappers = $typeMappers;
|
|
||||||
$this->symfonyStyle = $symfonyStyle;
|
|
||||||
$this->phpStanTypeClassFinder = $phpStanTypeClassFinder;
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function configure(): void
|
|
||||||
{
|
|
||||||
$this->setDescription('[DEV] check PHPStan types to TypeMappers');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
||||||
{
|
|
||||||
$missingNodeClasses = $this->getMissingNodeClasses();
|
|
||||||
if ($missingNodeClasses === []) {
|
|
||||||
$this->symfonyStyle->success('All PHPStan Types are covered by TypeMapper');
|
|
||||||
|
|
||||||
return ShellCode::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($missingNodeClasses as $missingNodeClass) {
|
|
||||||
$errorMessage = sprintf(
|
|
||||||
'Add new class to "%s" that implements "%s" for "%s" type',
|
|
||||||
'packages/phpstan-static-type-mapper/src/TypeMapper',
|
|
||||||
TypeMapperInterface::class,
|
|
||||||
$missingNodeClass
|
|
||||||
);
|
|
||||||
$this->symfonyStyle->error($errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ShellCode::ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return class-string[]
|
|
||||||
*/
|
|
||||||
private function getMissingNodeClasses(): array
|
|
||||||
{
|
|
||||||
$phpStanTypeClasses = $this->phpStanTypeClassFinder->find();
|
|
||||||
$supportedTypeClasses = $this->getSupportedTypeClasses();
|
|
||||||
|
|
||||||
$unsupportedTypeClasses = [];
|
|
||||||
foreach ($phpStanTypeClasses as $phpStanTypeClass) {
|
|
||||||
foreach ($supportedTypeClasses as $supportedPHPStanTypeClass) {
|
|
||||||
if (is_a($phpStanTypeClass, $supportedPHPStanTypeClass, true)) {
|
|
||||||
continue 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$unsupportedTypeClasses[] = $phpStanTypeClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
$typesToRemove = [NonexistentParentClassType::class, ParserNodeTypeToPHPStanType::class];
|
|
||||||
|
|
||||||
return array_diff($unsupportedTypeClasses, $typesToRemove);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
private function getSupportedTypeClasses(): array
|
|
||||||
{
|
|
||||||
$supportedPHPStanTypeClasses = [];
|
|
||||||
foreach ($this->typeMappers as $typeMappers) {
|
|
||||||
$supportedPHPStanTypeClasses[] = $typeMappers->getNodeClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $supportedPHPStanTypeClasses;
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,5 +12,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
|||||||
->autowire()
|
->autowire()
|
||||||
->autoconfigure();
|
->autoconfigure();
|
||||||
|
|
||||||
$services->load('Rector\Utils\PHPStanStaticTypeMapperChecker\\', __DIR__ . '/../src');
|
$services->load('Rector\Utils\PHPStanTypeMapperChecker\\', __DIR__ . '/../src');
|
||||||
};
|
};
|
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\Utils\PHPStanTypeMapperChecker\Command;
|
||||||
|
|
||||||
|
use Rector\Core\Console\Command\AbstractCommand;
|
||||||
|
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
|
||||||
|
use Rector\Utils\PHPStanTypeMapperChecker\Validator\MissingPHPStanTypeMappersResolver;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symplify\PackageBuilder\Console\ShellCode;
|
||||||
|
|
||||||
|
final class CheckStaticTypeMappersCommand extends AbstractCommand
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var SymfonyStyle
|
||||||
|
*/
|
||||||
|
private $symfonyStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var MissingPHPStanTypeMappersResolver
|
||||||
|
*/
|
||||||
|
private $missingPHPStanTypeMappersResolver;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
SymfonyStyle $symfonyStyle,
|
||||||
|
MissingPHPStanTypeMappersResolver $missingPHPStanTypeMappersResolver
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->symfonyStyle = $symfonyStyle;
|
||||||
|
$this->missingPHPStanTypeMappersResolver = $missingPHPStanTypeMappersResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->setDescription('[DEV] Check PHPStan types to TypeMappers');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$missingTypeNodeClasses = $this->missingPHPStanTypeMappersResolver->resolve();
|
||||||
|
|
||||||
|
if ($missingTypeNodeClasses === []) {
|
||||||
|
$this->symfonyStyle->success('All PHPStan Types and PHPStan Doc Types are covered');
|
||||||
|
return ShellCode::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($missingTypeNodeClasses as $missingDocTypeNodeClass) {
|
||||||
|
$errorMessage = sprintf(
|
||||||
|
'Add new class to "%s" that implements "%s" for "%s" type',
|
||||||
|
'packages/phpstan-static-type-mapper/src/TypeMapper',
|
||||||
|
TypeMapperInterface::class,
|
||||||
|
$missingDocTypeNodeClass
|
||||||
|
);
|
||||||
|
$this->symfonyStyle->error($errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ShellCode::ERROR;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\Utils\PHPStanTypeMapperChecker\DataProvider;
|
||||||
|
|
||||||
|
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
|
||||||
|
|
||||||
|
final class SupportedTypeMappersDataProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var TypeMapperInterface[]
|
||||||
|
*/
|
||||||
|
private $typeMappers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TypeMapperInterface[] $typeMappers
|
||||||
|
*/
|
||||||
|
public function __construct(array $typeMappers)
|
||||||
|
{
|
||||||
|
$this->typeMappers = $typeMappers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function provide(): array
|
||||||
|
{
|
||||||
|
$supportedPHPStanTypeClasses = [];
|
||||||
|
foreach ($this->typeMappers as $typeMappers) {
|
||||||
|
$supportedPHPStanTypeClasses[] = $typeMappers->getNodeClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $supportedPHPStanTypeClasses;
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Rector\Utils\PHPStanStaticTypeMapperChecker\Finder;
|
namespace Rector\Utils\PHPStanTypeMapperChecker\Finder;
|
||||||
|
|
||||||
use Nette\Loaders\RobotLoader;
|
use Nette\Loaders\RobotLoader;
|
||||||
use Nette\Utils\Strings;
|
use Nette\Utils\Strings;
|
||||||
@ -16,7 +16,7 @@ final class PHPStanTypeClassFinder
|
|||||||
private const ACCESSORY_SEPARATED_REGEX = '#\bAccessory\b#';
|
private const ACCESSORY_SEPARATED_REGEX = '#\bAccessory\b#';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return class-string[]
|
* @return string[]
|
||||||
*/
|
*/
|
||||||
public function find(): array
|
public function find(): array
|
||||||
{
|
{
|
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\Utils\PHPStanTypeMapperChecker\Validator;
|
||||||
|
|
||||||
|
use PHPStan\Type\NonexistentParentClassType;
|
||||||
|
use PHPStan\Type\ParserNodeTypeToPHPStanType;
|
||||||
|
use Rector\Utils\PHPStanTypeMapperChecker\DataProvider\SupportedTypeMappersDataProvider;
|
||||||
|
use Rector\Utils\PHPStanTypeMapperChecker\Finder\PHPStanTypeClassFinder;
|
||||||
|
|
||||||
|
final class MissingPHPStanTypeMappersResolver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var SupportedTypeMappersDataProvider
|
||||||
|
*/
|
||||||
|
private $supportedTypeMappersResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var PHPStanTypeClassFinder
|
||||||
|
*/
|
||||||
|
private $phpStanTypeClassFinder;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
PHPStanTypeClassFinder $phpStanTypeClassFinder,
|
||||||
|
SupportedTypeMappersDataProvider $supportedTypeMappersResolver
|
||||||
|
) {
|
||||||
|
$this->supportedTypeMappersResolver = $supportedTypeMappersResolver;
|
||||||
|
$this->phpStanTypeClassFinder = $phpStanTypeClassFinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function resolve(): array
|
||||||
|
{
|
||||||
|
$typeClasses = $this->phpStanTypeClassFinder->find();
|
||||||
|
$supportedTypeClasses = $this->supportedTypeMappersResolver->provide();
|
||||||
|
|
||||||
|
$unsupportedTypeClasses = [];
|
||||||
|
foreach ($typeClasses as $phpStanTypeClass) {
|
||||||
|
foreach ($supportedTypeClasses as $supportedPHPStanTypeClass) {
|
||||||
|
if (is_a($phpStanTypeClass, $supportedPHPStanTypeClass, true)) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$unsupportedTypeClasses[] = $phpStanTypeClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
$typesToRemove = [NonexistentParentClassType::class, ParserNodeTypeToPHPStanType::class];
|
||||||
|
|
||||||
|
return array_diff($unsupportedTypeClasses, $typesToRemove);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user