Updated Rector to commit 3b14af26db9eef322f21a9c783e6f9109e087c67

3b14af26db Type hint array reduce closure (#6725)
This commit is contained in:
Tomas Votruba 2025-02-09 16:30:04 +00:00
parent d79d9689ab
commit a29172639c
10 changed files with 246 additions and 8 deletions

View File

@ -0,0 +1,152 @@
<?php
declare (strict_types=1);
namespace Rector\TypeDeclaration\Rector\FunctionLike;
use PhpParser\Node;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Param;
use PHPStan\Reflection\Native\NativeFunctionReflection;
use PHPStan\Type\ClosureType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use PHPStan\Type\UnionTypeHelper;
use Rector\NodeTypeResolver\TypeComparator\TypeComparator;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\Rector\AbstractRector;
use Rector\Reflection\ReflectionResolver;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayReduceRector\AddClosureParamTypeForArrayReduceRectorTest
*/
final class AddClosureParamTypeForArrayReduceRector extends AbstractRector
{
/**
* @readonly
*/
private TypeComparator $typeComparator;
/**
* @readonly
*/
private StaticTypeMapper $staticTypeMapper;
/**
* @readonly
*/
private ReflectionResolver $reflectionResolver;
public function __construct(TypeComparator $typeComparator, StaticTypeMapper $staticTypeMapper, ReflectionResolver $reflectionResolver)
{
$this->typeComparator = $typeComparator;
$this->staticTypeMapper = $staticTypeMapper;
$this->reflectionResolver = $reflectionResolver;
}
public function getRuleDefinition() : RuleDefinition
{
return new RuleDefinition('Applies type hints to array_map closures', [new CodeSample(<<<'CODE_SAMPLE'
array_reduce($strings, function ($carry, $value, $key): string {
return $carry . $value;
}, $initialString);
CODE_SAMPLE
, <<<'CODE_SAMPLE'
array_reduce($strings, function (string $carry, string $value): string {
return $carry . $value;
}, $initialString);
CODE_SAMPLE
)]);
}
public function getNodeTypes() : array
{
return [FuncCall::class];
}
/**
* @param FuncCall $node
*/
public function refactor(Node $node) : ?Node
{
if ($node->isFirstClassCallable()) {
return null;
}
if (!$this->isName($node, 'array_reduce')) {
return null;
}
$funcReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node);
if (!$funcReflection instanceof NativeFunctionReflection) {
return null;
}
$args = $node->getArgs();
if (!isset($args[1]) || !$args[1]->value instanceof Closure) {
return null;
}
$closureType = $this->getType($args[1]->value);
if (!$closureType instanceof ClosureType) {
return null;
}
$carryType = $closureType->getReturnType();
if (isset($args[2])) {
$carryType = $this->combineTypes([$this->getType($args[2]->value), $carryType]);
}
$type = $this->getType($args[0]->value);
$valueType = $type->getIterableValueType();
if ($this->updateClosureWithTypes($args[1]->value, $valueType, $carryType)) {
return $node;
}
return null;
}
private function updateClosureWithTypes(Closure $closure, ?Type $valueType, ?Type $carryType) : bool
{
$changes = \false;
$carryParam = $closure->params[0] ?? null;
$valueParam = $closure->params[1] ?? null;
if ($valueParam instanceof Param && $valueType instanceof Type && $this->refactorParameter($valueParam, $valueType)) {
$changes = \true;
}
if ($carryParam instanceof Param && $carryType instanceof Type && $this->refactorParameter($carryParam, $carryType)) {
return \true;
}
return $changes;
}
private function refactorParameter(Param $param, Type $type) : bool
{
if ($type instanceof MixedType) {
return \false;
}
// already set → no change
if ($param->type instanceof Node) {
$currentParamType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($param->type);
if ($this->typeComparator->areTypesEqual($currentParamType, $type)) {
return \false;
}
}
$paramTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PARAM);
if (!$paramTypeNode instanceof Node) {
return \false;
}
$param->type = $paramTypeNode;
return \true;
}
/**
* @param Type[] $types
*/
private function combineTypes(array $types) : ?Type
{
if ($types === []) {
return null;
}
$types = \array_reduce($types, function (array $types, Type $type) : array {
foreach ($types as $previousType) {
if ($this->typeComparator->areTypesEqual($type, $previousType)) {
return $types;
}
}
$types[] = $type;
return $types;
}, []);
if (\count($types) === 1) {
return $types[0];
}
return new UnionType(UnionTypeHelper::sortTypes($types));
}
}

