[Type Declaration] Create new unit test for ArrayTypeMapper with fixes (#4054)

* Create new unit test for ArrayTypeMapper with fixes

* Fix typo in method name that breaks all tests

* Remove skip too many fixtures to implement the fix for these fixtures as a next PR.
This commit is contained in:
dobryy 2020-08-29 10:41:19 +02:00 committed by GitHub
parent 190d7cf616
commit d40309963c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 111 additions and 128 deletions

View File

@ -238,7 +238,8 @@
"Rector\\Decomplex\\Tests\\": "rules/decomplex/tests",
"Rector\\Downgrade\\Tests\\": "rules/downgrade/tests",
"Rector\\SymfonyPhpConfig\\Tests\\": "rules/symfony-php-config/tests",
"Rector\\Injection\\Tests\\": "rules/injection/tests"
"Rector\\Injection\\Tests\\": "rules/injection/tests",
"Rector\\PHPStanStaticTypeMapper\\Tests\\": "packages/phpstan-static-type-mapper/tests"
},
"classmap": [
"rules/cakephp/tests/Rector/Name/ImplicitShortClassNameUseStatementRector/Source",

View File

@ -6,6 +6,7 @@ namespace Rector\AttributeAwarePhpDoc\Ast\Type;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use Rector\BetterPhpDocParser\Attributes\Attribute\AttributeTrait;
use Rector\BetterPhpDocParser\Contract\PhpDocNode\AttributeAwareNodeInterface;
@ -21,6 +22,10 @@ final class AttributeAwareArrayTypeNode extends ArrayTypeNode implements Attribu
return sprintf('(%s)[]', $typeAsString);
}
if ($this->type instanceof UnionTypeNode || $this->type instanceof ArrayTypeNode) {
return sprintf('array<%s>', $typeAsString);
}
return $typeAsString . '[]';
}
}

View File

