[Downgrade PHP 7.1] feature/downgrade iterable pseudo type (#4706)

* [Downgrade PHP 7.1] iterable pseudo-type

* fix PHP version type

* cleanup

* static fixes

Co-authored-by: Norbert Orzechowicz <norbert@orzechowicz.pl>
This commit is contained in:
Tomas Votruba 2020-11-27 13:00:56 +01:00 committed by GitHub
parent 74a17fde01
commit 51ab276d57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 423 additions and 110 deletions

View File

@ -1,4 +1,6 @@
# 625 Rules Overview
# 626 Rules Overview
<br>
## Categories
@ -24,7 +26,7 @@
- [DoctrineGedmoToKnplabs](#doctrinegedmotoknplabs) (7)
- [DowngradePhp71](#downgradephp71) (4)
- [DowngradePhp71](#downgradephp71) (6)
- [DowngradePhp72](#downgradephp72) (2)
@ -56,7 +58,7 @@
- [Nette](#nette) (20)
- [NetteCodeQuality](#nettecodequality) (6)
- [NetteCodeQuality](#nettecodequality) (7)
- [NetteKdyby](#nettekdyby) (4)
@ -134,7 +136,7 @@
- [SymfonyPHPUnit](#symfonyphpunit) (1)
- [SymfonyPhpConfig](#symfonyphpconfig) (3)
- [SymfonyPhpConfig](#symfonyphpconfig) (1)
- [Transform](#transform) (12)
@ -142,6 +144,8 @@
- [TypeDeclaration](#typedeclaration) (10)
<br>
## Architecture
### ReplaceParentRepositoryCallsByRepositoryPropertyRector
@ -362,7 +366,7 @@ Moves array options to fluent setter method calls.
use Rector\CakePHP\Rector\MethodCall\ArrayToFluentCallRector;
use Rector\CakePHP\ValueObject\ArrayToFluentCall;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\SymfonyPhpConfig\ValueObjectInliner::inline(s;
use Symplify\SymfonyPhpConfig\ValueObjectInliner;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
@ -4822,6 +4826,90 @@ return static function (ContainerConfigurator $containerConfigurator): void {
<br>
### DowngradeIterablePseudoTypeParamDeclarationRector
Remove the iterable pseudo type params, add `@param` tags instead
:wrench: **configure it!**
- class: `Rector\DowngradePhp71\Rector\FunctionLike\DowngradeIterablePseudoTypeParamDeclarationRector`
```php
use Rector\DowngradePhp71\Rector\FunctionLike\DowngradeIterablePseudoTypeParamDeclarationRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(DowngradeIterablePseudoTypeParamDeclarationRector::class)
->call('configure', [[
DowngradeIterablePseudoTypeParamDeclarationRector::ADD_DOC_BLOCK => true,
]]);
};
```
```diff
<?php
class SomeClass
{
- public function run(iterable $iterator)
+ /**
+ * @param mixed[]|\Traversable $iterator
+ */
+ public function run($iterator)
{
// do something
}
}
```
<br>
### DowngradeIterablePseudoTypeReturnDeclarationRector
Remove returning iterable pseud type, add a `@return` tag instead
:wrench: **configure it!**
- class: `Rector\DowngradePhp71\Rector\FunctionLike\DowngradeIterablePseudoTypeReturnDeclarationRector`
```php
use Rector\DowngradePhp71\Rector\FunctionLike\DowngradeIterablePseudoTypeReturnDeclarationRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(DowngradeIterablePseudoTypeReturnDeclarationRector::class)
->call('configure', [[
DowngradeIterablePseudoTypeReturnDeclarationRector::ADD_DOC_BLOCK => true,
]]);
};
```
```diff
<?php
class SomeClass
{
- public function run(): iterable
+ /**
+ * @return mixed[]|\Traversable
+ */
+ public function run()
{
// do something
}
}
```
<br>
### DowngradeNullableTypeParamDeclarationRector
Remove the nullable type params, add `@param` tags instead
@ -8441,6 +8529,21 @@ Move `@inject` properties to constructor, if there already is one
<br>
### SubstrMinusToStringEndsWithRector
Change `substr` function with minus to `Strings::endsWith()`
- class: `Rector\NetteCodeQuality\Rector\Identical\SubstrMinusToStringEndsWithRector`
```diff
-substr($var, -4) !== 'Test';
-substr($var, -4) === 'Test';
+! \Nette\Utils\Strings::endsWith($var, 'Test');
+\Nette\Utils\Strings::endsWith($var, 'Test');
```
<br>
## NetteKdyby
### ChangeNetteEventNamesInGetSubscribedEventsRector
@ -15332,100 +15435,6 @@ Make sure there is `public(),` `autowire(),` `autoconfigure()` calls on `default
<br>
### ChangeServiceArgumentsToMethodCallRector
Change `$service->arg(...)` to `$service->call(...)`
:wrench: **configure it!**
- class: `Rector\SymfonyPhpConfig\Rector\MethodCall\ChangeServiceArgumentsToMethodCallRector`
```php
use Rector\SymfonyPhpConfig\Rector\MethodCall\ChangeServiceArgumentsToMethodCallRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(ChangeServiceArgumentsToMethodCallRector::class)
->call('configure', [[
ChangeServiceArgumentsToMethodCallRector::CLASS_TYPE_TO_METHOD_NAME => [
'SomeClass' => 'configure',
],
]]);
};
```
```diff
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(SomeClass::class)
- ->arg('$key', 'value');
+ ->call('configure', [[
+ '$key' => 'value
+ ]]);
}
```
<br>
### ReplaceArrayWithObjectRector
Replace complex array configuration in configs with value object
:wrench: **configure it!**
- class: `Rector\SymfonyPhpConfig\Rector\ArrayItem\ReplaceArrayWithObjectRector`
```php
use Rector\Renaming\Rector\MethodCall\RenameMethodRector;
use Rector\Renaming\ValueObject\MethodCallRename;
use Rector\SymfonyPhpConfig\Rector\ArrayItem\ReplaceArrayWithObjectRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(ReplaceArrayWithObjectRector::class)
->call('configure', [[
ReplaceArrayWithObjectRector::CONSTANT_NAMES_TO_VALUE_OBJECTS => [
RenameMethodRector::OLD_TO_NEW_METHODS_BY_CLASS => MethodCallRename::class,
],
]]);
};
```
```diff
use Rector\Renaming\Rector\MethodCall\RenameMethodRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(RenameMethodRector::class)
->call('configure', [[
- RenameMethodRector::OLD_TO_NEW_METHODS_BY_CLASS => [
- 'Illuminate\Auth\Access\Gate' => [
- 'access' => 'inspect',
- ]
- ]]
- ]);
+ RenameMethodRector::OLD_TO_NEW_METHODS_BY_CLASS => \Symplify\SymfonyPhpConfig\ValueObjectInliner::inline([
+ new \Rector\Renaming\ValueObject\MethodCallRename('Illuminate\Auth\Access\Gate', 'access', 'inspect'),
+ ])
+ ]]);
}
```
<br>
## Transform
### ArgumentFuncCallToMethodCallRector
@ -15674,7 +15683,9 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(NewToStaticCallRector::class)
->call('configure', [[
NewToStaticCallRector::TYPE_TO_STATIC_CALLS => ValueObjectInliner::inline([new NewToStaticCall('Cookie', 'Cookie', 'create')]),
NewToStaticCallRector::TYPE_TO_STATIC_CALLS => ValueObjectInliner::inline([
new NewToStaticCall('Cookie', 'Cookie', 'create'),
]),
]]);
};
```

View File

@ -9,9 +9,14 @@ use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\IterableType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\UnionType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\DowngradePhp71\Contract\Rector\DowngradeParamDeclarationRectorInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Traversable;
abstract class AbstractDowngradeParamDeclarationRector extends AbstractDowngradeRector implements DowngradeParamDeclarationRectorInterface
{
@ -58,6 +63,11 @@ abstract class AbstractDowngradeParamDeclarationRector extends AbstractDowngrade
if ($param->type !== null) {
$type = $this->staticTypeMapper->mapPhpParserNodePHPStanType($param->type);
if ($type instanceof IterableType) {
$type = new UnionType([$type, new IntersectionType([new ObjectType(Traversable::class)])]);
}
$paramName = $this->getName($param->var) ?? '';
$phpDocInfo->changeParamType($type, $param, $paramName);
}

View File

@ -5,11 +5,17 @@ declare(strict_types=1);
namespace Rector\DowngradePhp71\Rector\FunctionLike;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\IterableType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\UnionType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\DowngradePhp71\Contract\Rector\DowngradeReturnDeclarationRectorInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Traversable;
abstract class AbstractDowngradeReturnDeclarationRector extends AbstractDowngradeRector implements DowngradeReturnDeclarationRectorInterface
{
@ -31,20 +37,34 @@ abstract class AbstractDowngradeReturnDeclarationRector extends AbstractDowngrad
}
if ($this->addDocBlock) {
/** @var PhpDocInfo|null $phpDocInfo */
$phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
if ($phpDocInfo === null) {
$phpDocInfo = $this->phpDocInfoFactory->createEmpty($node);
}
if ($node->returnType !== null) {
$type = $this->staticTypeMapper->mapPhpParserNodePHPStanType($node->returnType);
$phpDocInfo->changeReturnType($type);
}
$this->addDocBlockReturn($node);
}
$node->returnType = null;
return $node;
}
/**
* @param ClassMethod|Function_ $functionLike
*/
private function addDocBlockReturn(FunctionLike $functionLike): void
{
/** @var PhpDocInfo|null $phpDocInfo */
$phpDocInfo = $functionLike->getAttribute(AttributeKey::PHP_DOC_INFO);
if ($phpDocInfo === null) {
$phpDocInfo = $this->phpDocInfoFactory->createEmpty($functionLike);
}
if ($functionLike->returnType === null) {
return;
}
$type = $this->staticTypeMapper->mapPhpParserNodePHPStanType($functionLike->returnType);
if ($type instanceof IterableType) {
$type = new UnionType([$type, new IntersectionType([new ObjectType(Traversable::class)])]);
}
$phpDocInfo->changeReturnType($type);
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Rector\DowngradePhp71\Rector\FunctionLike;
use PhpParser\Node\Identifier;
use PhpParser\Node\Param;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\DowngradePhp71\Tests\Rector\FunctionLike\DowngradeIterablePseudoTypeParamDeclarationRector\DowngradeIterablePseudoTypeParamDeclarationRectorTest
*/
final class DowngradeIterablePseudoTypeParamDeclarationRector extends AbstractDowngradeParamDeclarationRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Remove the iterable pseudo type params, add @param tags instead',
[
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
<?php
class SomeClass
{
public function run(iterable $iterator)
{
// do something
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
<?php
class SomeClass
{
/**
* @param mixed[]|\Traversable $iterator
*/
public function run($iterator)
{
// do something
}
}
CODE_SAMPLE
,
[
self::ADD_DOC_BLOCK => true,
]
),
]
);
}
public function shouldRemoveParamDeclaration(Param $param): bool
{
if ($param->type === null) {
return false;
}
return $param->type instanceof Identifier && $param->type->toString() === 'iterable';
}
}

View File

@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Rector\DowngradePhp71\Rector\FunctionLike;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\DowngradePhp71\Tests\Rector\FunctionLike\DowngradeIterablePseudoTypeReturnDeclarationRector\DowngradeIterablePseudoTypeReturnDeclarationRectorTest
*/
final class DowngradeIterablePseudoTypeReturnDeclarationRector extends AbstractDowngradeReturnDeclarationRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Remove returning iterable pseud type, add a @return tag instead',
[
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
<?php
class SomeClass
{
public function run(): iterable
{
// do something
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
<?php
class SomeClass
{
/**
* @return mixed[]|\Traversable
*/
public function run()
{
// do something
}
}
CODE_SAMPLE
,
[
self::ADD_DOC_BLOCK => true,
]
),
]
);
}
/**
* @param ClassMethod|Function_ $functionLike
*/
public function shouldRemoveReturnDeclaration(FunctionLike $functionLike): bool
{
$functionLikeReturnType = $functionLike->returnType;
if ($functionLikeReturnType === null) {
return false;
}
return $this->isName($functionLikeReturnType, 'iterable');
}
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Rector\DowngradePhp71\Tests\Rector\FunctionLike\DowngradeIterablePseudoTypeParamDeclarationRector;
use Iterator;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\DowngradePhp71\Rector\FunctionLike\DowngradeIterablePseudoTypeParamDeclarationRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class DowngradeIterablePseudoTypeParamDeclarationRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return DowngradeIterablePseudoTypeParamDeclarationRector::class;
}
protected function getPhpVersion(): int
{
return PhpVersionFeature::ITERABLE_TYPE - 1;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Rector\DowngradePhp71\Tests\Rector\FunctionLike\DowngradeIterablePseudoTypeParamDeclarationRector\Fixture;
class SomeClass
{
public function run(iterable $iterator)
{
// do something
}
}
?>
-----
<?php
namespace Rector\DowngradePhp71\Tests\Rector\FunctionLike\DowngradeIterablePseudoTypeParamDeclarationRector\Fixture;
class SomeClass
{
/**
* @param mixed[]|\Traversable $iterator
*/
public function run($iterator)
{
// do something
}
}
?>

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Rector\DowngradePhp71\Tests\Rector\FunctionLike\DowngradeIterablePseudoTypeReturnDeclarationRector;
use Iterator;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\DowngradePhp71\Rector\FunctionLike\DowngradeIterablePseudoTypeReturnDeclarationRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class DowngradeIterablePseudoTypeReturnDeclarationRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return DowngradeIterablePseudoTypeReturnDeclarationRector::class;
}
protected function getPhpVersion(): int
{
return PhpVersionFeature::ITERABLE_TYPE - 1;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Rector\DowngradePhp71\Tests\Rector\FunctionLike\DowngradeIterablePseudoTypeReturnDeclarationRector\Fixture;
class SomeClass
{
public function run(): iterable
{
// do something
}
}
?>
-----
<?php
namespace Rector\DowngradePhp71\Tests\Rector\FunctionLike\DowngradeIterablePseudoTypeReturnDeclarationRector\Fixture;
class SomeClass
{
/**
* @return mixed[]|\Traversable
*/
public function run()
{
// do something
}
}
?>