mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-17 13:28:18 +01:00
[PHP] Add PHP 7.4 Type Property Rector [ref #638]
This commit is contained in:
parent
9058c60a6e
commit
070cfe310e
2
bin/rector-prefixed/build-composer-json.php
Executable file → Normal file
2
bin/rector-prefixed/build-composer-json.php
Executable file → Normal file
@ -9,7 +9,7 @@ require_once __DIR__ . '/../bootstrap.php';
|
||||
$buildDestination = getenv('BUILD_DESTINATION');
|
||||
|
||||
// load
|
||||
$composerJsonPath = $buildDestination . '/composer.json';
|
||||
$composerJsonPath = $buildDestination . '/composer.json';
|
||||
$composerContent = Json::decode(FileSystem::read($composerJsonPath), Json::FORCE_ARRAY);
|
||||
|
||||
// remove unused sections
|
||||
|
@ -41,6 +41,7 @@
|
||||
"Rector\\NodeTypeResolver\\": "packages/NodeTypeResolver/src",
|
||||
"Rector\\Symfony\\": "packages/Symfony/src",
|
||||
"Rector\\CakePHP\\": "packages/CakePHP/src",
|
||||
"Rector\\Php\\": "packages/Php/src",
|
||||
"Rector\\Silverstripe\\": "packages/Silverstripe/src",
|
||||
"Rector\\ParameterGuider\\": "packages/ParameterGuider/src",
|
||||
"Rector\\Sensio\\": "packages/Sensio/src",
|
||||
@ -59,6 +60,7 @@
|
||||
"Rector\\Tests\\": "tests",
|
||||
"Rector\\NodeTypeResolver\\Tests\\": "packages/NodeTypeResolver/tests",
|
||||
"Rector\\CakePHP\\Tests\\": "packages/CakePHP/tests",
|
||||
"Rector\\Php\\Tests\\": "packages/Php/tests",
|
||||
"Rector\\Symfony\\Tests\\": "packages/Symfony/tests",
|
||||
"Rector\\Silverstripe\\Tests\\": "packages/Silverstripe/tests",
|
||||
"Rector\\Sensio\\Tests\\": "packages/Sensio/tests",
|
||||
@ -72,7 +74,19 @@
|
||||
"Rector\\FileSystemRector\\Tests\\": "packages/FileSystemRector/tests"
|
||||
},
|
||||
"classmap": [
|
||||
"packages",
|
||||
"packages/CakePHP/tests",
|
||||
"packages/Doctrine/tests",
|
||||
"packages/FileSystemRector/tests",
|
||||
"packages/NodeTypeResolver/tests",
|
||||
"packages/ParameterGuider/tests",
|
||||
"packages/PhpParser/tests",
|
||||
"packages/PHPUnit/tests",
|
||||
"packages/Sensio/tests",
|
||||
"packages/Silverstripe/tests",
|
||||
"packages/Sylius/tests",
|
||||
"packages/Symfony/tests",
|
||||
"packages/Twig/tests",
|
||||
"packages/Utils/tests",
|
||||
"tests/Issues",
|
||||
"tests/Rector"
|
||||
],
|
||||
|
2
config/level/php/php74.yml
Normal file
2
config/level/php/php74.yml
Normal file
@ -0,0 +1,2 @@
|
||||
services:
|
||||
Rector\Php\Rector\TypedPropertyRector: ~
|
4
ecs.yml
4
ecs.yml
@ -103,3 +103,7 @@ parameters:
|
||||
- '*CompilerPass.php'
|
||||
# array type check
|
||||
- 'src/RectorDefinition/RectorDefinition.php'
|
||||
|
||||
Symplify\CodingStandard\Sniffs\ControlStructure\SprintfOverContactSniff:
|
||||
# respects inherited pattern for better comparing
|
||||
- 'src/Printer/BetterStandardPrinter.php'
|
||||
|
@ -120,6 +120,20 @@ final class DocBlockAnalyzer
|
||||
return $phpDocInfo->getVarTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNonFqnVarTypes(Node $node): array
|
||||
{
|
||||
if ($node->getDocComment() === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
|
||||
|
||||
return $phpDocInfo->getVarTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo add test for Multi|Types
|
||||
*/
|
||||
|
211
packages/Php/src/Rector/TypedPropertyRector.php
Normal file
211
packages/Php/src/Rector/TypedPropertyRector.php
Normal file
@ -0,0 +1,211 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Rector;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Scalar\DNumber;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\NodeTypeResolver\Node\Attribute;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockAnalyzer;
|
||||
use Rector\Php\TypeAnalyzer;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
/**
|
||||
* @source https://wiki.php.net/rfc/typed_properties_v2#proposal
|
||||
*/
|
||||
final class TypedPropertyRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const PHP74_PROPERTY_TYPE = 'php74_property_type';
|
||||
|
||||
/**
|
||||
* @var DocBlockAnalyzer
|
||||
*/
|
||||
private $docBlockAnalyzer;
|
||||
|
||||
/**
|
||||
* @var string[][]
|
||||
*/
|
||||
private $typeNameToAllowedDefaultNodeType = [
|
||||
'string' => [String_::class],
|
||||
'bool' => [ConstFetch::class],
|
||||
'array' => [Array_::class],
|
||||
'float' => [DNumber::class, LNumber::class],
|
||||
'int' => [LNumber::class],
|
||||
'iterable' => [Array_::class],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var TypeAnalyzer
|
||||
*/
|
||||
private $typeAnalyzer;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isNullableType = false;
|
||||
|
||||
public function __construct(DocBlockAnalyzer $docBlockAnalyzer, TypeAnalyzer $typeAnalyzer)
|
||||
{
|
||||
$this->docBlockAnalyzer = $docBlockAnalyzer;
|
||||
$this->typeAnalyzer = $typeAnalyzer;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition(
|
||||
'Changes property @var annotations from annotation to type.',
|
||||
[
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
final class SomeClass
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private count;
|
||||
}
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
final class SomeClass
|
||||
{
|
||||
private int count;
|
||||
}
|
||||
CODE_SAMPLE
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Property::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Property $propertyNode
|
||||
*/
|
||||
public function refactor(Node $propertyNode): ?Node
|
||||
{
|
||||
// non FQN, so they are 1:1 to possible imported doc type
|
||||
$varTypes = $this->docBlockAnalyzer->getNonFqnVarTypes($propertyNode);
|
||||
|
||||
// too many types to handle
|
||||
if (count($varTypes) > 2) {
|
||||
return $propertyNode;
|
||||
}
|
||||
|
||||
$this->isNullableType = in_array('null', $varTypes, true);
|
||||
|
||||
// exactly 1 type only can be changed || 2 types with nullable; nothing else
|
||||
if (count($varTypes) !== 1 && (count($varTypes) === 2 && ! $this->isNullableType)) {
|
||||
return $propertyNode;
|
||||
}
|
||||
|
||||
$propertyType = $this->getPropertyTypeWithoutNull($varTypes);
|
||||
$propertyType = $this->shortenLongType($propertyType);
|
||||
if (! $this->typeAnalyzer->isPropertyTypeHintableType($propertyType)) {
|
||||
return $propertyNode;
|
||||
}
|
||||
|
||||
if (! $this->matchesDocTypeAndDefaultValueType($propertyType, $propertyNode)) {
|
||||
return $propertyNode;
|
||||
}
|
||||
|
||||
if ($this->isNullableType) {
|
||||
$propertyType = '?' . $propertyType;
|
||||
}
|
||||
|
||||
$this->docBlockAnalyzer->removeTagFromNode($propertyNode, 'var');
|
||||
|
||||
$propertyNode->setAttribute(self::PHP74_PROPERTY_TYPE, $propertyType);
|
||||
|
||||
// invoke the print, because only attribute has changed
|
||||
$propertyNode->setAttribute(Attribute::ORIGINAL_NODE, null);
|
||||
|
||||
return $propertyNode;
|
||||
}
|
||||
|
||||
private function matchesDocTypeAndDefaultValueType(string $propertyType, Property $propertyNode): bool
|
||||
{
|
||||
$defaultValueNode = $propertyNode->props[0]->default;
|
||||
if ($defaultValueNode === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! isset($this->typeNameToAllowedDefaultNodeType[$propertyType])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->isNullableType) {
|
||||
// is default value "null"?
|
||||
return $this->isContantWithValue($defaultValueNode, 'null');
|
||||
}
|
||||
|
||||
return $this->matchesDefaultValueToExpectedNodeTypes($propertyType, $defaultValueNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[]|string $value
|
||||
*/
|
||||
private function isContantWithValue(Node $node, $value): bool
|
||||
{
|
||||
return $node instanceof ConstFetch && in_array((string) $node->name, (array) $value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $varTypes
|
||||
*/
|
||||
private function getPropertyTypeWithoutNull(array $varTypes): string
|
||||
{
|
||||
if ($this->isNullableType) {
|
||||
$nullTypePosition = array_search('null', $varTypes, true);
|
||||
unset($varTypes[$nullTypePosition]);
|
||||
}
|
||||
|
||||
return (string) array_pop($varTypes);
|
||||
}
|
||||
|
||||
private function shortenLongType(string $type): string
|
||||
{
|
||||
if ($type === 'boolean') {
|
||||
return 'bool';
|
||||
}
|
||||
|
||||
if ($type === 'integer') {
|
||||
return 'int';
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
private function matchesDefaultValueToExpectedNodeTypes(string $propertyType, Node $defaultValueNode): bool
|
||||
{
|
||||
$allowedDefaultNodeTypes = $this->typeNameToAllowedDefaultNodeType[$propertyType];
|
||||
|
||||
foreach ($allowedDefaultNodeTypes as $allowedDefaultNodeType) {
|
||||
if (is_a($defaultValueNode, $allowedDefaultNodeType, true)) {
|
||||
if ($propertyType === 'bool') {
|
||||
// make sure it's the right constant value
|
||||
return $this->isContantWithValue($defaultValueNode, ['true', 'false']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
final class ClassWithProperty
|
||||
{
|
||||
private int $count;
|
||||
|
||||
/**
|
||||
* @var int|null|bool
|
||||
*/
|
||||
private $multiCount;
|
||||
|
||||
/**
|
||||
* another comment
|
||||
*/
|
||||
private bool $isTrue = false;
|
||||
|
||||
/**
|
||||
* @var void
|
||||
*/
|
||||
private $shouldBeSkipped;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $shouldBeSkippedToo;
|
||||
|
||||
/**
|
||||
* @var invalid
|
||||
*/
|
||||
private $cantTouchThis;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
use Rector\Php\Tests\Rector\TypedPropertyRector\Source\AnotherClass;
|
||||
|
||||
final class ClassWithClassProperty
|
||||
{
|
||||
private AnotherClass $anotherClass;
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
use Rector\Php\Tests\Rector\TypedPropertyRector\Source\AnotherClass;
|
||||
|
||||
final class ClassWithNullableProperty
|
||||
{
|
||||
private ?AnotherClass $anotherClass = null;
|
||||
|
||||
private ?AnotherClass $yetAnotherClass;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
use Rector\Php\Tests\Rector\TypedPropertyRector\Source\AnotherClass;
|
||||
|
||||
final class ClassWithStaticProperty
|
||||
{
|
||||
private static iterable $iterable;
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
final class DefaultValues
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $name = 'not_a_bool';
|
||||
|
||||
private bool $isItRealName = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isItRealNameNull = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $size = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $items = null;
|
||||
|
||||
/**
|
||||
* @var iterable
|
||||
*/
|
||||
private $itemsB = null;
|
||||
|
||||
private ?array $nullableItems = null;
|
||||
|
||||
private float $a = 42.42;
|
||||
|
||||
private float $b = 42;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $c = 'hey';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $e = 42.42;
|
||||
|
||||
private int $f = 42;
|
||||
|
||||
private array $g = [1, 2, 3];
|
||||
|
||||
private iterable $h = [1, 2, 3];
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
final class MatchTypes
|
||||
{
|
||||
private bool $a;
|
||||
|
||||
private bool $b;
|
||||
|
||||
private int $c;
|
||||
|
||||
private int $d;
|
||||
|
||||
private float $e;
|
||||
|
||||
private string $f;
|
||||
|
||||
private object $g;
|
||||
|
||||
private iterable $h;
|
||||
|
||||
private self $i;
|
||||
|
||||
private parent $j;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Source;
|
||||
|
||||
final class AnotherClass
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
/**
|
||||
* @covers \Rector\Php\Rector\TypedPropertyRector
|
||||
*/
|
||||
final class TypedPropertyRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideWrongToFixedFiles()
|
||||
*/
|
||||
public function test(string $wrong, string $fixed): void
|
||||
{
|
||||
$this->doTestFileMatchesExpectedContent($wrong, $fixed);
|
||||
}
|
||||
|
||||
public function provideWrongToFixedFiles(): Iterator
|
||||
{
|
||||
yield [__DIR__ . '/Wrong/ClassWithProperty.php', __DIR__ . '/Correct/correct.php.inc'];
|
||||
yield [__DIR__ . '/Wrong/ClassWithClassProperty.php', __DIR__ . '/Correct/correct2.php.inc'];
|
||||
yield [__DIR__ . '/Wrong/ClassWithNullableProperty.php', __DIR__ . '/Correct/correct3.php.inc'];
|
||||
yield [__DIR__ . '/Wrong/ClassWithStaticProperty.php', __DIR__ . '/Correct/correct4.php.inc'];
|
||||
yield [__DIR__ . '/Wrong/DefaultValues.php', __DIR__ . '/Correct/correct5.php.inc'];
|
||||
// based on: https://wiki.php.net/rfc/typed_properties_v2#supported_types
|
||||
yield [__DIR__ . '/Wrong/MatchTypes.php', __DIR__ . '/Correct/correct6.php.inc'];
|
||||
}
|
||||
|
||||
protected function provideConfig(): string
|
||||
{
|
||||
return __DIR__ . '/config.yml';
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
use Rector\Php\Tests\Rector\TypedPropertyRector\Source\AnotherClass;
|
||||
|
||||
final class ClassWithClassProperty
|
||||
{
|
||||
/**
|
||||
* @var AnotherClass
|
||||
*/
|
||||
private $anotherClass;
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
use Rector\Php\Tests\Rector\TypedPropertyRector\Source\AnotherClass;
|
||||
|
||||
final class ClassWithNullableProperty
|
||||
{
|
||||
/**
|
||||
* @var AnotherClass|null
|
||||
*/
|
||||
private $anotherClass = null;
|
||||
|
||||
/**
|
||||
* @var null|AnotherClass
|
||||
*/
|
||||
private $yetAnotherClass;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
final class ClassWithProperty
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $count;
|
||||
|
||||
/**
|
||||
* @var int|null|bool
|
||||
*/
|
||||
private $multiCount;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* another comment
|
||||
*/
|
||||
private $isTrue = false;
|
||||
|
||||
/**
|
||||
* @var void
|
||||
*/
|
||||
private $shouldBeSkipped;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $shouldBeSkippedToo;
|
||||
|
||||
/**
|
||||
* @var invalid
|
||||
*/
|
||||
private $cantTouchThis;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
use Rector\Php\Tests\Rector\TypedPropertyRector\Source\AnotherClass;
|
||||
|
||||
final class ClassWithStaticProperty
|
||||
{
|
||||
/**
|
||||
* @var iterable
|
||||
*/
|
||||
private static $iterable;
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
final class DefaultValues
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $name = 'not_a_bool';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isItRealName = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isItRealNameNull = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $size = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $items = null;
|
||||
|
||||
/**
|
||||
* @var iterable
|
||||
*/
|
||||
private $itemsB = null;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private $nullableItems = null;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $a = 42.42;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $b = 42;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $c = 'hey';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $e = 42.42;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $f = 42;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $g = [1, 2, 3];
|
||||
|
||||
/**
|
||||
* @var iterable
|
||||
*/
|
||||
private $h = [1, 2, 3];
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\TypedPropertyRector\Wrong;
|
||||
|
||||
final class MatchTypes
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $a;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private $b;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $c;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $d;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $e;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $f;
|
||||
|
||||
/**
|
||||
* @var object
|
||||
*/
|
||||
private $g;
|
||||
|
||||
/**
|
||||
* @var iterable
|
||||
*/
|
||||
private $h;
|
||||
|
||||
/**
|
||||
* @var self
|
||||
*/
|
||||
private $i;
|
||||
|
||||
/**
|
||||
* @var parent
|
||||
*/
|
||||
private $j;
|
||||
}
|
2
packages/Php/tests/Rector/TypedPropertyRector/config.yml
Normal file
2
packages/Php/tests/Rector/TypedPropertyRector/config.yml
Normal file
@ -0,0 +1,2 @@
|
||||
services:
|
||||
Rector\Php\Rector\TypedPropertyRector: ~
|
@ -6,11 +6,49 @@ use Nette\Utils\Strings;
|
||||
|
||||
final class TypeAnalyzer
|
||||
{
|
||||
public function isPropertyTypeHintableType(string $type): bool
|
||||
{
|
||||
if (empty($type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// first letter is upper, probably class type
|
||||
if (ctype_upper($type[0])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $this->isPhpReservedType($type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// callable and iterable are not property typehintable
|
||||
// @see https://wiki.php.net/rfc/typed_properties_v2#supported_types
|
||||
if (in_array($type, ['callable', 'void'], true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isPhpReservedType(string $type): bool
|
||||
{
|
||||
return in_array(
|
||||
$type,
|
||||
['string', 'bool', 'null', 'false', 'true', 'mixed', 'object', 'iterable', 'array', 'float', 'int'],
|
||||
[
|
||||
'string',
|
||||
'bool',
|
||||
'null',
|
||||
'false',
|
||||
'true',
|
||||
'mixed',
|
||||
'object',
|
||||
'iterable',
|
||||
'array',
|
||||
'float',
|
||||
'int',
|
||||
'self',
|
||||
'parent',
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
@ -4,8 +4,10 @@ namespace Rector\Printer;
|
||||
|
||||
use PhpParser\Node\Expr\Yield_;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PhpParser\PrettyPrinter\Standard;
|
||||
use Rector\NodeTypeResolver\Node\Attribute;
|
||||
use Rector\Php\Rector\TypedPropertyRector;
|
||||
use function Safe\sprintf;
|
||||
|
||||
final class BetterStandardPrinter extends Standard
|
||||
@ -45,4 +47,17 @@ final class BetterStandardPrinter extends Standard
|
||||
$shouldAddBrackets ? ')' : ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print property with PHP 7.4 type
|
||||
*/
|
||||
protected function pStmt_Property(Property $node): string
|
||||
{
|
||||
$type = $node->getAttribute(TypedPropertyRector::PHP74_PROPERTY_TYPE);
|
||||
|
||||
return $node->flags === 0 ? 'var ' : $this->pModifiers($node->flags) .
|
||||
($type ? $type . ' ' : '') .
|
||||
$this->pCommaSeparated($node->props) .
|
||||
';';
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user