View File

@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = '3569d162bf6cb3730c091db0898798e59c93b4f4';
public const PACKAGE_VERSION = '3b14af26db9eef322f21a9c783e6f9109e087c67';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2025-02-08 12:36:53';
public const RELEASE_DATE = '2025-02-09 17:27:26';
/**
* @var int
*/

View File

@ -48,6 +48,8 @@ use Rector\TypeDeclaration\Rector\Closure\AddClosureVoidReturnTypeWhereNoReturnR
use Rector\TypeDeclaration\Rector\Closure\ClosureReturnTypeRector;
use Rector\TypeDeclaration\Rector\Empty_\EmptyOnNullableObjectToInstanceOfRector;
use Rector\TypeDeclaration\Rector\Function_\AddFunctionVoidReturnTypeWhereNoReturnRector;
use Rector\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector;
use Rector\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayReduceRector;
use Rector\TypeDeclaration\Rector\FunctionLike\AddParamTypeSplFixedArrayRector;
use Rector\TypeDeclaration\Rector\FunctionLike\AddReturnTypeDeclarationFromYieldsRector;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector;
@ -106,6 +108,8 @@ final class TypeDeclarationLevel
// closures
AddClosureNeverReturnTypeRector::class,
ClosureReturnTypeRector::class,
AddClosureParamTypeForArrayReduceRector::class,
AddClosureParamTypeForArrayMapRector::class,
// more risky rules
ReturnTypeFromStrictParamRector::class,
AddParamTypeFromPropertyTypeRector::class,

View File

