?????????? ????????? - ??????????????? - /home/agenciai/public_html/cd38d8/Php81.tar
???????
NodeFactory/EnumFactory.php 0000644 00000017533 15126701311 0011734 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\NodeFactory; use RectorPrefix202411\Nette\Utils\Strings; use PhpParser\BuilderFactory; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ArrayItem; use PhpParser\Node\Identifier; use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Enum_; use PhpParser\Node\Stmt\EnumCase; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PhpParser\Node\Value\ValueResolver; final class EnumFactory { /** * @readonly * @var \Rector\NodeNameResolver\NodeNameResolver */ private $nodeNameResolver; /** * @readonly * @var \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory */ private $phpDocInfoFactory; /** * @readonly * @var \PhpParser\BuilderFactory */ private $builderFactory; /** * @readonly * @var \Rector\PhpParser\Node\Value\ValueResolver */ private $valueResolver; /** * @readonly * @var \Rector\PhpParser\Node\BetterNodeFinder */ private $betterNodeFinder; /** * @var string * @see https://stackoverflow.com/a/2560017 * @see https://regex101.com/r/2xEQVj/1 for changing iso9001 to iso_9001 * @see https://regex101.com/r/Ykm6ub/1 for changing XMLParser to XML_Parser * @see https://regex101.com/r/Zv4JhD/1 for changing needsReview to needs_Review */ private const PASCAL_CASE_TO_UNDERSCORE_REGEX = '/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])/'; /** * @var string * @see https://regex101.com/r/FneU33/1 */ private const MULTI_UNDERSCORES_REGEX = '#_{2,}#'; public function __construct(NodeNameResolver $nodeNameResolver, PhpDocInfoFactory $phpDocInfoFactory, BuilderFactory $builderFactory, ValueResolver $valueResolver, BetterNodeFinder $betterNodeFinder) { $this->nodeNameResolver = $nodeNameResolver; $this->phpDocInfoFactory = $phpDocInfoFactory; $this->builderFactory = $builderFactory; $this->valueResolver = $valueResolver; $this->betterNodeFinder = $betterNodeFinder; } public function createFromClass(Class_ $class) : Enum_ { $shortClassName = $this->nodeNameResolver->getShortName($class); $enum = new Enum_($shortClassName, [], ['startLine' => $class->getStartLine(), 'endLine' => $class->getEndLine()]); $enum->namespacedName = $class->namespacedName; $constants = $class->getConstants(); $enum->stmts = $class->getTraitUses(); if ($constants !== []) { $value = $this->valueResolver->getValue($constants[0]->consts[0]->value); $enum->scalarType = \is_string($value) ? new Identifier('string') : new Identifier('int'); // constant to cases foreach ($constants as $constant) { $enum->stmts[] = $this->createEnumCaseFromConst($constant); } } $enum->stmts = \array_merge($enum->stmts, $class->getMethods()); return $enum; } public function createFromSpatieClass(Class_ $class, bool $enumNameInSnakeCase = \false) : Enum_ { $shortClassName = $this->nodeNameResolver->getShortName($class); $enum = new Enum_($shortClassName, [], ['startLine' => $class->getStartLine(), 'endLine' => $class->getEndLine()]); $enum->namespacedName = $class->namespacedName; // constant to cases $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($class); $docBlockMethods = $phpDocInfo->getTagsByName('@method'); if ($docBlockMethods !== []) { $mapping = $this->generateMappingFromClass($class); $identifierType = $this->getIdentifierTypeFromMappings($mapping); $enum->scalarType = new Identifier($identifierType); foreach ($docBlockMethods as $docBlockMethod) { $enum->stmts[] = $this->createEnumCaseFromDocComment($docBlockMethod, $class, $mapping, $enumNameInSnakeCase); } } return $enum; } private function createEnumCaseFromConst(ClassConst $classConst) : EnumCase { $constConst = $classConst->consts[0]; $enumCase = new EnumCase($constConst->name, $constConst->value, [], ['startLine' => $constConst->getStartLine(), 'endLine' => $constConst->getEndLine()]); // mirror comments $enumCase->setAttribute(AttributeKey::PHP_DOC_INFO, $classConst->getAttribute(AttributeKey::PHP_DOC_INFO)); $enumCase->setAttribute(AttributeKey::COMMENTS, $classConst->getAttribute(AttributeKey::COMMENTS)); return $enumCase; } /** * @param array<int|string, mixed> $mapping */ private function createEnumCaseFromDocComment(PhpDocTagNode $phpDocTagNode, Class_ $class, array $mapping = [], bool $enumNameInSnakeCase = \false) : EnumCase { /** @var MethodTagValueNode $nodeValue */ $nodeValue = $phpDocTagNode->value; $enumValue = $mapping[$nodeValue->methodName] ?? $nodeValue->methodName; if ($enumNameInSnakeCase) { $enumName = \strtoupper(Strings::replace($nodeValue->methodName, self::PASCAL_CASE_TO_UNDERSCORE_REGEX, '_$0')); $enumName = Strings::replace($enumName, self::MULTI_UNDERSCORES_REGEX, '_'); } else { $enumName = \strtoupper($nodeValue->methodName); } $enumExpr = $this->builderFactory->val($enumValue); return new EnumCase($enumName, $enumExpr, [], ['startLine' => $class->getStartLine(), 'endLine' => $class->getEndLine()]); } /** * @return array<int|string, mixed> */ private function generateMappingFromClass(Class_ $class) : array { $classMethod = $class->getMethod('values'); if (!$classMethod instanceof ClassMethod) { return []; } $returns = $this->betterNodeFinder->findReturnsScoped($classMethod); /** @var array<int|string, mixed> $mapping */ $mapping = []; foreach ($returns as $return) { if (!$return->expr instanceof Array_) { continue; } $mapping = $this->collectMappings($return->expr->items, $mapping); } return $mapping; } /** * @param null[]|ArrayItem[] $items * @param array<int|string, mixed> $mapping * @return array<int|string, mixed> */ private function collectMappings(array $items, array $mapping) : array { foreach ($items as $item) { if (!$item instanceof ArrayItem) { continue; } if (!$item->key instanceof LNumber && !$item->key instanceof String_) { continue; } if (!$item->value instanceof LNumber && !$item->value instanceof String_) { continue; } $mapping[$item->key->value] = $item->value->value; } return $mapping; } /** * @param array<int|string, mixed> $mapping */ private function getIdentifierTypeFromMappings(array $mapping) : string { $callableGetType = static function ($value) : string { return \gettype($value); }; $valueTypes = \array_map($callableGetType, $mapping); $uniqueValueTypes = \array_unique($valueTypes); if (\count($uniqueValueTypes) === 1) { $identifierType = \reset($uniqueValueTypes); if ($identifierType === 'integer') { $identifierType = 'int'; } } else { $identifierType = 'string'; } return $identifierType; } } NodeAnalyzer/ComplexNewAnalyzer.php 0000644 00000003775 15126701311 0013450 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\NodeAnalyzer; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ArrayItem; use PhpParser\Node\Expr\New_; use PhpParser\Node\Name\FullyQualified; use Rector\NodeAnalyzer\ExprAnalyzer; final class ComplexNewAnalyzer { /** * @readonly * @var \Rector\NodeAnalyzer\ExprAnalyzer */ private $exprAnalyzer; public function __construct(ExprAnalyzer $exprAnalyzer) { $this->exprAnalyzer = $exprAnalyzer; } public function isDynamic(New_ $new) : bool { if (!$new->class instanceof FullyQualified) { return \true; } if ($new->isFirstClassCallable()) { return \false; } $args = $new->getArgs(); foreach ($args as $arg) { $value = $arg->value; if ($this->isAllowedNew($value)) { continue; } // new inside array is allowed for New in initializer if ($value instanceof Array_ && $this->isAllowedArray($value)) { continue; } if (!$this->exprAnalyzer->isDynamicExpr($value)) { continue; } return \true; } return \false; } private function isAllowedNew(Expr $expr) : bool { if ($expr instanceof New_) { return !$this->isDynamic($expr); } return \false; } private function isAllowedArray(Array_ $array) : bool { if (!$this->exprAnalyzer->isDynamicArray($array)) { return \true; } $arrayItems = $array->items; foreach ($arrayItems as $arrayItem) { if (!$arrayItem instanceof ArrayItem) { continue; } if (!$arrayItem->value instanceof New_) { return \false; } if ($this->isDynamic($arrayItem->value)) { return \false; } } return \true; } } NodeAnalyzer/CoalesePropertyAssignMatcher.php 0000644 00000004063 15126701311 0015441 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\NodeAnalyzer; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\BinaryOp\Coalesce; use PhpParser\Node\Expr\New_; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Expression; use Rector\NodeNameResolver\NodeNameResolver; final class CoalesePropertyAssignMatcher { /** * @readonly * @var \Rector\Php81\NodeAnalyzer\ComplexNewAnalyzer */ private $complexNewAnalyzer; /** * @readonly * @var \Rector\NodeNameResolver\NodeNameResolver */ private $nodeNameResolver; public function __construct(\Rector\Php81\NodeAnalyzer\ComplexNewAnalyzer $complexNewAnalyzer, NodeNameResolver $nodeNameResolver) { $this->complexNewAnalyzer = $complexNewAnalyzer; $this->nodeNameResolver = $nodeNameResolver; } /** * Matches * * $this->value = $param ?? 'default'; */ public function matchCoalesceAssignsToLocalPropertyNamed(Stmt $stmt, string $propertyName) : ?Coalesce { if (!$stmt instanceof Expression) { return null; } if (!$stmt->expr instanceof Assign) { return null; } $assign = $stmt->expr; if (!$assign->expr instanceof Coalesce) { return null; } $coalesce = $assign->expr; if (!$coalesce->right instanceof New_) { return null; } if ($this->complexNewAnalyzer->isDynamic($coalesce->right)) { return null; } if (!$this->isLocalPropertyFetchNamed($assign->var, $propertyName)) { return null; } return $assign->expr; } private function isLocalPropertyFetchNamed(Expr $expr, string $propertyName) : bool { if (!$expr instanceof PropertyFetch) { return \false; } if (!$this->nodeNameResolver->isName($expr->var, 'this')) { return \false; } return $this->nodeNameResolver->isName($expr->name, $propertyName); } } Rector/Class_/SpatieEnumClassToEnumRector.php 0000644 00000004717 15126701311 0015274 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\Rector\Class_; use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Enum_; use PHPStan\Type\ObjectType; use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\Php81\NodeFactory\EnumFactory; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php81\Rector\Class_\SpatieEnumClassToEnumRector\SpatieEnumClassToEnumRectorTest */ final class SpatieEnumClassToEnumRector extends AbstractRector implements MinPhpVersionInterface, ConfigurableRectorInterface { /** * @readonly * @var \Rector\Php81\NodeFactory\EnumFactory */ private $enumFactory; /** * @var string */ public const TO_UPPER_SNAKE_CASE = 'toUpperSnakeCase'; /** * @var bool */ private $toUpperSnakeCase = \false; public function __construct(EnumFactory $enumFactory) { $this->enumFactory = $enumFactory; } public function provideMinPhpVersion() : int { return PhpVersionFeature::ENUM; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Refactor Spatie enum class to native Enum', [new ConfiguredCodeSample(<<<'CODE_SAMPLE' use \Spatie\Enum\Enum; /** * @method static self draft() * @method static self published() * @method static self archived() */ class StatusEnum extends Enum { } CODE_SAMPLE , <<<'CODE_SAMPLE' enum StatusEnum : string { case DRAFT = 'draft'; case PUBLISHED = 'published'; case ARCHIVED = 'archived'; } CODE_SAMPLE , [self::TO_UPPER_SNAKE_CASE => \false])]); } /** * @return array<class-string<Node>> */ public function getNodeTypes() : array { return [Class_::class]; } /** * @param Class_ $node */ public function refactor(Node $node) : ?Enum_ { if (!$this->isObjectType($node, new ObjectType('Spatie\\Enum\\Enum'))) { return null; } return $this->enumFactory->createFromSpatieClass($node, $this->toUpperSnakeCase); } /** * @param mixed[] $configuration */ public function configure(array $configuration) : void { $this->toUpperSnakeCase = $configuration[self::TO_UPPER_SNAKE_CASE] ?? \false; } } Rector/Class_/MyCLabsClassToEnumRector.php 0000644 00000003447 15126701311 0014513 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\Rector\Class_; use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PHPStan\Type\ObjectType; use Rector\Php81\NodeFactory\EnumFactory; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php81\Rector\Class_\MyCLabsClassToEnumRector\MyCLabsClassToEnumRectorTest */ final class MyCLabsClassToEnumRector extends AbstractRector implements MinPhpVersionInterface { /** * @readonly * @var \Rector\Php81\NodeFactory\EnumFactory */ private $enumFactory; public function __construct(EnumFactory $enumFactory) { $this->enumFactory = $enumFactory; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Refactor MyCLabs enum class to native Enum', [new CodeSample(<<<'CODE_SAMPLE' use MyCLabs\Enum\Enum; final class Action extends Enum { private const VIEW = 'view'; private const EDIT = 'edit'; } CODE_SAMPLE , <<<'CODE_SAMPLE' enum Action : string { case VIEW = 'view'; case EDIT = 'edit'; } CODE_SAMPLE )]); } /** * @return array<class-string<Node>> */ public function getNodeTypes() : array { return [Class_::class]; } /** * @param Class_ $node */ public function refactor(Node $node) : ?Node { if (!$this->isObjectType($node, new ObjectType('MyCLabs\\Enum\\Enum'))) { return null; } return $this->enumFactory->createFromClass($node); } public function provideMinPhpVersion() : int { return PhpVersionFeature::ENUM; } } Rector/MethodCall/MyCLabsMethodCallToEnumConstRector.php 0000644 00000021577 15126701311 0017305 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\Rector\MethodCall; use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php81\Rector\MethodCall\MyCLabsMethodCallToEnumConstRector\MyCLabsMethodCallToEnumConstRectorTest */ final class MyCLabsMethodCallToEnumConstRector extends AbstractRector implements MinPhpVersionInterface { /** * @readonly * @var \PHPStan\Reflection\ReflectionProvider */ private $reflectionProvider; /** * @var string[] */ private const ENUM_METHODS = ['from', 'values', 'keys', 'isValid', 'search', 'toArray', 'assertValidValue']; public function __construct(ReflectionProvider $reflectionProvider) { $this->reflectionProvider = $reflectionProvider; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Refactor MyCLabs enum fetch to Enum const', [new CodeSample(<<<'CODE_SAMPLE' $name = SomeEnum::VALUE()->getKey(); CODE_SAMPLE , <<<'CODE_SAMPLE' $name = SomeEnum::VALUE; CODE_SAMPLE )]); } /** * @return array<class-string<Node>> */ public function getNodeTypes() : array { return [MethodCall::class, StaticCall::class]; } /** * @param MethodCall|StaticCall $node */ public function refactor(Node $node) : ?Node { if ($node->name instanceof Expr) { return null; } $enumCaseName = $this->getName($node->name); if ($enumCaseName === null) { return null; } if ($this->shouldOmitEnumCase($enumCaseName)) { return null; } if ($node instanceof MethodCall) { return $this->refactorMethodCall($node, $enumCaseName); } if (!$this->isObjectType($node->class, new ObjectType('MyCLabs\\Enum\\Enum'))) { return null; } $className = $this->getName($node->class); if (!\is_string($className)) { return null; } if (!$this->isEnumConstant($className, $enumCaseName)) { return null; } return $this->nodeFactory->createClassConstFetch($className, $enumCaseName); } public function provideMinPhpVersion() : int { return PhpVersionFeature::ENUM; } private function isEnumConstant(string $className, string $constant) : bool { $classReflection = $this->reflectionProvider->getClass($className); return $classReflection->hasConstant($constant); } private function refactorGetKeyMethodCall(MethodCall $methodCall) : ?ClassConstFetch { if (!$methodCall->var instanceof StaticCall) { return null; } $staticCall = $methodCall->var; $className = $this->getName($staticCall->class); if ($className === null) { return null; } $enumCaseName = $this->getName($staticCall->name); if ($enumCaseName === null) { return null; } if ($this->shouldOmitEnumCase($enumCaseName)) { return null; } return $this->nodeFactory->createClassConstFetch($className, $enumCaseName); } private function refactorGetValueMethodCall(MethodCall $methodCall) : ?PropertyFetch { if (!$methodCall->var instanceof StaticCall) { return null; } $staticCall = $methodCall->var; $className = $this->getName($staticCall->class); if ($className === null) { return null; } $enumCaseName = $this->getName($staticCall->name); if ($enumCaseName === null) { return null; } if ($this->shouldOmitEnumCase($enumCaseName)) { return null; } $classConstFetch = $this->nodeFactory->createClassConstFetch($className, $enumCaseName); return new PropertyFetch($classConstFetch, 'value'); } private function refactorEqualsMethodCall(MethodCall $methodCall) : ?Identical { $expr = $this->getNonEnumReturnTypeExpr($methodCall->var); if (!$expr instanceof Expr) { $expr = $this->getValidEnumExpr($methodCall->var); if (!$expr instanceof Expr) { return null; } } $arg = $methodCall->getArgs()[0] ?? null; if (!$arg instanceof Arg) { return null; } $right = $this->getNonEnumReturnTypeExpr($arg->value); if (!$right instanceof Expr) { $right = $this->getValidEnumExpr($arg->value); if (!$right instanceof Expr) { return null; } } return new Identical($expr, $right); } /** * @param \PhpParser\Node\Expr\StaticCall|\PhpParser\Node\Expr\MethodCall $node */ private function isCallerClassEnum($node) : bool { if ($node instanceof StaticCall) { return $this->isObjectType($node->class, new ObjectType('MyCLabs\\Enum\\Enum')); } return $this->isObjectType($node->var, new ObjectType('MyCLabs\\Enum\\Enum')); } /** * @return null|\PhpParser\Node\Expr\ClassConstFetch|\PhpParser\Node\Expr */ private function getNonEnumReturnTypeExpr(Node $node) { if (!$node instanceof StaticCall && !$node instanceof MethodCall) { return null; } if ($this->isCallerClassEnum($node)) { $methodName = $this->getName($node->name); if ($methodName === null) { return null; } if ($node instanceof StaticCall) { $className = $this->getName($node->class); } if ($node instanceof MethodCall) { $className = $this->getName($node->var); } if ($className === null) { return null; } $classReflection = $this->reflectionProvider->getClass($className); // method self::getValidEnumExpr process enum static methods from constants if ($classReflection->hasConstant($methodName)) { return null; } } return $node; } /** * @return null|\PhpParser\Node\Expr\ClassConstFetch|\PhpParser\Node\Expr */ private function getValidEnumExpr(Node $node) { switch (\get_class($node)) { case Variable::class: case PropertyFetch::class: return $this->getPropertyFetchOrVariable($node); case StaticCall::class: return $this->getEnumConstFetch($node); default: return null; } } /** * @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\Variable $expr * @return null|\PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\Variable */ private function getPropertyFetchOrVariable($expr) { if (!$this->isObjectType($expr, new ObjectType('MyCLabs\\Enum\\Enum'))) { return null; } return $expr; } private function getEnumConstFetch(StaticCall $staticCall) : ?\PhpParser\Node\Expr\ClassConstFetch { $className = $this->getName($staticCall->class); if ($className === null) { return null; } $enumCaseName = $this->getName($staticCall->name); if ($enumCaseName === null) { return null; } if ($this->shouldOmitEnumCase($enumCaseName)) { return null; } return $this->nodeFactory->createClassConstFetch($className, $enumCaseName); } /** * @return null|\PhpParser\Node\Expr\ClassConstFetch|\PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\BinaryOp\Identical */ private function refactorMethodCall(MethodCall $methodCall, string $methodName) { if (!$this->isObjectType($methodCall->var, new ObjectType('MyCLabs\\Enum\\Enum'))) { return null; } if ($methodName === 'getKey') { return $this->refactorGetKeyMethodCall($methodCall); } if ($methodName === 'getValue') { return $this->refactorGetValueMethodCall($methodCall); } if ($methodName === 'equals') { return $this->refactorEqualsMethodCall($methodCall); } return null; } private function shouldOmitEnumCase(string $enumCaseName) : bool { return \in_array($enumCaseName, self::ENUM_METHODS, \true); } } Rector/MethodCall/SpatieEnumMethodCallToEnumConstRector.php 0000644 00000010612 15126701311 0020051 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\Rector\MethodCall; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\StaticCall; use PHPStan\Type\ObjectType; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php81\Rector\MethodCall\MyCLabsMethodCallToEnumConstRector\MyCLabsMethodCallToEnumConstRectorTest */ final class SpatieEnumMethodCallToEnumConstRector extends AbstractRector implements MinPhpVersionInterface { /** * @var string */ private const SPATIE_FQN = 'Spatie\\Enum\\Enum'; /** * @var string[] */ private const ENUM_METHODS = ['from', 'values', 'keys', 'isValid', 'search', 'toArray', 'assertValidValue']; public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Refactor Spatie enum method calls', [new CodeSample(<<<'CODE_SAMPLE' $value1 = SomeEnum::SOME_CONSTANT()->getValue(); $value2 = SomeEnum::SOME_CONSTANT()->value; $name1 = SomeEnum::SOME_CONSTANT()->getName(); $name2 = SomeEnum::SOME_CONSTANT()->name; CODE_SAMPLE , <<<'CODE_SAMPLE' $value1 = SomeEnum::SOME_CONSTANT->value; $value2 = SomeEnum::SOME_CONSTANT->value; $name1 = SomeEnum::SOME_CONSTANT->name; $name2 = SomeEnum::SOME_CONSTANT->name; CODE_SAMPLE )]); } /** * @return array<class-string<Node>> */ public function getNodeTypes() : array { return [MethodCall::class, StaticCall::class]; } /** * @param MethodCall|StaticCall $node */ public function refactor(Node $node) : ?Node { if ($node->name instanceof Expr) { return null; } $enumCaseName = $this->getName($node->name); if ($enumCaseName === null) { return null; } if ($this->shouldOmitEnumCase($enumCaseName)) { return null; } if ($node instanceof MethodCall) { return $this->refactorMethodCall($node, $enumCaseName); } if (!$this->isObjectType($node->class, new ObjectType(self::SPATIE_FQN))) { return null; } $className = $this->getName($node->class); if (!\is_string($className)) { return null; } $constantName = \strtoupper($enumCaseName); return $this->nodeFactory->createClassConstFetch($className, $constantName); } public function provideMinPhpVersion() : int { return PhpVersionFeature::ENUM; } private function refactorGetterToMethodCall(MethodCall $methodCall, string $property) : ?PropertyFetch { if (!$methodCall->var instanceof StaticCall) { return null; } $staticCall = $methodCall->var; $className = $this->getName($staticCall->class); if ($className === null) { return null; } $enumCaseName = $this->getName($staticCall->name); if ($enumCaseName === null) { return null; } if ($this->shouldOmitEnumCase($enumCaseName)) { return null; } $upperCaseName = \strtoupper($enumCaseName); $classConstFetch = $this->nodeFactory->createClassConstFetch($className, $upperCaseName); return new PropertyFetch($classConstFetch, $property); } private function refactorMethodCall(MethodCall $methodCall, string $methodName) : ?\PhpParser\Node\Expr\PropertyFetch { if (!$this->isObjectType($methodCall->var, new ObjectType(self::SPATIE_FQN))) { return null; } if ($methodName === 'getName') { return $this->refactorGetterToMethodCall($methodCall, 'name'); } if ($methodName === 'label') { return $this->refactorGetterToMethodCall($methodCall, 'name'); } if ($methodName === 'getValue') { return $this->refactorGetterToMethodCall($methodCall, 'value'); } if ($methodName === 'value') { return $this->refactorGetterToMethodCall($methodCall, 'value'); } return null; } private function shouldOmitEnumCase(string $enumCaseName) : bool { return \in_array($enumCaseName, self::ENUM_METHODS, \true); } } Rector/ClassMethod/NewInInitializerRector.php 0000644 00000014642 15126701311 0015330 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\Rector\ClassMethod; use PhpParser\Node; use PhpParser\Node\Expr\BinaryOp\Coalesce; use PhpParser\Node\NullableType; use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; use PHPStan\Reflection\ClassReflection; use Rector\FamilyTree\NodeAnalyzer\ClassChildAnalyzer; use Rector\NodeManipulator\StmtsManipulator; use Rector\Php81\NodeAnalyzer\CoalesePropertyAssignMatcher; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; use Rector\ValueObject\MethodName; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php81\Rector\ClassMethod\NewInInitializerRector\NewInInitializerRectorTest */ final class NewInInitializerRector extends AbstractRector implements MinPhpVersionInterface { /** * @readonly * @var \Rector\Reflection\ReflectionResolver */ private $reflectionResolver; /** * @readonly * @var \Rector\FamilyTree\NodeAnalyzer\ClassChildAnalyzer */ private $classChildAnalyzer; /** * @readonly * @var \Rector\Php81\NodeAnalyzer\CoalesePropertyAssignMatcher */ private $coalesePropertyAssignMatcher; /** * @readonly * @var \Rector\NodeManipulator\StmtsManipulator */ private $stmtsManipulator; public function __construct(ReflectionResolver $reflectionResolver, ClassChildAnalyzer $classChildAnalyzer, CoalesePropertyAssignMatcher $coalesePropertyAssignMatcher, StmtsManipulator $stmtsManipulator) { $this->reflectionResolver = $reflectionResolver; $this->classChildAnalyzer = $classChildAnalyzer; $this->coalesePropertyAssignMatcher = $coalesePropertyAssignMatcher; $this->stmtsManipulator = $stmtsManipulator; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Replace property declaration of new state with direct new', [new CodeSample(<<<'CODE_SAMPLE' class SomeClass { private Logger $logger; public function __construct( ?Logger $logger = null, ) { $this->logger = $logger ?? new NullLogger; } } CODE_SAMPLE , <<<'CODE_SAMPLE' class SomeClass { public function __construct( private Logger $logger = new NullLogger, ) { } } CODE_SAMPLE )]); } /** * @return array<class-string<Node>> */ public function getNodeTypes() : array { return [Class_::class]; } /** * @param Class_ $node */ public function refactor(Node $node) : ?Node { if ($node->stmts === null || $node->stmts === []) { return null; } if ($node->isAbstract() || $node->isAnonymous()) { return null; } $constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); if (!$constructClassMethod instanceof ClassMethod) { return null; } $params = $this->resolveParams($constructClassMethod); if ($params === []) { return null; } $hasChanged = \false; // stmts variable defined to avoid unset overlap when used via array_slice() on // StmtsManipulator::isVariableUsedInNextStmt() // @see https://github.com/rectorphp/rector-src/pull/5968 // @see https://3v4l.org/eojhk $stmts = (array) $constructClassMethod->stmts; foreach ((array) $constructClassMethod->stmts as $key => $stmt) { foreach ($params as $param) { $paramName = $this->getName($param); $coalesce = $this->coalesePropertyAssignMatcher->matchCoalesceAssignsToLocalPropertyNamed($stmt, $paramName); if (!$coalesce instanceof Coalesce) { continue; } if ($this->stmtsManipulator->isVariableUsedInNextStmt($stmts, $key + 1, $paramName)) { continue; } /** @var NullableType $currentParamType */ $currentParamType = $param->type; $param->type = $currentParamType->type; $param->default = $coalesce->right; unset($constructClassMethod->stmts[$key]); $this->processPropertyPromotion($node, $param, $paramName); $hasChanged = \true; } } if ($hasChanged) { return $node; } return null; } public function provideMinPhpVersion() : int { return PhpVersionFeature::NEW_INITIALIZERS; } /** * @return Param[] */ private function resolveParams(ClassMethod $classMethod) : array { $params = $this->matchConstructorParams($classMethod); if ($params === []) { return []; } if ($this->isOverrideAbstractMethod($classMethod)) { return []; } return $params; } private function isOverrideAbstractMethod(ClassMethod $classMethod) : bool { $classReflection = $this->reflectionResolver->resolveClassReflection($classMethod); $methodName = $this->nodeNameResolver->getName($classMethod); return $classReflection instanceof ClassReflection && $this->classChildAnalyzer->hasAbstractParentClassMethod($classReflection, $methodName); } private function processPropertyPromotion(Class_ $class, Param $param, string $paramName) : void { foreach ($class->stmts as $key => $stmt) { if (!$stmt instanceof Property) { continue; } $property = $stmt; if (!$this->isName($stmt, $paramName)) { continue; } $param->flags = $property->flags; $param->attrGroups = \array_merge($property->attrGroups, $param->attrGroups); unset($class->stmts[$key]); } } /** * @return Param[] */ private function matchConstructorParams(ClassMethod $classMethod) : array { // skip empty constructor assigns, as we need those here if ($classMethod->stmts === null || $classMethod->stmts === []) { return []; } return \array_filter($classMethod->params, static function (Param $param) : bool { return $param->type instanceof NullableType; }); } } Rector/FuncCall/NullToStrictStringFuncCallArgRector.php 0000644 00000023157 15126701311 0017225 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\Rector\FuncCall; use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Cast\String_ as CastString_; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Identifier; use PhpParser\Node\Scalar\Encapsed; use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\Native\NativeFunctionReflection; use PHPStan\Reflection\Native\NativeParameterWithPhpDocsReflection; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Type\ErrorType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\UnionType; use Rector\NodeAnalyzer\ArgsAnalyzer; use Rector\NodeAnalyzer\PropertyFetchAnalyzer; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\PHPStan\ParametersAcceptorSelectorVariantsWrapper; use Rector\Php81\Enum\NameNullToStrictNullFunctionMap; use Rector\PhpParser\Node\Value\ValueResolver; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector\NullToStrictStringFuncCallArgRectorTest */ final class NullToStrictStringFuncCallArgRector extends AbstractRector implements MinPhpVersionInterface { /** * @readonly * @var \Rector\Reflection\ReflectionResolver */ private $reflectionResolver; /** * @readonly * @var \Rector\NodeAnalyzer\ArgsAnalyzer */ private $argsAnalyzer; /** * @readonly * @var \Rector\NodeAnalyzer\PropertyFetchAnalyzer */ private $propertyFetchAnalyzer; /** * @readonly * @var \Rector\PhpParser\Node\Value\ValueResolver */ private $valueResolver; public function __construct(ReflectionResolver $reflectionResolver, ArgsAnalyzer $argsAnalyzer, PropertyFetchAnalyzer $propertyFetchAnalyzer, ValueResolver $valueResolver) { $this->reflectionResolver = $reflectionResolver; $this->argsAnalyzer = $argsAnalyzer; $this->propertyFetchAnalyzer = $propertyFetchAnalyzer; $this->valueResolver = $valueResolver; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Change null to strict string defined function call args', [new CodeSample(<<<'CODE_SAMPLE' class SomeClass { public function run() { preg_split("#a#", null); } } CODE_SAMPLE , <<<'CODE_SAMPLE' class SomeClass { public function run() { preg_split("#a#", ''); } } CODE_SAMPLE )]); } /** * @return array<class-string<Node>> */ public function getNodeTypes() : array { return [FuncCall::class]; } /** * @param FuncCall $node */ public function refactor(Node $node) : ?Node { if ($this->shouldSkip($node)) { return null; } $scope = $node->getAttribute(AttributeKey::SCOPE); if (!$scope instanceof Scope) { return null; } $args = $node->getArgs(); $positions = $this->argsAnalyzer->hasNamedArg($args) ? $this->resolveNamedPositions($node, $args) : $this->resolveOriginalPositions($node, $scope); if ($positions === []) { return null; } $classReflection = $scope->getClassReflection(); $isTrait = $classReflection instanceof ClassReflection && $classReflection->isTrait(); $functionReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node); if (!$functionReflection instanceof FunctionReflection) { return null; } $parametersAcceptor = ParametersAcceptorSelectorVariantsWrapper::select($functionReflection, $node, $scope); $isChanged = \false; foreach ($positions as $position) { $result = $this->processNullToStrictStringOnNodePosition($node, $args, $position, $isTrait, $scope, $parametersAcceptor); if ($result instanceof Node) { $node = $result; $isChanged = \true; } } if ($isChanged) { return $node; } return null; } public function provideMinPhpVersion() : int { return PhpVersionFeature::DEPRECATE_NULL_ARG_IN_STRING_FUNCTION; } /** * @param Arg[] $args * @return int[]|string[] */ private function resolveNamedPositions(FuncCall $funcCall, array $args) : array { $functionName = $this->nodeNameResolver->getName($funcCall); $argNames = NameNullToStrictNullFunctionMap::FUNCTION_TO_PARAM_NAMES[$functionName]; $positions = []; foreach ($args as $position => $arg) { if (!$arg->name instanceof Identifier) { continue; } if (!$this->nodeNameResolver->isNames($arg->name, $argNames)) { continue; } $positions[] = $position; } return $positions; } /** * @param Arg[] $args * @param int|string $position */ private function processNullToStrictStringOnNodePosition(FuncCall $funcCall, array $args, $position, bool $isTrait, Scope $scope, ParametersAcceptor $parametersAcceptor) : ?FuncCall { if (!isset($args[$position])) { return null; } $argValue = $args[$position]->value; if ($argValue instanceof ConstFetch && $this->valueResolver->isNull($argValue)) { $args[$position]->value = new String_(''); $funcCall->args = $args; return $funcCall; } $type = $this->nodeTypeResolver->getType($argValue); if ($type->isString()->yes()) { return null; } $nativeType = $this->nodeTypeResolver->getNativeType($argValue); if ($nativeType->isString()->yes()) { return null; } if ($this->shouldSkipType($type)) { return null; } if ($argValue instanceof Encapsed) { return null; } if ($this->isAnErrorType($argValue, $nativeType, $scope)) { return null; } if ($this->shouldSkipTrait($argValue, $type, $isTrait)) { return null; } $parameter = $parametersAcceptor->getParameters()[$position] ?? null; if ($parameter instanceof NativeParameterWithPhpDocsReflection && $parameter->getType() instanceof UnionType) { $parameterType = $parameter->getType(); if (!$this->isValidUnionType($parameterType)) { return null; } } $args[$position]->value = new CastString_($argValue); $funcCall->args = $args; return $funcCall; } private function isValidUnionType(Type $type) : bool { if (!$type instanceof UnionType) { return \false; } foreach ($type->getTypes() as $childType) { if ($childType->isString()->yes()) { continue; } if ($childType->isNull()->yes()) { continue; } return \false; } return \true; } private function shouldSkipType(Type $type) : bool { return !$type instanceof MixedType && !$type instanceof NullType && !$this->isValidUnionType($type); } private function shouldSkipTrait(Expr $expr, Type $type, bool $isTrait) : bool { if (!$type instanceof MixedType) { return \false; } if (!$isTrait) { return \false; } if ($type->isExplicitMixed()) { return \false; } if (!$expr instanceof MethodCall) { return $this->propertyFetchAnalyzer->isLocalPropertyFetch($expr); } return \true; } private function isAnErrorType(Expr $expr, Type $type, Scope $scope) : bool { if ($type instanceof ErrorType) { return \true; } $parentScope = $scope->getParentScope(); if ($parentScope instanceof Scope) { return $parentScope->getType($expr) instanceof ErrorType; } return \false; } /** * @return int[]|string[] */ private function resolveOriginalPositions(FuncCall $funcCall, Scope $scope) : array { $functionReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($funcCall); if (!$functionReflection instanceof NativeFunctionReflection) { return []; } $parametersAcceptor = ParametersAcceptorSelectorVariantsWrapper::select($functionReflection, $funcCall, $scope); $functionName = $functionReflection->getName(); $argNames = NameNullToStrictNullFunctionMap::FUNCTION_TO_PARAM_NAMES[$functionName]; $positions = []; foreach ($parametersAcceptor->getParameters() as $position => $parameterReflection) { if (\in_array($parameterReflection->getName(), $argNames, \true)) { $positions[] = $position; } } return $positions; } private function shouldSkip(FuncCall $funcCall) : bool { $functionNames = \array_keys(NameNullToStrictNullFunctionMap::FUNCTION_TO_PARAM_NAMES); if (!$this->nodeNameResolver->isNames($funcCall, $functionNames)) { return \true; } return $funcCall->isFirstClassCallable(); } } Rector/Array_/FirstClassCallableRector.php 0000644 00000012550 15126701311 0014604 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\Rector\Array_; use PhpParser\Node; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\Property; use PhpParser\Node\VariadicPlaceholder; use PhpParser\NodeTraverser; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; use Rector\NodeCollector\NodeAnalyzer\ArrayCallableMethodMatcher; use Rector\NodeCollector\ValueObject\ArrayCallable; use Rector\Rector\AbstractScopeAwareRector; use Rector\Reflection\ReflectionResolver; use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; use Rector\ValueObject\PhpVersion; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php81\Rector\Array_\FirstClassCallableRector\FirstClassCallableRectorTest */ final class FirstClassCallableRector extends AbstractScopeAwareRector implements MinPhpVersionInterface { /** * @readonly * @var \Rector\NodeCollector\NodeAnalyzer\ArrayCallableMethodMatcher */ private $arrayCallableMethodMatcher; /** * @readonly * @var \PHPStan\Reflection\ReflectionProvider */ private $reflectionProvider; /** * @readonly * @var \Rector\Reflection\ReflectionResolver */ private $reflectionResolver; public function __construct(ArrayCallableMethodMatcher $arrayCallableMethodMatcher, ReflectionProvider $reflectionProvider, ReflectionResolver $reflectionResolver) { $this->arrayCallableMethodMatcher = $arrayCallableMethodMatcher; $this->reflectionProvider = $reflectionProvider; $this->reflectionResolver = $reflectionResolver; } public function getRuleDefinition() : RuleDefinition { // see RFC https://wiki.php.net/rfc/first_class_callable_syntax return new RuleDefinition('Upgrade array callable to first class callable', [new CodeSample(<<<'CODE_SAMPLE' final class SomeClass { public function run() { $name = [$this, 'name']; } public function name() { } } CODE_SAMPLE , <<<'CODE_SAMPLE' final class SomeClass { public function run() { $name = $this->name(...); } public function name() { } } CODE_SAMPLE )]); } /** * @return array<class-string<Node>> */ public function getNodeTypes() : array { return [Property::class, ClassConst::class, Array_::class]; } /** * @param Property|ClassConst|Array_ $node * @return int|null|\PhpParser\Node\Expr\StaticCall|\PhpParser\Node\Expr\MethodCall */ public function refactorWithScope(Node $node, Scope $scope) { if ($node instanceof Property || $node instanceof ClassConst) { return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN; } $arrayCallable = $this->arrayCallableMethodMatcher->match($node, $scope); if (!$arrayCallable instanceof ArrayCallable) { return null; } $callerExpr = $arrayCallable->getCallerExpr(); if (!$callerExpr instanceof Variable && !$callerExpr instanceof PropertyFetch && !$callerExpr instanceof ClassConstFetch) { return null; } $args = [new VariadicPlaceholder()]; if ($callerExpr instanceof ClassConstFetch) { $type = $this->getType($callerExpr->class); if ($type instanceof FullyQualifiedObjectType && $this->isNonStaticOtherObject($type, $arrayCallable, $scope)) { return null; } return new StaticCall($callerExpr->class, $arrayCallable->getMethod(), $args); } $methodName = $arrayCallable->getMethod(); $methodCall = new MethodCall($callerExpr, $methodName, $args); $classReflection = $this->reflectionResolver->resolveClassReflectionSourceObject($methodCall); if ($classReflection instanceof ClassReflection && $classReflection->hasNativeMethod($methodName)) { $method = $classReflection->getNativeMethod($methodName); if (!$method->isPublic()) { return null; } } return $methodCall; } public function provideMinPhpVersion() : int { return PhpVersion::PHP_81; } private function isNonStaticOtherObject(FullyQualifiedObjectType $fullyQualifiedObjectType, ArrayCallable $arrayCallable, Scope $scope) : bool { $classReflection = $scope->getClassReflection(); if ($classReflection instanceof ClassReflection && $classReflection->getName() === $fullyQualifiedObjectType->getClassName()) { return \false; } $arrayClassReflection = $this->reflectionProvider->getClass($arrayCallable->getClass()); // we're unable to find it if (!$arrayClassReflection->hasMethod($arrayCallable->getMethod())) { return \false; } $extendedMethodReflection = $arrayClassReflection->getMethod($arrayCallable->getMethod(), $scope); if (!$extendedMethodReflection->isStatic()) { return \true; } return !$extendedMethodReflection->isPublic(); } } Rector/Property/ReadOnlyPropertyRector.php 0000644 00000022764 15126701312 0015011 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\Rector\Property; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\Clone_; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; use PhpParser\NodeTraverser; use PHPStan\Analyser\Scope; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Comments\NodeDocBlock\DocBlockUpdater; use Rector\NodeAnalyzer\ParamAnalyzer; use Rector\NodeManipulator\PropertyFetchAssignManipulator; use Rector\NodeManipulator\PropertyManipulator; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Privatization\NodeManipulator\VisibilityManipulator; use Rector\Rector\AbstractScopeAwareRector; use Rector\ValueObject\MethodName; use Rector\ValueObject\PhpVersionFeature; use Rector\ValueObject\Visibility; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php81\Rector\Property\ReadOnlyPropertyRector\ReadOnlyPropertyRectorTest */ final class ReadOnlyPropertyRector extends AbstractScopeAwareRector implements MinPhpVersionInterface { /** * @readonly * @var \Rector\NodeManipulator\PropertyManipulator */ private $propertyManipulator; /** * @readonly * @var \Rector\NodeManipulator\PropertyFetchAssignManipulator */ private $propertyFetchAssignManipulator; /** * @readonly * @var \Rector\NodeAnalyzer\ParamAnalyzer */ private $paramAnalyzer; /** * @readonly * @var \Rector\Privatization\NodeManipulator\VisibilityManipulator */ private $visibilityManipulator; /** * @readonly * @var \Rector\PhpParser\Node\BetterNodeFinder */ private $betterNodeFinder; /** * @readonly * @var \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory */ private $phpDocInfoFactory; /** * @readonly * @var \Rector\Comments\NodeDocBlock\DocBlockUpdater */ private $docBlockUpdater; public function __construct(PropertyManipulator $propertyManipulator, PropertyFetchAssignManipulator $propertyFetchAssignManipulator, ParamAnalyzer $paramAnalyzer, VisibilityManipulator $visibilityManipulator, BetterNodeFinder $betterNodeFinder, PhpDocInfoFactory $phpDocInfoFactory, DocBlockUpdater $docBlockUpdater) { $this->propertyManipulator = $propertyManipulator; $this->propertyFetchAssignManipulator = $propertyFetchAssignManipulator; $this->paramAnalyzer = $paramAnalyzer; $this->visibilityManipulator = $visibilityManipulator; $this->betterNodeFinder = $betterNodeFinder; $this->phpDocInfoFactory = $phpDocInfoFactory; $this->docBlockUpdater = $docBlockUpdater; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Decorate read-only property with `readonly` attribute', [new CodeSample(<<<'CODE_SAMPLE' class SomeClass { public function __construct( private string $name ) { } public function getName() { return $this->name; } } CODE_SAMPLE , <<<'CODE_SAMPLE' class SomeClass { public function __construct( private readonly string $name ) { } public function getName() { return $this->name; } } CODE_SAMPLE )]); } /** * @return array<class-string<Node>> */ public function getNodeTypes() : array { return [Class_::class]; } /** * @param Class_ $node */ public function refactorWithScope(Node $node, Scope $scope) : ?Node { if ($this->shouldSkip($node)) { return null; } $hasChanged = \false; $classMethod = $node->getMethod(MethodName::CONSTRUCT); if ($classMethod instanceof ClassMethod) { foreach ($classMethod->params as $param) { $justChanged = $this->refactorParam($node, $classMethod, $param, $scope); // different variable to ensure $hasRemoved not replaced if ($justChanged instanceof Param) { $hasChanged = \true; } } } foreach ($node->getProperties() as $property) { $changedProperty = $this->refactorProperty($node, $property, $scope); if ($changedProperty instanceof Property) { $hasChanged = \true; } } if ($hasChanged) { return $node; } return null; } public function provideMinPhpVersion() : int { return PhpVersionFeature::READONLY_PROPERTY; } private function refactorProperty(Class_ $class, Property $property, Scope $scope) : ?Property { // 1. is property read-only? if ($property->isReadonly()) { return null; } if ($property->props[0]->default instanceof Expr) { return null; } if ($property->type === null) { return null; } if ($property->isStatic()) { return null; } if (!$this->visibilityManipulator->hasVisibility($property, Visibility::PRIVATE)) { return null; } if ($this->propertyManipulator->isPropertyChangeableExceptConstructor($class, $property, $scope)) { return null; } if ($this->propertyFetchAssignManipulator->isAssignedMultipleTimesInConstructor($class, $property)) { return null; } $this->visibilityManipulator->makeReadonly($property); $attributeGroups = $property->attrGroups; if ($attributeGroups !== []) { $property->setAttribute(AttributeKey::ORIGINAL_NODE, null); } $this->removeReadOnlyDoc($property); return $property; } /** * @param \PhpParser\Node\Stmt\Property|\PhpParser\Node\Param $node */ private function removeReadOnlyDoc($node) : void { $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); $readonlyDoc = $phpDocInfo->getByName('readonly'); if (!$readonlyDoc instanceof PhpDocTagNode) { return; } if (!$readonlyDoc->value instanceof GenericTagValueNode) { return; } if ($readonlyDoc->value->value !== '') { return; } $phpDocInfo->removeByName('readonly'); $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); } private function refactorParam(Class_ $class, ClassMethod $classMethod, Param $param, Scope $scope) : ?\PhpParser\Node\Param { if (!$this->visibilityManipulator->hasVisibility($param, Visibility::PRIVATE)) { return null; } if ($param->type === null) { return null; } // early check not property promotion and already readonly if ($param->flags === 0 || $this->visibilityManipulator->isReadonly($param)) { return null; } if ($this->propertyManipulator->isPropertyChangeableExceptConstructor($class, $param, $scope)) { return null; } if ($this->paramAnalyzer->isParamReassign($classMethod, $param)) { return null; } if ($this->isPromotedPropertyAssigned($class, $param)) { return null; } if ($param->attrGroups !== []) { $param->setAttribute(AttributeKey::ORIGINAL_NODE, null); } $this->visibilityManipulator->makeReadonly($param); $this->removeReadOnlyDoc($param); return $param; } private function isPromotedPropertyAssigned(Class_ $class, Param $param) : bool { $constructClassMethod = $class->getMethod(MethodName::CONSTRUCT); if (!$constructClassMethod instanceof ClassMethod) { return \false; } if ($param->flags === 0) { return \false; } $propertyFetch = new PropertyFetch(new Variable('this'), $this->getName($param)); $isAssigned = \false; $this->traverseNodesWithCallable($class->stmts, function (Node $node) use($propertyFetch, &$isAssigned) : ?int { if (!$node instanceof Assign) { return null; } if ($this->nodeComparator->areNodesEqual($propertyFetch, $node->var)) { $isAssigned = \true; return NodeTraverser::STOP_TRAVERSAL; } return null; }); return $isAssigned; } private function shouldSkip(Class_ $class) : bool { if ($class->isReadonly()) { return \true; } // not safe if ($class->getTraitUses() !== []) { return \true; } // skip "clone $this" cases, as can create unexpected write to local constructor property return $this->hasCloneThis($class); } private function hasCloneThis(Class_ $class) : bool { return (bool) $this->betterNodeFinder->findFirst($class, function (Node $node) : bool { if (!$node instanceof Clone_) { return \false; } if (!$node->expr instanceof Variable) { return \false; } return $this->isName($node->expr, 'this'); }); } } Enum/AttributeName.php 0000644 00000000613 15126701312 0010723 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\Enum; final class AttributeName { /** * Available since PHP 8.1 * @see https://php.watch/versions/8.1/ReturnTypeWillChange * @var string */ public const RETURN_TYPE_WILL_CHANGE = 'ReturnTypeWillChange'; /** * @var string */ public const ALLOW_DYNAMIC_PROPERTIES = 'AllowDynamicProperties'; } Enum/NameNullToStrictNullFunctionMap.php 0000644 00000025373 15126701312 0014377 0 ustar 00 <?php declare (strict_types=1); namespace Rector\Php81\Enum; final class NameNullToStrictNullFunctionMap { /** * @var array<string, string[]> */ public const FUNCTION_TO_PARAM_NAMES = ['preg_split' => ['subject'], 'preg_match' => ['subject'], 'preg_match_all' => ['subject'], 'preg_filter' => ['replacement', 'subject'], 'preg_replace' => ['replacement', 'subject'], 'preg_replace_callback' => ['subject'], 'preg_replace_callback_array' => ['subject'], 'explode' => ['string'], 'strlen' => ['string'], 'str_contains' => ['haystack', 'needle'], 'strtotime' => ['datetime'], 'str_replace' => ['subject'], 'substr_replace' => ['string', 'replace'], 'str_ireplace' => ['search', 'replace', 'subject'], 'substr' => ['string'], 'str_starts_with' => ['haystack', 'needle'], 'strtoupper' => ['string'], 'strtolower' => ['string'], 'strpos' => ['haystack', 'needle'], 'stripos' => ['haystack', 'needle'], 'json_decode' => ['json'], 'urlencode' => ['string'], 'urldecode' => ['string'], 'rawurlencode' => ['string'], 'rawurldecode' => ['string'], 'base64_encode' => ['string'], 'base64_decode' => ['string'], 'utf8_encode' => ['string'], 'utf8_decode' => ['string'], 'bin2hex' => ['string'], 'hex2bin' => ['string'], 'hexdec' => ['hex_string'], 'octdec' => ['octal_string'], 'base_convert' => ['num'], 'htmlspecialchars' => ['string'], 'htmlspecialchars_decode' => ['string'], 'html_entity_decode' => ['string'], 'htmlentities' => ['string'], 'addslashes' => ['string'], 'addcslashes' => ['string', 'characters'], 'stripslashes' => ['string'], 'stripcslashes' => ['string'], 'quotemeta' => ['string'], 'quoted_printable_decode' => ['string'], 'quoted_printable_encode' => ['string'], 'escapeshellarg' => ['arg'], 'curl_escape' => ['string'], 'curl_unescape' => ['string'], 'convert_uuencode' => ['string'], 'setcookie' => ['value', 'path', 'domain'], 'setrawcookie' => ['value', 'path', 'domain'], 'zlib_encode' => ['data'], 'gzdeflate' => ['data'], 'gzencode' => ['data'], 'gzcompress' => ['data'], 'gzwrite' => ['data'], 'gzputs' => ['data'], 'deflate_add' => ['data'], 'inflate_add' => ['data'], 'unpack' => ['format', 'string'], 'iconv_mime_encode' => ['field_name', 'field_value'], 'iconv_mime_decode' => ['string'], 'iconv' => ['from_encoding', 'to_encoding', 'string'], 'sodium_bin2hex' => ['string'], 'sodium_hex2bin' => ['string', 'ignore'], 'sodium_bin2base64' => ['string'], 'sodium_base642bin' => ['string', 'ignore'], 'mb_detect_encoding' => ['string'], 'mb_encode_mimeheader' => ['string'], 'mb_decode_mimeheader' => ['string'], 'mb_encode_numericentity' => ['string'], 'mb_decode_numericentity' => ['string'], 'transliterator_transliterate' => ['string'], 'mysqli_real_escape_string' => ['string'], 'mysqli_escape_string' => ['string'], 'pg_escape_bytea' => ['string'], 'pg_escape_literal' => ['string'], 'pg_escape_string' => ['string'], 'pg_unescape_bytea' => ['string'], 'ucfirst' => ['string'], 'lcfirst' => ['string'], 'ucwords' => ['string'], 'trim' => ['string'], 'ltrim' => ['string'], 'rtrim' => ['string'], 'chop' => ['string'], 'str_rot13' => ['string'], 'str_shuffle' => ['string'], 'substr_count' => ['haystack', 'needle'], 'strcoll' => ['string1', 'string2'], 'str_split' => ['string'], 'chunk_split' => ['string'], 'wordwrap' => ['string'], 'strrev' => ['string'], 'str_repeat' => ['string'], 'str_pad' => ['string'], 'nl2br' => ['string'], 'strip_tags' => ['string'], 'hebrev' => ['string'], 'iconv_substr' => ['string'], 'mb_strtoupper' => ['string'], 'mb_strtolower' => ['string'], 'mb_convert_case' => ['string'], 'mb_convert_kana' => ['string'], 'mb_convert_encoding' => ['string'], 'mb_scrub' => ['string'], 'mb_substr' => ['string'], 'mb_substr_count' => ['haystack', 'needle'], 'mb_str_split' => ['string'], 'mb_split' => ['pattern', 'string'], 'sodium_pad' => ['string'], 'grapheme_substr' => ['string'], 'strrpos' => ['haystack', 'needle'], 'strripos' => ['haystack', 'needle'], 'iconv_strpos' => ['haystack', 'needle'], 'iconv_strrpos' => ['haystack', 'needle'], 'mb_strpos' => ['haystack', 'needle'], 'mb_strrpos' => ['haystack', 'needle'], 'mb_stripos' => ['haystack', 'needle'], 'mb_strripos' => ['haystack', 'needle'], 'grapheme_extract' => ['haystack'], 'grapheme_strpos' => ['haystack', 'needle'], 'grapheme_strrpos' => ['haystack', 'needle'], 'grapheme_stripos' => ['haystack', 'needle'], 'grapheme_strripos' => ['haystack', 'needle'], 'strcmp' => ['string1', 'string2'], 'strncmp' => ['string1', 'string2'], 'strcasecmp' => ['string1', 'string2'], 'strncasecmp' => ['string1', 'string2'], 'strnatcmp' => ['string1', 'string2'], 'strnatcasecmp' => ['string1', 'string2'], 'substr_compare' => ['haystack', 'needle'], 'str_ends_with' => ['haystack', 'needle'], 'collator_compare' => ['string1', 'string2'], 'collator_get_sort_key' => ['string'], 'metaphone' => ['string'], 'soundex' => ['string'], 'levenshtein' => ['string1', 'string2'], 'similar_text' => ['string1', 'string2'], 'sodium_compare' => ['string1', 'string2'], 'sodium_memcmp' => ['string1', 'string2'], 'strstr' => ['haystack', 'needle'], 'strchr' => ['haystack', 'needle'], 'stristr' => ['haystack', 'needle'], 'strrchr' => ['haystack', 'needle'], 'strpbrk' => ['string', 'characters'], 'strspn' => ['string', 'characters'], 'strcspn' => ['string', 'characters'], 'strtr' => ['string'], 'strtok' => ['string'], 'str_word_count' => ['string'], 'count_chars' => ['string'], 'iconv_strlen' => ['string'], 'mb_strlen' => ['string'], 'mb_strstr' => ['haystack', 'needle'], 'mb_strrchr' => ['haystack', 'needle'], 'mb_stristr' => ['haystack', 'needle'], 'mb_strrichr' => ['haystack', 'needle'], 'mb_strcut' => ['string'], 'mb_strwidth' => ['string'], 'mb_strimwidth' => ['string', 'trim_marker'], 'grapheme_strlen' => ['string'], 'grapheme_strstr' => ['haystack', 'needle'], 'grapheme_stristr' => ['haystack', 'needle'], 'preg_quote' => ['str'], 'mb_ereg' => ['pattern', 'string'], 'mb_eregi' => ['pattern', 'string'], 'mb_ereg_replace' => ['pattern', 'replacement', 'string'], 'mb_eregi_replace' => ['pattern', 'replacement', 'string'], 'mb_ereg_replace_callback' => ['pattern', 'string'], 'mb_ereg_match' => ['pattern', 'string'], 'mb_ereg_search_init' => ['string'], 'normalizer_normalize' => ['string'], 'normalizer_is_normalized' => ['string'], 'normalizer_get_raw_decomposition' => ['string'], 'numfmt_parse' => ['string'], 'hash' => ['algo', 'data'], 'hash_hmac' => ['algo', 'data', 'key'], 'hash_update' => ['data'], 'hash_pbkdf2' => ['algo', 'password', 'salt'], 'crc32' => ['string'], 'md5' => ['string'], 'sha1' => ['string'], 'crypt' => ['string', 'salt'], 'basename' => ['path'], 'dirname' => ['path'], 'pathinfo' => ['path'], 'sscanf' => ['string'], 'fwrite' => ['data'], 'fputs' => ['data'], 'output_add_rewrite_var' => ['name', 'value'], 'parse_url' => ['url'], 'parse_str' => ['string'], 'mb_parse_str' => ['string'], 'parse_ini_string' => ['ini_string'], 'locale_accept_from_http' => ['header'], 'msgfmt_parse' => ['string'], 'msgfmt_parse_message' => ['locale', 'pattern', 'message'], 'str_getcsv' => ['string'], 'fgetcsv' => ['escape'], 'fputcsv' => ['escape'], 'password_hash' => ['password'], 'password_verify' => ['password', 'hash'], 'bcadd' => ['num1', 'num2'], 'bcsub' => ['num1', 'num2'], 'bcmul' => ['num1', 'num2'], 'bcdiv' => ['num1', 'num2'], 'bcmod' => ['num1', 'num2'], 'bcpow' => ['num', 'exponent'], 'bcpowmod' => ['num', 'exponent', 'modulus'], 'bcsqrt' => ['num'], 'bccomp' => ['num1', 'num2'], 'simplexml_load_string' => ['data'], 'xml_parse' => ['data'], 'xml_parse_into_struct' => ['data'], 'xml_parser_create_ns' => ['separator'], 'xmlwriter_set_indent_string' => ['indentation'], 'xmlwriter_write_attribute' => ['name', 'value'], 'xmlwriter_write_attribute_ns' => ['value'], 'xmlwriter_write_pi' => ['target', 'content'], 'xmlwriter_write_cdata' => ['content'], 'xmlwriter_text' => ['content'], 'xmlwriter_write_raw' => ['content'], 'xmlwriter_write_comment' => ['content'], 'xmlwriter_write_dtd' => ['name'], 'xmlwriter_write_dtd_element' => ['name', 'content'], 'xmlwriter_write_dtd_attlist' => ['name', 'content'], 'xmlwriter_write_dtd_entity' => ['name', 'content'], 'sodium_crypto_aead_aes256gcm_encrypt' => ['message', 'additional_data', 'nonce', 'key'], 'sodium_crypto_aead_aes256gcm_decrypt' => ['ciphertext', 'additional_data', 'nonce', 'key'], 'sodium_crypto_aead_chacha20poly1305_encrypt' => ['message', 'additional_data', 'nonce', 'key'], 'sodium_crypto_aead_chacha20poly1305_decrypt' => ['ciphertext', 'additional_data', 'nonce', 'key'], 'sodium_crypto_aead_chacha20poly1305_ietf_encrypt' => ['message', 'additional_data', 'nonce', 'key'], 'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['ciphertext', 'additional_data', 'nonce', 'key'], 'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' => ['message', 'additional_data', 'nonce', 'key'], 'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt' => ['ciphertext', 'additional_data', 'nonce', 'key'], 'sodium_crypto_auth' => ['message', 'key'], 'sodium_crypto_auth_verify' => ['mac', 'message', 'key'], 'sodium_crypto_box' => ['message', 'nonce', 'key_pair'], 'sodium_crypto_box_seal' => ['message', 'public_key'], 'sodium_crypto_generichash' => ['message'], 'sodium_crypto_generichash_update' => ['message'], 'sodium_crypto_secretbox' => ['message', 'nonce', 'key'], 'sodium_crypto_secretstream_xchacha20poly1305_push' => ['message'], 'sodium_crypto_secretstream_xchacha20poly1305_pull' => ['ciphertext'], 'sodium_crypto_shorthash' => ['message', 'key'], 'sodium_crypto_sign' => ['message', 'secret_key'], 'sodium_crypto_sign_detached' => ['message'], 'sodium_crypto_sign_open' => ['signed_message', 'public_key'], 'sodium_crypto_sign_verify_detached' => ['signature', 'message', 'public_key'], 'sodium_crypto_stream_xor' => ['message', 'nonce', 'key'], 'sodium_crypto_stream_xchacha20_xor' => ['message', 'nonce', 'key'], 'imagechar' => ['char'], 'imagecharup' => ['char'], 'imageftbbox' => ['string'], 'imagefttext' => ['text'], 'imagestring' => ['string'], 'imagestringup' => ['string'], 'imagettfbbox' => ['string'], 'imagettftext' => ['text'], 'pspell_add_to_personal' => ['word'], 'pspell_add_to_session' => ['word'], 'pspell_check' => ['word'], 'pspell_config_create' => ['language', 'spelling', 'jargon', 'encoding'], 'pspell_new' => ['spelling', 'jargon', 'encoding'], 'pspell_new_personal' => ['spelling', 'jargon', 'encoding'], 'pspell_store_replacement' => ['correct'], 'pspell_suggest' => ['word'], 'stream_get_line' => ['ending'], 'stream_socket_sendto' => ['data'], 'socket_sendto' => ['data'], 'socket_write' => ['data'], 'socket_send' => ['data'], 'mail' => ['to', 'subject', 'message'], 'mb_send_mail' => ['to', 'subject', 'message'], 'ctype_alnum' => ['text'], 'ctype_alpha' => ['text'], 'ctype_cntrl' => ['text'], 'ctype_digit' => ['text'], 'ctype_graph' => ['text'], 'ctype_lower' => ['text'], 'ctype_print' => ['text'], 'ctype_punct' => ['text'], 'ctype_space' => ['text'], 'ctype_upper' => ['text'], 'ctype_xdigit' => ['text'], 'uniqid' => ['prefix']]; }
| ver. 1.6 |
Github
|
.
| PHP 8.2.30 | ??????????? ?????????: 0 |
proxy
|
phpinfo
|
???????????