@ -7,9 +7,7 @@ namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
use PhpParser\Node;
use PhpParser\Node\Name;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantIntegerType;
@ -18,6 +16,7 @@ use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareArrayTypeNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareGenericTypeNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareIdentifierTypeNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareUnionTypeNode;
@ -58,16 +57,17 @@ final class ArrayTypeMapper implements TypeMapperInterface
*/
public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
{
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($type->getItemType());
$itemType = $type->getItemType();
if ($itemTypeNode instanceof UnionTypeNode) {
return $this->convertUnionArrayTypeNodesToArrayTypeOfUnionTypeNodes($itemTypeNode);
if ($itemType instanceof UnionType) {
return $this->createUnionType($itemType);
}
if ($this->isGenericArrayCandidate($type)) {
return $this->createGenericArrayType($type->getKeyType(), $itemTypeNode);
if ($itemType instanceof ArrayType || $this->isGenericArrayCandidate($type)) {
return $this->createGenericArrayType($type, $this->isGenericArrayCandidate($type));
}
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($itemType);
return new ArrayTypeNode($itemTypeNode);
}
@ -94,24 +94,21 @@ final class ArrayTypeMapper implements TypeMapperInterface
return $this->phpStanStaticTypeMapper->mapToDocString($itemType, $parentType) . '[]';
}
private function convertUnionArrayTypeNodesToArrayTypeOfUnionTypeNodes(
UnionTypeNode $unionTypeNode
): AttributeAwareUnionTypeNode {
private function createUnionType(UnionType $unionType): ArrayTypeNode
{
$unionedArrayType = [];
foreach ($unionTypeNode->types as $unionedType) {
if ($unionedType instanceof UnionTypeNode) {
foreach ($unionedType->types as $key => $subUnionedType) {
$unionedType->types[$key] = new ArrayTypeNode($subUnionedType);
}
$unionedArrayType[] = $unionedType;
continue;
}
$unionedArrayType[] = new ArrayTypeNode($unionedType);
foreach ($unionType->getTypes() as $unionedType) {
$typeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($unionedType);
$unionedArrayType[(string) $typeNode] = $typeNode;
}
return new AttributeAwareUnionTypeNode($unionedArrayType);
if (count($unionedArrayType) > 1) {
return new AttributeAwareArrayTypeNode(new AttributeAwareUnionTypeNode($unionedArrayType));
}
/** @var TypeNode $arrayType */
$arrayType = array_shift($unionedArrayType);
return new AttributeAwareArrayTypeNode($arrayType);
}
private function isGenericArrayCandidate(ArrayType $arrayType): bool
@ -148,15 +145,20 @@ final class ArrayTypeMapper implements TypeMapperInterface
return false;
}
private function createGenericArrayType(Type $keyType, TypeNode $itemTypeNode): AttributeAwareGenericTypeNode
private function createGenericArrayType(ArrayType $arrayType, bool $withKey = false): AttributeAwareGenericTypeNode
{
/** @var IdentifierTypeNode $keyTypeNode */
$keyTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($keyType);
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($arrayType->getItemType());
$attributeAwareIdentifierTypeNode = new AttributeAwareIdentifierTypeNode('array');
if ($withKey) {
$keyTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($arrayType->getKeyType());
$genericTypes = [$keyTypeNode, $itemTypeNode];
} else {
$genericTypes = [$itemTypeNode];
}
// @see https://github.com/phpstan/phpdoc-parser/blob/98a088b17966bdf6ee25c8a4b634df313d8aa531/tests/PHPStan/Parser/PhpDocParserTest.php#L2692-L2696
$genericTypes = [$keyTypeNode, $itemTypeNode];
return new AttributeAwareGenericTypeNode($attributeAwareIdentifierTypeNode, $genericTypes);
}

View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
use Iterator;
use PHPStan\Type\ArrayType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use PHPStan\Type\UnionType;
use Rector\Core\HttpKernel\RectorKernel;
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;
class ArrayTypeMapperTest extends AbstractKernelTestCase
{
/**
* @var ArrayTypeMapper
*/
private $arrayTypeMapper;
protected function setUp(): void
{
self::bootKernel(RectorKernel::class);
$this->arrayTypeMapper = self::$container->get(ArrayTypeMapper::class);
}
/**
* @dataProvider provideDataMapToPHPStanPhpDocTypeNode()
*/
public function testMapToPHPStanPhpDocTypeNode(ArrayType $arrayType, string $expectedResult): void
{
$actualTypeNode = $this->arrayTypeMapper->mapToPHPStanPhpDocTypeNode($arrayType);
self::assertSame($expectedResult, (string) $actualTypeNode);
}
public function provideDataMapToPHPStanPhpDocTypeNode(): Iterator
{
$arrayType = new ArrayType(new MixedType(), new StringType());
yield[$arrayType, 'string[]'];
$unionArrayType = new ArrayType(new MixedType(), new UnionType([new StringType(), new IntegerType()]));
yield [$unionArrayType, 'array<int|string>'];
$unionArrayType = new ArrayType(new MixedType(), new UnionType([new StringType(), new IntegerType()]));
$moreNestedUnionArrayType = new ArrayType(new MixedType(), $unionArrayType);
yield [$moreNestedUnionArrayType, 'array<array<int|string>>'];
$evenMoreNestedUnionArrayType = new ArrayType(new MixedType(), $moreNestedUnionArrayType);
yield [$evenMoreNestedUnionArrayType, 'array<array<array<int|string>>>'];
$arrayType = new ArrayType(new MixedType(), new UnionType([
new StringType(),
new StringType(),
new StringType(),
]));
yield[$arrayType, 'string[]'];
$arrayType = new ArrayType(new StringType(), new ArrayType(new MixedType(), new StringType()));
yield[$arrayType, 'array<string, string[]>'];
$arrayType = new ArrayType(new StringType(), new ArrayType(new MixedType(), new UnionType([
new StringType(),
new IntegerType(),
])));
yield[$arrayType, 'array<string, array<int|string>>'];
$arrayType = new ArrayType(new StringType(), new IntegerType());
yield[$arrayType, 'array<string, int>'];
}
}

View File

@ -54,7 +54,6 @@ final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer
$type = $this->typeNormalizer->normalizeArrayTypeAndArrayNever($originalType);
$type = $this->typeNormalizer->uniqueateConstantArrayType($type);
$type = $this->typeNormalizer->normalizeArrayOfUnionToUnionArray($type);
// in case of void, check return type of children methods
if ($type instanceof MixedType) {

View File

@ -25,7 +25,7 @@ use Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\
final class FullyQualifiedName
{
/**
* @return \Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Source\ValidationResult[]|bool[]
* @return array<\Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Source\ValidationResult|bool>
*/
public function isValidDataProvider(): array
{

View File

@ -25,7 +25,7 @@ use Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\
final class FullyQualifiedNameNestedArray
{
/**
* @return string[][]|\Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Source\ValidationResult[][]
* @return array<string, array<string|\Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Source\ValidationResult>>
*/
public function getValidationErrorMessagesAsStringDataProvider(): array
{

View File

@ -1,31 +0,0 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
class SkipTooMany
{
/**
* @return mixed[][]
*/
public function provideFilesToContent(): array
{
return [
[__DIR__ . '/Yaml/spaces.yml', [
'address' => [
'street' => '742 Evergreen Terrace',
],
]],
# arrays
[__DIR__ . '/Yaml/list.yml', [
'services' => ['SomeService', 'SomeService'],
]],
[__DIR__ . '/Yaml/array.yml', [
'services' => [
'SomeService' => null,
],
]],
# multi lines
[__DIR__ . '/Yaml/multi-lines.yml', ['perex' => 'Multi' . PHP_EOL . 'line']],
];
}
}

View File

@ -1,66 +0,0 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use Nette\Utils\Strings;
final class SkipToOMany2
{
/**
* @param string[] $packageNames
*/
public function createPackagesData(array $packageNames): array
{
$packagesData = [];
foreach ($packageNames as $packageName) {
$packageKey = $this->createPackageKey($packageName);
$packageDownloads = $this->provideForPackage($packageName);
if ($packageDownloads === []) {
continue;
}
// complete relative number of downloads
$totalDownloads = array_sum($packageDownloads[1]);
foreach ($packageDownloads[1] as $version => $absoluteDownloads) {
$relativeRate = 100 * ($absoluteDownloads / $totalDownloads);
$packageDownloads[1][$version] = [
'absolute_downloads' => $absoluteDownloads,
'relative_downloads' => round($relativeRate, 1),
'version_publish_date' => $this->provideForPackageAndVersion(
$packageName,
$version
),
];
}
$packagesData[$packageKey] = [
'package_name' => $packageName,
'package_short_name' => Strings::after($packageName, '/'),
] + $packageDownloads;
}
return $packagesData;
}
private function createPackageKey(string $packageName): string
{
return Strings::replace($packageName, '#(/|-)#', '_');
}
/**
* @return int[][]
*/
private function provideForPackage(string $packageName): array
{
return [[1, 3]];
}
private function provideForPackageAndVersion(): ?string
{
return '' ? null : 'string';
}
}

View File

@ -45,7 +45,7 @@ abstract class AbstractRectorTestCase extends AbstractGenericRectorTestCase
$inputFileInfo = $inputFileInfoAndExpectedFileInfo->getInputFileInfo();
$this->nodeScopeResolver->setAnalysedFiles([$inputFileInfo->getRealPath()]);
$expectedFileInfo = $inputFileInfoAndExpectedFileInfo->getExpectedFilenfo();
$expectedFileInfo = $inputFileInfoAndExpectedFileInfo->getExpectedFileInfo();
$this->doTestFileMatchesExpectedContent($inputFileInfo, $expectedFileInfo, $fixtureFileInfo);
$this->originalTempFileInfo = $inputFileInfo;