@ -1790,6 +1790,7 @@ return array(
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertCompareOnCountableWithMethodToAssertCountRector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertCompareOnCountableWithMethodToAssertCountRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertCompareToSpecificMethodRector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertCompareToSpecificMethodRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertComparisonToSpecificMethodRector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertComparisonToSpecificMethodRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertCountWithZeroToAssertEmptyRector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertCountWithZeroToAssertEmptyRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertEmptyNullableObjectToAssertInstanceofRector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertEmptyNullableObjectToAssertInstanceofRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertEqualsOrAssertSameFloatParameterToSpecificMethodsTypeRector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertEqualsOrAssertSameFloatParameterToSpecificMethodsTypeRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertEqualsToSameRector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertEqualsToSameRector.php',
@ -2547,6 +2548,7 @@ return array(
'Rector\\TypeDeclaration\\Rector\\Empty_\\EmptyOnNullableObjectToInstanceOfRector' => $baseDir . '/rules/TypeDeclaration/Rector/Empty_/EmptyOnNullableObjectToInstanceOfRector.php',
'Rector\\TypeDeclaration\\Rector\\Expression\\InlineVarDocTagToAssertRector' => $baseDir . '/rules/TypeDeclaration/Rector/Expression/InlineVarDocTagToAssertRector.php',
'Rector\\TypeDeclaration\\Rector\\FunctionLike\\AddClosureParamTypeForArrayMapRector' => $baseDir . '/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector.php',
'Rector\\TypeDeclaration\\Rector\\FunctionLike\\AddClosureParamTypeForArrayReduceRector' => $baseDir . '/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayReduceRector.php',
'Rector\\TypeDeclaration\\Rector\\FunctionLike\\AddClosureParamTypeFromArgRector' => $baseDir . '/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeFromArgRector.php',
'Rector\\TypeDeclaration\\Rector\\FunctionLike\\AddClosureParamTypeFromIterableMethodCallRector' => $baseDir . '/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeFromIterableMethodCallRector.php',
'Rector\\TypeDeclaration\\Rector\\FunctionLike\\AddClosureParamTypeFromObjectRector' => $baseDir . '/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeFromObjectRector.php',

View File

@ -2009,6 +2009,7 @@ class ComposerStaticInita9ca9624b9ec3b7183135fb9d7d95d23
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertCompareOnCountableWithMethodToAssertCountRector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertCompareOnCountableWithMethodToAssertCountRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertCompareToSpecificMethodRector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertCompareToSpecificMethodRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertComparisonToSpecificMethodRector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertComparisonToSpecificMethodRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertCountWithZeroToAssertEmptyRector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertCountWithZeroToAssertEmptyRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertEmptyNullableObjectToAssertInstanceofRector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertEmptyNullableObjectToAssertInstanceofRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertEqualsOrAssertSameFloatParameterToSpecificMethodsTypeRector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertEqualsOrAssertSameFloatParameterToSpecificMethodsTypeRector.php',
'Rector\\PHPUnit\\CodeQuality\\Rector\\MethodCall\\AssertEqualsToSameRector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/Rector/MethodCall/AssertEqualsToSameRector.php',
@ -2766,6 +2767,7 @@ class ComposerStaticInita9ca9624b9ec3b7183135fb9d7d95d23
'Rector\\TypeDeclaration\\Rector\\Empty_\\EmptyOnNullableObjectToInstanceOfRector' => __DIR__ . '/../..' . '/rules/TypeDeclaration/Rector/Empty_/EmptyOnNullableObjectToInstanceOfRector.php',
'Rector\\TypeDeclaration\\Rector\\Expression\\InlineVarDocTagToAssertRector' => __DIR__ . '/../..' . '/rules/TypeDeclaration/Rector/Expression/InlineVarDocTagToAssertRector.php',
'Rector\\TypeDeclaration\\Rector\\FunctionLike\\AddClosureParamTypeForArrayMapRector' => __DIR__ . '/../..' . '/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector.php',
'Rector\\TypeDeclaration\\Rector\\FunctionLike\\AddClosureParamTypeForArrayReduceRector' => __DIR__ . '/../..' . '/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayReduceRector.php',
'Rector\\TypeDeclaration\\Rector\\FunctionLike\\AddClosureParamTypeFromArgRector' => __DIR__ . '/../..' . '/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeFromArgRector.php',
'Rector\\TypeDeclaration\\Rector\\FunctionLike\\AddClosureParamTypeFromIterableMethodCallRector' => __DIR__ . '/../..' . '/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeFromIterableMethodCallRector.php',
'Rector\\TypeDeclaration\\Rector\\FunctionLike\\AddClosureParamTypeFromObjectRector' => __DIR__ . '/../..' . '/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeFromObjectRector.php',

View File

@ -1804,12 +1804,12 @@
"source": {
"type": "git",
"url": "https:\/\/github.com\/rectorphp\/rector-phpunit.git",
"reference": "03e2418e0107b33496c0f7bb99520ac8606a3d7a"
"reference": "45b0f6bfc11edec5aa9cf85314d26a6f48220c60"
},
"dist": {
"type": "zip",
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-phpunit\/zipball\/03e2418e0107b33496c0f7bb99520ac8606a3d7a",
"reference": "03e2418e0107b33496c0f7bb99520ac8606a3d7a",
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-phpunit\/zipball\/45b0f6bfc11edec5aa9cf85314d26a6f48220c60",
"reference": "45b0f6bfc11edec5aa9cf85314d26a6f48220c60",
"shasum": ""
},
"require": {
@ -1830,7 +1830,7 @@
"tomasvotruba\/class-leak": "^1.2",
"tracy\/tracy": "^2.10"
},
"time": "2025-02-02T01:09:12+00:00",
"time": "2025-02-09T14:37:12+00:00",
"default-branch": true,
"type": "rector-extension",
"extra": {

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ namespace Rector\RectorInstaller;
*/
final class GeneratedConfig
{
public const EXTENSIONS = array('rector/rector-doctrine' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-doctrine', 'relative_install_path' => '../../rector-doctrine', 'extra' => NULL, 'version' => 'dev-main ceda7a4'), 'rector/rector-downgrade-php' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-downgrade-php', 'relative_install_path' => '../../rector-downgrade-php', 'extra' => NULL, 'version' => 'dev-main 6dba109'), 'rector/rector-phpunit' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-phpunit', 'relative_install_path' => '../../rector-phpunit', 'extra' => NULL, 'version' => 'dev-main 03e2418'), 'rector/rector-symfony' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-symfony', 'relative_install_path' => '../../rector-symfony', 'extra' => NULL, 'version' => 'dev-main 4661c01'));
public const EXTENSIONS = array('rector/rector-doctrine' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-doctrine', 'relative_install_path' => '../../rector-doctrine', 'extra' => NULL, 'version' => 'dev-main ceda7a4'), 'rector/rector-downgrade-php' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-downgrade-php', 'relative_install_path' => '../../rector-downgrade-php', 'extra' => NULL, 'version' => 'dev-main 6dba109'), 'rector/rector-phpunit' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-phpunit', 'relative_install_path' => '../../rector-phpunit', 'extra' => NULL, 'version' => 'dev-main 45b0f6b'), 'rector/rector-symfony' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-symfony', 'relative_install_path' => '../../rector-symfony', 'extra' => NULL, 'version' => 'dev-main 4661c01'));
private function __construct()
{
}

