Cover ArrayShape type and other doc nodes (#4967)

Co-authored-by: rector-bot <tomas@getrector.org>
This commit is contained in:
Tomas Votruba 2020-12-23 22:11:37 +01:00 committed by GitHub
parent 8a648216ca
commit 61e1ceaf5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 359 additions and 378 deletions

View File

@ -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"
} }

View File

@ -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
{ {

View File

@ -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);
} }
} }

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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]);
}
}

View File

@ -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);
}
}

View File

@ -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];
}
}

View File

@ -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#'

View File

@ -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
*/ */

View 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;
}
}

View File

@ -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 = [];
}
?>

View File

@ -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) {

View File

@ -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 = [];
}
?>

View File

@ -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);
} }

View File

@ -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;
}
}

View File

@ -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');
}; };

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
{ {

View File

@ -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);
}
}