mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-07 03:40:50 +00:00
[TypeDeclaration] Add ArrayShapeFromConstantArrayReturnRector (#1908)
This commit is contained in:
parent
55a13c0020
commit
8057e33f91
|
@ -2,6 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Nette\Utils\FileSystem;
|
||||
use Nette\Utils\Strings;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
@ -45,14 +46,14 @@ final class CleanPhpstanCommand extends Command
|
|||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$originalContent = (string) file_get_contents(self::FILE);
|
||||
$originalContent = FileSystem::read(self::FILE);
|
||||
$newContent = str_replace(
|
||||
'reportUnmatchedIgnoredErrors: false',
|
||||
'reportUnmatchedIgnoredErrors: true',
|
||||
$originalContent
|
||||
);
|
||||
|
||||
file_put_contents(self::FILE, $newContent);
|
||||
FileSystem::write(self::FILE, $newContent);
|
||||
|
||||
$process = new Process(['composer', 'phpstan']);
|
||||
$process->run();
|
||||
|
@ -63,7 +64,7 @@ final class CleanPhpstanCommand extends Command
|
|||
$output->writeln($result);
|
||||
|
||||
if (! $isFailure) {
|
||||
file_put_contents(self::FILE, $originalContent);
|
||||
FileSystem::write(self::FILE, $originalContent);
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -74,8 +75,8 @@ final class CleanPhpstanCommand extends Command
|
|||
|
||||
$matchAll = Strings::matchAll($result, self::ONELINE_IGNORED_PATTERN_REGEX);
|
||||
|
||||
foreach ($matchAll as $match) {
|
||||
$newContent = str_replace(' - \'' . $match['content'] . '\'', '', $newContent);
|
||||
foreach ($matchAll as $singleMatchAll) {
|
||||
$newContent = str_replace(" - '" . $singleMatchAll['content'] . "'", '', $newContent);
|
||||
}
|
||||
|
||||
$newContent = str_replace(
|
||||
|
@ -83,7 +84,7 @@ final class CleanPhpstanCommand extends Command
|
|||
'reportUnmatchedIgnoredErrors: false',
|
||||
$newContent
|
||||
);
|
||||
file_put_contents(self::FILE, $newContent);
|
||||
FileSystem::write(self::FILE, $newContent);
|
||||
|
||||
$process2 = new Process(['composer', 'phpstan']);
|
||||
$process2->run();
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromReturnNewRector;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector;
|
||||
|
@ -22,7 +23,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
|||
$services->set(ReturnTypeFromStrictTypedCallRector::class);
|
||||
$services->set(AddVoidReturnTypeWhereNoReturnRector::class);
|
||||
$services->set(ReturnTypeFromReturnNewRector::class);
|
||||
|
||||
$services->set(TypedPropertyFromStrictGetterMethodReturnTypeRector::class);
|
||||
$services->set(AddMethodCallBasedStrictParamTypeRector::class);
|
||||
$services->set(ArrayShapeFromConstantArrayReturnRector::class);
|
||||
};
|
||||
|
|
|
@ -95,7 +95,7 @@ final class IndentTest extends TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
* @return array{int-one: int, int-greater-than-one: int}
|
||||
*/
|
||||
private static function sizes(): array
|
||||
{
|
||||
|
|
|
@ -28,6 +28,9 @@ use Rector\PHPStanStaticTypeMapper\Utils\TypeUnwrapper;
|
|||
use Rector\StaticTypeMapper\StaticTypeMapper;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
/**
|
||||
* @see https://wiki.php.net/rfc/internal_method_return_types#proposal
|
||||
*/
|
||||
final class PhpDocFromTypeDeclarationDecorator
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -67,7 +67,7 @@ final class RectorWithLineChange implements SerializableInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
* @return array{rector_class: class-string<RectorInterface>, line: int}
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
|||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\BooleanType;
|
||||
use PHPStan\Type\Constant\ConstantArrayType;
|
||||
use PHPStan\Type\Constant\ConstantBooleanType;
|
||||
use PHPStan\Type\ConstantScalarType;
|
||||
use PHPStan\Type\Generic\GenericClassStringType;
|
||||
|
@ -213,6 +214,14 @@ final class TypeComparator
|
|||
return false;
|
||||
}
|
||||
|
||||
if ($firstType instanceof ConstantArrayType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($secondType instanceof ConstantArrayType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$firstKeyType = $this->normalizeSingleUnionType($firstType->getKeyType());
|
||||
$secondKeyType = $this->normalizeSingleUnionType($secondType->getKeyType());
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
|
||||
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\Constant\ConstantArrayType;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\NeverType;
|
||||
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
|
||||
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
|
||||
|
||||
final class ArrayShapeTypeMapper
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PHPStanStaticTypeMapper $phpStanStaticTypeMapper
|
||||
) {
|
||||
}
|
||||
|
||||
public function mapConstantArrayType(ConstantArrayType $constantArrayType): ArrayShapeNode|ArrayType|null
|
||||
{
|
||||
// empty array
|
||||
if ($constantArrayType->getKeyType() instanceof NeverType) {
|
||||
return new ArrayType(new MixedType(), new MixedType());
|
||||
}
|
||||
|
||||
$arrayShapeItemNodes = [];
|
||||
|
||||
foreach ($constantArrayType->getKeyTypes() as $index => $keyType) {
|
||||
// not real array shape
|
||||
if (! $keyType instanceof ConstantStringType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$keyDocTypeNode = new IdentifierTypeNode($keyType->getValue());
|
||||
$valueType = $constantArrayType->getValueTypes()[$index];
|
||||
|
||||
$valueDocTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode(
|
||||
$valueType,
|
||||
TypeKind::RETURN()
|
||||
);
|
||||
|
||||
$arrayShapeItemNodes[] = new ArrayShapeItemNode(
|
||||
$keyDocTypeNode,
|
||||
$constantArrayType->isOptionalKey($index),
|
||||
$valueDocTypeNode
|
||||
);
|
||||
}
|
||||
|
||||
return new ArrayShapeNode($arrayShapeItemNodes);
|
||||
}
|
||||
}
|
|
@ -53,6 +53,8 @@ final class ArrayTypeMapper implements TypeMapperInterface
|
|||
|
||||
private DetailedTypeAnalyzer $detailedTypeAnalyzer;
|
||||
|
||||
private ArrayShapeTypeMapper $arrayShapeTypeMapper;
|
||||
|
||||
// To avoid circular dependency
|
||||
|
||||
#[Required]
|
||||
|
@ -61,13 +63,15 @@ final class ArrayTypeMapper implements TypeMapperInterface
|
|||
UnionTypeCommonTypeNarrower $unionTypeCommonTypeNarrower,
|
||||
ReflectionProvider $reflectionProvider,
|
||||
GenericClassStringTypeNormalizer $genericClassStringTypeNormalizer,
|
||||
DetailedTypeAnalyzer $detailedTypeAnalyzer
|
||||
DetailedTypeAnalyzer $detailedTypeAnalyzer,
|
||||
ArrayShapeTypeMapper $arrayShapeTypeMapper
|
||||
): void {
|
||||
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
|
||||
$this->unionTypeCommonTypeNarrower = $unionTypeCommonTypeNarrower;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
$this->genericClassStringTypeNormalizer = $genericClassStringTypeNormalizer;
|
||||
$this->detailedTypeAnalyzer = $detailedTypeAnalyzer;
|
||||
$this->arrayShapeTypeMapper = $arrayShapeTypeMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,6 +93,13 @@ final class ArrayTypeMapper implements TypeMapperInterface
|
|||
return $this->createArrayTypeNodeFromUnionType($itemType, $typeKind);
|
||||
}
|
||||
|
||||
if ($type instanceof ConstantArrayType && $typeKind->equals(TypeKind::RETURN())) {
|
||||
$arrayShapeNode = $this->arrayShapeTypeMapper->mapConstantArrayType($type);
|
||||
if ($arrayShapeNode instanceof TypeNode) {
|
||||
return $arrayShapeNode;
|
||||
}
|
||||
}
|
||||
|
||||
if ($itemType instanceof ArrayType && $this->isGenericArrayCandidate($itemType)) {
|
||||
return $this->createGenericArrayType($type, $typeKind, true);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ includes:
|
|||
- vendor/symplify/phpstan-rules/config/services-rules.neon
|
||||
- vendor/symplify/phpstan-rules/config/static-rules.neon
|
||||
- vendor/symplify/phpstan-rules/config/string-to-constant-rules.neon
|
||||
- vendor/symplify/phpstan-rules/config/symfony-rules.neon
|
||||
- vendor/symplify/phpstan-rules/packages/symfony/config/symfony-rules.neon
|
||||
- vendor/symplify/phpstan-rules/config/test-rules.neon
|
||||
|
||||
parameters:
|
||||
|
@ -575,3 +575,10 @@ parameters:
|
|||
-
|
||||
message: '#Make callable type explicit#'
|
||||
path: src/NodeManipulator/BinaryOpManipulator.php
|
||||
|
||||
# resolve later
|
||||
- '#Add explicit array type to assigned "(.*?)" expression#'
|
||||
- '#Complete known array shape to the method @return type#'
|
||||
# @todo remove from phpstan-rules for reoctr
|
||||
- '#Function "array_walk\(\)" cannot be used/left in the code\: #'
|
||||
- '#Instead of "Nette\\Utils\\FileSystem" class/interface use "Symplify\\SmartFileSystem\\SmartFileSystem"#'
|
||||
|
|
|
@ -16,6 +16,7 @@ use Rector\PHPUnit\Set\PHPUnitSetList;
|
|||
use Rector\Privatization\Rector\Class_\FinalizeClassesWithoutChildrenRector;
|
||||
use Rector\Set\ValueObject\LevelSetList;
|
||||
use Rector\Set\ValueObject\SetList;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
|
@ -73,6 +74,10 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
|||
MyCLabsClassToEnumRector::class,
|
||||
SpatieEnumClassToEnumRector::class,
|
||||
|
||||
ArrayShapeFromConstantArrayReturnRector::class => [
|
||||
__DIR__ . '/rules/Transform/Rector/ClassMethod/ReturnTypeWillChangeRector.php',
|
||||
],
|
||||
|
||||
// test paths
|
||||
'*/tests/**/Fixture/*',
|
||||
'*/rules-tests/**/Fixture/*',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Rector\Tests\CodingStyle\Rector\ClassConst\VarConstantCommentRector\Fixture;
|
||||
|
||||
class PairClassStringArray
|
||||
final class PairClassStringArray
|
||||
{
|
||||
private const CLASSES = [
|
||||
\stdClass::class => PairClassStringArray::class,
|
||||
|
@ -15,7 +15,7 @@ class PairClassStringArray
|
|||
|
||||
namespace Rector\Tests\CodingStyle\Rector\ClassConst\VarConstantCommentRector\Fixture;
|
||||
|
||||
class PairClassStringArray
|
||||
final class PairClassStringArray
|
||||
{
|
||||
/**
|
||||
* @var array<string, class-string<\Rector\Tests\CodingStyle\Rector\ClassConst\VarConstantCommentRector\Fixture\PairClassStringArray>>
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddArrayReturnDocTypeR
|
|||
class ChildHasPriority extends ParentClassWithDefinedReturn
|
||||
{
|
||||
/**
|
||||
* @return array<int, array<string, int|float|string>>
|
||||
* @return array<int, array{a: string, b: int, c: float}>
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class ArrayShapeFromConstantArrayReturnRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideData()
|
||||
*/
|
||||
public function test(SmartFileInfo $fileInfo): void
|
||||
{
|
||||
$this->doTestFileInfo($fileInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<SmartFileInfo>
|
||||
*/
|
||||
public function provideData(): Iterator
|
||||
{
|
||||
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
|
||||
}
|
||||
|
||||
public function provideConfigFilePath(): string
|
||||
{
|
||||
return __DIR__ . '/config/configured_rule.php';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;
|
||||
|
||||
final class IncludeConstants
|
||||
{
|
||||
public const NAME = 'name';
|
||||
|
||||
public function run(string $name)
|
||||
{
|
||||
return [self::NAME => $name];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;
|
||||
|
||||
final class IncludeConstants
|
||||
{
|
||||
public const NAME = 'name';
|
||||
|
||||
/**
|
||||
* @return array{name: string}
|
||||
*/
|
||||
public function run(string $name)
|
||||
{
|
||||
return [self::NAME => $name];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;
|
||||
|
||||
use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Source\ExternalConstant;
|
||||
|
||||
final class IncludeExternalConstants
|
||||
{
|
||||
/**
|
||||
* @return array<string, string|int>
|
||||
*/
|
||||
public function run(int $line, string $file): array
|
||||
{
|
||||
return [
|
||||
ExternalConstant::LINE => $line,
|
||||
ExternalConstant::FILE => $file,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;
|
||||
|
||||
use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Source\ExternalConstant;
|
||||
|
||||
final class IncludeExternalConstants
|
||||
{
|
||||
/**
|
||||
* @return array{line: int, file: string}
|
||||
*/
|
||||
public function run(int $line, string $file): array
|
||||
{
|
||||
return [
|
||||
ExternalConstant::LINE => $line,
|
||||
ExternalConstant::FILE => $file,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;
|
||||
|
||||
final class SkipEmptyArray
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;
|
||||
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
|
||||
final class SkipNoStringKeys
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
return [ClassMethod::class];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;
|
||||
|
||||
final class SomeClass
|
||||
{
|
||||
public function run(string $name)
|
||||
{
|
||||
return ['name' => $name];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;
|
||||
|
||||
final class SomeClass
|
||||
{
|
||||
/**
|
||||
* @return array{name: string}
|
||||
*/
|
||||
public function run(string $name)
|
||||
{
|
||||
return ['name' => $name];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Source;
|
||||
|
||||
final class ExternalConstant
|
||||
{
|
||||
public const LINE = 'line';
|
||||
|
||||
public const FILE = 'file';
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$services = $containerConfigurator->services();
|
||||
$services->set(ArrayShapeFromConstantArrayReturnRector::class);
|
||||
};
|
|
@ -20,7 +20,7 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
|||
final class DowngradePhp72JsonConstRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var array<string>
|
||||
* @var string[]
|
||||
*/
|
||||
private const CONSTANTS = ['JSON_INVALID_UTF8_IGNORE', 'JSON_INVALID_UTF8_SUBSTITUTE'];
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ final class EregToPcreTransformer
|
|||
/**
|
||||
* Recursively converts ERE into PCRE, starting at the position $i.
|
||||
*
|
||||
* @return mixed[]
|
||||
* @return float[]|int[]|string[]
|
||||
*/
|
||||
private function _ere2pcre(string $content, int $i): array
|
||||
{
|
||||
|
@ -223,11 +223,14 @@ final class EregToPcreTransformer
|
|||
$i = $ii;
|
||||
}
|
||||
|
||||
// retype
|
||||
$i = (int) $i;
|
||||
|
||||
return $i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
* @return float[]|int[]|string[]
|
||||
*/
|
||||
private function processSquareBracket(string $s, int $i, int $l, string $cls, bool $start): array
|
||||
{
|
||||
|
|
|
@ -119,7 +119,7 @@ CODE_SAMPLE
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array<int, Assign|FuncCall>
|
||||
* @return array<Assign|FuncCall>
|
||||
*/
|
||||
private function createNewNodes(Expr $assignVariable, Expr $eachedVariable): array
|
||||
{
|
||||
|
|
|
@ -83,7 +83,6 @@ CODE_SAMPLE
|
|||
return null;
|
||||
}
|
||||
|
||||
/** @var FuncCall $node */
|
||||
$node->args = $this->composeNewArgs($node);
|
||||
|
||||
return $node;
|
||||
|
|
|
@ -20,6 +20,8 @@ final class ArgumentSorter
|
|||
$newArgsOrParams = [];
|
||||
|
||||
foreach (array_keys($argOrParams) as $position) {
|
||||
assert(is_int($position));
|
||||
|
||||
$newPosition = $oldToNewPositions[$position] ?? null;
|
||||
if ($newPosition === null) {
|
||||
continue;
|
||||
|
|
|
@ -28,6 +28,8 @@ final class SwitchExprsResolver
|
|||
$this->moveDefaultCaseToLast($switch);
|
||||
|
||||
foreach ($switch->cases as $key => $case) {
|
||||
assert(is_int($key));
|
||||
|
||||
if (! $this->isValidCase($case)) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -163,8 +163,8 @@ CODE_SAMPLE
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Param[] $currentClassMethodParams
|
||||
* @param Param[] $parentClassMethodParams
|
||||
* @param array<int, Param> $currentClassMethodParams
|
||||
* @param array<int, Param> $parentClassMethodParams
|
||||
*/
|
||||
private function processReplaceClassMethodParams(
|
||||
ClassMethod $node,
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\Rector\ClassMethod;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
|
||||
use PHPStan\Type\Constant\ConstantArrayType;
|
||||
use PHPStan\Type\NeverType;
|
||||
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
|
||||
use Rector\BetterPhpDocParser\ValueObject\Type\SpacingAwareArrayTypeNode;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
|
||||
use Symplify\Astral\TypeAnalyzer\ClassMethodReturnTypeResolver;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
||||
/**
|
||||
* @see \Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\ArrayShapeFromConstantArrayReturnRectorTest
|
||||
*/
|
||||
final class ArrayShapeFromConstantArrayReturnRector extends AbstractRector
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClassMethodReturnTypeResolver $classMethodReturnTypeResolver,
|
||||
private readonly PhpDocTypeChanger $phpDocTypeChanger
|
||||
) {
|
||||
}
|
||||
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
{
|
||||
return new RuleDefinition('Add array shape exact types based on constant keys of array', [
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
final class SomeClass
|
||||
{
|
||||
public function run(string $name)
|
||||
{
|
||||
return ['name' => $name];
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
final class SomeClass
|
||||
{
|
||||
/**
|
||||
* @return array{name: string}
|
||||
*/
|
||||
public function run(string $name)
|
||||
{
|
||||
return ['name' => $name];
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<class-string<Node>>
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [ClassMethod::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
/** @var Return_[] $returns */
|
||||
$returns = $this->betterNodeFinder->findInstanceOf($node, Return_::class);
|
||||
// exact one shape only
|
||||
if (count($returns) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$return = $returns[0];
|
||||
if (! $return->expr instanceof Expr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$returnExprType = $this->getType($return->expr);
|
||||
if (! $returnExprType instanceof ConstantArrayType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// empty array
|
||||
if ($returnExprType->getKeyType() instanceof NeverType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$returnType = $this->classMethodReturnTypeResolver->resolve($node, $node->getAttribute(AttributeKey::SCOPE));
|
||||
if ($returnType instanceof ConstantArrayType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
|
||||
|
||||
$returnExprTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode(
|
||||
$returnExprType,
|
||||
TypeKind::RETURN()
|
||||
);
|
||||
|
||||
if ($returnExprTypeNode instanceof GenericTypeNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($returnExprTypeNode instanceof SpacingAwareArrayTypeNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$hasChanged = $this->phpDocTypeChanger->changeReturnType($phpDocInfo, $returnExprType);
|
||||
if (! $hasChanged) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
}
|
|
@ -70,6 +70,10 @@ final class TypeNormalizer
|
|||
return $type;
|
||||
}
|
||||
|
||||
if ($type instanceof ConstantArrayType && $arrayNesting === 1) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
// first collection of types
|
||||
if ($arrayNesting === 1) {
|
||||
$this->collectedNestedArrayTypes = [];
|
||||
|
|
|
@ -10,7 +10,7 @@ use Rector\Core\Exception\Configuration\InvalidConfigurationException;
|
|||
final class OutputFormatterCollector
|
||||
{
|
||||
/**
|
||||
* @var OutputFormatterInterface[]
|
||||
* @var array<string, OutputFormatterInterface>
|
||||
*/
|
||||
private array $outputFormatters = [];
|
||||
|
||||
|
@ -32,7 +32,7 @@ final class OutputFormatterCollector
|
|||
}
|
||||
|
||||
/**
|
||||
* @return int[]|string[]
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNames(): array
|
||||
{
|
||||
|
|
|
@ -142,12 +142,9 @@ final class NodeFactory
|
|||
*/
|
||||
public function createArgs(array $values): array
|
||||
{
|
||||
$normalizedValues = [];
|
||||
foreach ($values as $key => $value) {
|
||||
$normalizedValues[$key] = $this->normalizeArgValue($value);
|
||||
}
|
||||
array_walk($values, fn ($value) => $this->normalizeArgValue($value));
|
||||
|
||||
return $this->builderFactory->args($normalizedValues);
|
||||
return $this->builderFactory->args($values);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,6 +34,8 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
|
|||
|
||||
/**
|
||||
* @see \Rector\Core\Tests\PhpParser\Printer\BetterStandardPrinterTest
|
||||
*
|
||||
* @property array<string, array{string, bool, string, null}> $insertionMap
|
||||
*/
|
||||
final class BetterStandardPrinter extends Standard
|
||||
{
|
||||
|
|
|
@ -105,7 +105,7 @@ final class FileDiff implements SerializableInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
* @return array{relative_file_path: string, diff: string, diff_console_formatted: string, rectors_with_line_changes: RectorWithLineChange[]}
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue
Block a user