View File

@ -16,6 +16,7 @@ use Rector\PHPUnit\CodeQuality\Rector\ClassMethod\ReplaceTestAnnotationWithPrefi
use Rector\PHPUnit\CodeQuality\Rector\Foreach_\SimplifyForeachInstanceOfRector;
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertCompareOnCountableWithMethodToAssertCountRector;
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertComparisonToSpecificMethodRector;
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertCountWithZeroToAssertEmptyRector;
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertEmptyNullableObjectToAssertInstanceofRector;
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertEqualsOrAssertSameFloatParameterToSpecificMethodsTypeRector;
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertEqualsToSameRector;
@ -68,6 +69,7 @@ return static function (RectorConfig $rectorConfig) : void {
UseSpecificWillMethodRector::class,
UseSpecificWithMethodRector::class,
AssertEmptyNullableObjectToAssertInstanceofRector::class,
AssertCountWithZeroToAssertEmptyRector::class,
/**
* Improve direct testing of your code, without mock creep. Make it simple, clear and easy to maintain:
*

View File

@ -0,0 +1,76 @@
<?php
declare (strict_types=1);
namespace Rector\PHPUnit\CodeQuality\Rector\MethodCall;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name;
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\PHPUnit\Tests\CodeQuality\Rector\MethodCall\AssertCountWithZeroToAssertEmptyRector\AssertCountWithZeroToAssertEmptyRectorTest
*/
final class AssertCountWithZeroToAssertEmptyRector extends AbstractRector
{
/**
* @readonly
*/
private TestsNodeAnalyzer $testsNodeAnalyzer;
public function __construct(TestsNodeAnalyzer $testsNodeAnalyzer)
{
$this->testsNodeAnalyzer = $testsNodeAnalyzer;
}
public function getRuleDefinition() : RuleDefinition
{
return new RuleDefinition('Change $this->assertCount(0, ...) to $this->assertEmpty(...)', [new CodeSample(<<<'CODE_SAMPLE'
$this->assertCount(0, $countable);
$this->assertNotCount(0, $countable);
CODE_SAMPLE
, <<<'CODE_SAMPLE'
$this->assertEmpty($countable);
$this->assertNotEmpty($countable);
CODE_SAMPLE
)]);
}
/**
* @return array<class-string<MethodCall|StaticCall>>
*/
public function getNodeTypes() : array
{
return [MethodCall::class, StaticCall::class];
}
/**
* @param MethodCall|StaticCall $node
* @return \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall|null
*/
public function refactor(Node $node)
{
if (!$this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, ['assertCount', 'assertNotCount'])) {
return null;
}
if ($node->isFirstClassCallable()) {
return null;
}
if (\count($node->getArgs()) < 2) {
return null;
}
$type = $this->getType($node->getArgs()[0]->value);
$value = $type->getConstantScalarValues()[0] ?? null;
if ($value === 0) {
$args = $node->getArgs();
if ($this->isName($node->name, 'assertNotCount')) {
$node->name = new Name('assertNotEmpty');
} else {
$node->name = new Name('assertEmpty');
}
\array_shift($args);
$node->args = $args;
return $node;
}
return null;
}
}