[PHP 8.0] Add OptionalParametersAfterRequiredRector (#5277)

This commit is contained in:
Tomas Votruba 2021-01-22 01:17:43 +01:00 committed by GitHub
parent f7fc1ea144
commit e90a430bcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 233 additions and 12 deletions

View File

@ -11,6 +11,7 @@ use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
use Rector\Php80\Rector\Class_\StringableForToStringRector;
use Rector\Php80\Rector\ClassMethod\FinalPrivateToPrivateVisibilityRector;
use Rector\Php80\Rector\ClassMethod\OptionalParametersAfterRequiredRector;
use Rector\Php80\Rector\ClassMethod\SetStateToStaticRector;
use Rector\Php80\Rector\FuncCall\ClassOnObjectRector;
use Rector\Php80\Rector\FuncCall\TokenGetAllToObjectRector;
@ -38,6 +39,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(RemoveUnusedVariableInCatchRector::class);
$services->set(ClassPropertyAssignToConstructorPromotionRector::class);
$services->set(ChangeSwitchToMatchRector::class);
// nette\utils and Strings::replace()
$services->set(ArgumentAdderRector::class)->call('configure', [[
ArgumentAddingScope::ADDED_ARGUMENTS => ValueObjectInliner::inline([
@ -76,4 +78,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
'pg_setclientencoding' => 'pg_set_client_encoding',
],
]]);
$services->set(OptionalParametersAfterRequiredRector::class);
};

View File

@ -9,6 +9,7 @@ use PhpParser\Node\Name;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\CallableType;
use PHPStan\Type\ClosureType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareCallableTypeNode;
@ -46,7 +47,7 @@ final class CallableTypeMapper implements TypeMapperInterface
}
/**
* @param CallableType $type
* @param CallableType|ClosureType $type
*/
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
{

View File

@ -6,7 +6,6 @@ namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
use Closure;
use PhpParser\Node;
use PhpParser\Node\Name;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ClosureType;
@ -23,6 +22,16 @@ final class ClosureTypeMapper implements TypeMapperInterface, PHPStanStaticTypeM
*/
private $phpStanStaticTypeMapper;
/**
* @var CallableTypeMapper
*/
private $callableTypeMapper;
public function __construct(CallableTypeMapper $callableTypeMapper)
{
$this->callableTypeMapper = $callableTypeMapper;
}
public function getNodeClass(): string
{
return ClosureType::class;
@ -45,11 +54,7 @@ final class ClosureTypeMapper implements TypeMapperInterface, PHPStanStaticTypeM
*/
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
{
if ($kind === 'property') {
return null;
}
return new Name('callable');
return $this->callableTypeMapper->mapToPhpParserNode($type, $kind);
}
public function mapToDocString(Type $type, ?Type $parentType = null): string

View File

@ -43,9 +43,7 @@ final class TemplateFinder
$filePaths[] = __DIR__ . '/../../templates/rules/__package__/tests/Rector/__Category__/__Name__/Source/extra_file.php.inc';
}
/** @var string[] $filePaths */
$filePaths = $this->addRuleAndTestCase($rectorRecipe, $filePaths);
$filePaths[] = $this->resolveFixtureFilePath();
$this->ensureFilePathsExists($filePaths);
@ -87,7 +85,7 @@ final class TemplateFinder
private function resolveFixtureFilePath(): string
{
return __DIR__ . '/../../templates/rules/__package__/tests/Rector/__Category__/__Name__/Fixture/fixture.php.inc';
return __DIR__ . '/../../templates/rules/__package__/tests/Rector/__Category__/__Name__/Fixture/some_class.php.inc';
}
/**

View File

@ -19,7 +19,7 @@ final class __Name__Test extends AbstractRectorTestCase
public function provideData(): \Iterator
{
yield [new \Symplify\SmartFileSystem\SmartFileInfo(__DIR__ . '/Fixture/fixture.php.inc'), '__ExtraFileName__', __DIR__ . '/Source/extra_file.php'];
yield [new \Symplify\SmartFileSystem\SmartFileInfo(__DIR__ . '/Fixture/some_class.php.inc'), '__ExtraFileName__', __DIR__ . '/Source/extra_file.php'];
}
/**

View File

@ -19,7 +19,7 @@ final class __Name__Test extends AbstractRectorTestCase
public function provideData(): \Iterator
{
yield [new \Symplify\SmartFileSystem\SmartFileInfo(__DIR__ . '/Fixture/fixture.php.inc'), '__ExtraFileName__', __DIR__ . '/Source/extra_file.php'];
yield [new \Symplify\SmartFileSystem\SmartFileInfo(__DIR__ . '/Fixture/some_class.php.inc'), '__ExtraFileName__', __DIR__ . '/Source/extra_file.php'];
}
protected function getRectorClass(): string

View File

@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Rector\Php80\Rector\ClassMethod;
use PhpParser\Node;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see https://php.watch/versions/8.0#deprecate-required-param-after-optional
*
* @see \Rector\Php80\Tests\Rector\ClassMethod\OptionalParametersAfterRequiredRector\OptionalParametersAfterRequiredRectorTest
*/
final class OptionalParametersAfterRequiredRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Move required parameters after optional ones', [
new CodeSample(
<<<'CODE_SAMPLE'
class SomeObject
{
public function run($optional = 1, $required)
{
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeObject
{
public function run($required, $optional = 1)
{
}
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}
/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?Node
{
if ($node->params === []) {
return null;
}
$requireParams = $this->resolveRequiredParams($node);
$optionalParams = $this->resolveOptionalParams($node);
$expectedOrderParams = array_merge($requireParams, $optionalParams);
if ($node->params === $expectedOrderParams) {
return null;
}
$node->params = $expectedOrderParams;
return $node;
}
/**
* @return array<int, Param>
*/
private function resolveOptionalParams(ClassMethod $classMethod): array
{
$paramsByPosition = [];
foreach ($classMethod->params as $position => $param) {
if ($param->default === null) {
continue;
}
$paramsByPosition[$position] = $param;
}
return $paramsByPosition;
}
/**
* @return Param[]
*/
private function resolveRequiredParams(ClassMethod $classMethod): array
{
$paramsByPosition = [];
foreach ($classMethod->params as $param) {
if ($param->default !== null) {
continue;
}
$paramsByPosition[] = $param;
}
return $paramsByPosition;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Rector\Php80\Tests\Rector\ClassMethod\OptionalParametersAfterRequiredRector\Fixture;
class Mashup
{
public function run($optional = 1, $required, $anotherOptional = false, $yetRequired)
{
}
}
?>
-----
<?php
namespace Rector\Php80\Tests\Rector\ClassMethod\OptionalParametersAfterRequiredRector\Fixture;
class Mashup
{
public function run($required, $yetRequired, $optional = 1, $anotherOptional = false)
{
}
}
?>

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Rector\Php80\Tests\Rector\ClassMethod\OptionalParametersAfterRequiredRector\Fixture;
final class SkipCorrectOrder
{
public function run($required, $optional = 1)
{
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Rector\Php80\Tests\Rector\ClassMethod\OptionalParametersAfterRequiredRector\Fixture;
final class SkipNoParams
{
public function run()
{
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Rector\Php80\Tests\Rector\ClassMethod\OptionalParametersAfterRequiredRector\Fixture;
class SomeObject
{
public function run($optional = 1, $required)
{
}
}
?>
-----
<?php
namespace Rector\Php80\Tests\Rector\ClassMethod\OptionalParametersAfterRequiredRector\Fixture;
class SomeObject
{
public function run($required, $optional = 1)
{
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\Php80\Tests\Rector\ClassMethod\OptionalParametersAfterRequiredRector;
use Iterator;
use Rector\Php80\Rector\ClassMethod\OptionalParametersAfterRequiredRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class OptionalParametersAfterRequiredRectorTest 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 OptionalParametersAfterRequiredRector::class;
}
}