diff --git a/config/set/php81.php b/config/set/php81.php index d73299ea7da..5a08ab3fdfd 100644 --- a/config/set/php81.php +++ b/config/set/php81.php @@ -10,7 +10,6 @@ use Rector\Php81\Rector\Array_\ArrayToFirstClassCallableRector; use Rector\Php81\Rector\Class_\MyCLabsClassToEnumRector; use Rector\Php81\Rector\Class_\SpatieEnumClassToEnumRector; -use Rector\Php81\Rector\ClassMethod\NewInInitializerRector; use Rector\Php81\Rector\FuncCall\NullToStrictIntPregSlitFuncCallLimitArgRector; use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector; use Rector\Php81\Rector\MethodCall\MyCLabsMethodCallToEnumConstRector; @@ -41,7 +40,5 @@ ClosureFromCallableToFirstClassCallableRector::class, FunctionFirstClassCallableRector::class, RemoveReflectionSetAccessibleCallsRector::class, - - NewInInitializerRector::class, ]); }; diff --git a/phpstan.neon b/phpstan.neon index 326a1c7045c..3bd4cf3b344 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -423,7 +423,9 @@ parameters: # deprecated rule - '#Rule Rector\\Php81\\Rector\\Array_\\FirstClassCallableRector must implements Rector\\VersionBonding\\Contract\\MinPhpVersionInterface#' - '#Register "Rector\\Php81\\Rector\\Array_\\FirstClassCallableRector" service to "php81\.php" config set#' + - '#Register "Rector\\Php81\\Rector\\ClassMethod\\NewInInitializerRector" service to "php81\.php" config set#' - '#Class "Rector\\CodingStyle\\Rector\\String_\\SymplifyQuoteEscapeRector" is missing @see annotation with test case class reference#' + - '#Class "Rector\\Php81\\Rector\\ClassMethod\\NewInInitializerRector" is missing @see annotation with test case class reference#' # BC layer for FileWithoutNamespace node - diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_class_const_fetch_arg.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_class_const_fetch_arg.php.inc deleted file mode 100644 index e4f7e19866c..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_class_const_fetch_arg.php.inc +++ /dev/null @@ -1,35 +0,0 @@ -dateTime = $dateTime ?? new DateTime(SomeValueObject::NOW); - } -} - -?> ------ - diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_const_fetch_arg.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_const_fetch_arg.php.inc deleted file mode 100644 index cf6a79f9754..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_const_fetch_arg.php.inc +++ /dev/null @@ -1,37 +0,0 @@ -dateTime = $dateTime ?? new DateTime(NOW); - } -} - -?> ------ - diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_non_dynamic_arg.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_non_dynamic_arg.php.inc deleted file mode 100644 index 770f8e03170..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_non_dynamic_arg.php.inc +++ /dev/null @@ -1,35 +0,0 @@ -dateTime = $dateTime ?? new DateTime('now', new DateTimeZone('Asia/Jakarta')); - } -} - -?> ------ - diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_non_dynamic_arg2.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_non_dynamic_arg2.php.inc deleted file mode 100644 index c78cd27296a..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/pass_non_dynamic_arg2.php.inc +++ /dev/null @@ -1,29 +0,0 @@ -logger = $logger ?? new NullLogger(['a' => 'b']); - } -} - -?> ------ - 'b'])) - { - } -} - -?> diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/property_with_attributes.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/property_with_attributes.php.inc deleted file mode 100644 index 5ca8fc1980f..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/property_with_attributes.php.inc +++ /dev/null @@ -1,43 +0,0 @@ -id = $id ?? new Ulid(); - } -} - -?> ------ - diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_abstract.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_abstract.php.inc deleted file mode 100644 index b4224a81e76..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_abstract.php.inc +++ /dev/null @@ -1,14 +0,0 @@ -logger = $logger ?? new NullLogger; - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_assign_coalesce_with_new_in_inner_class.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_assign_coalesce_with_new_in_inner_class.php.inc deleted file mode 100644 index 2d7f298018b..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_assign_coalesce_with_new_in_inner_class.php.inc +++ /dev/null @@ -1,23 +0,0 @@ -logger = $logger ?? new NullLogger; - } - }; - - $this->logger = $logger; - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_dynamic_new.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_dynamic_new.php.inc deleted file mode 100644 index 6acb207f933..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_dynamic_new.php.inc +++ /dev/null @@ -1,17 +0,0 @@ -stdClass = $stdClass ?? new $fallbackClass; - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_if_no_default_null.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_if_no_default_null.php.inc deleted file mode 100644 index 69cbaf4ca7a..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_if_no_default_null.php.inc +++ /dev/null @@ -1,17 +0,0 @@ -logger = $logger ?? new NullLogger(); - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_override_abstract_method.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_override_abstract_method.php.inc deleted file mode 100644 index 081de648a91..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_override_abstract_method.php.inc +++ /dev/null @@ -1,17 +0,0 @@ -dateTime = $dateTime ?? new DateTime('now'); - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_override_interface_method.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_override_interface_method.php.inc deleted file mode 100644 index d5a927051a8..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_override_interface_method.php.inc +++ /dev/null @@ -1,17 +0,0 @@ -dateTime = $dateTime ?? new DateTime('now'); - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_pass_class_const_fetch_arg_dynamic_class.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_pass_class_const_fetch_arg_dynamic_class.php.inc deleted file mode 100644 index 27e4476b283..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_pass_class_const_fetch_arg_dynamic_class.php.inc +++ /dev/null @@ -1,18 +0,0 @@ -dateTime = $dateTime ?? new DateTime($class::NOW); - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_pass_dynamic_arg2.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_pass_dynamic_arg2.php.inc deleted file mode 100644 index 372c1ed83cd..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_pass_dynamic_arg2.php.inc +++ /dev/null @@ -1,17 +0,0 @@ -dateTime = $dateTime ?? new DateTime('now', new DateTimeZone($timezone)); - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_pass_dynamic_arg3.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_pass_dynamic_arg3.php.inc deleted file mode 100644 index 641a3f405ca..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_pass_dynamic_arg3.php.inc +++ /dev/null @@ -1,15 +0,0 @@ -logger = $logger ?? new NullLogger(['a' => $x]); - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_static_call_null_coalesce_operator.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_static_call_null_coalesce_operator.php.inc deleted file mode 100644 index 055535b8e3d..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_static_call_null_coalesce_operator.php.inc +++ /dev/null @@ -1,13 +0,0 @@ -foo ??= InstantiableViaNamedConstructor::make(100); - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_static_call_right_coalesce.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_static_call_right_coalesce.php.inc deleted file mode 100644 index b39541a2ed6..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_static_call_right_coalesce.php.inc +++ /dev/null @@ -1,16 +0,0 @@ -foo = $foo ?? InstantiableViaNamedConstructor::make(100); - } - -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_variable_used_next_stmt.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_variable_used_next_stmt.php.inc deleted file mode 100644 index 25965ef9b95..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_variable_used_next_stmt.php.inc +++ /dev/null @@ -1,15 +0,0 @@ -time = $time ?? new \DateTime(); - $this->timeWasSet = null !== $time; - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_with_next_required.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_with_next_required.php.inc deleted file mode 100644 index fa30893f88d..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/skip_with_next_required.php.inc +++ /dev/null @@ -1,13 +0,0 @@ -time = $time ?? new \DateTime(); - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/some_class.php.inc b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/some_class.php.inc deleted file mode 100644 index 69689f45e8d..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Fixture/some_class.php.inc +++ /dev/null @@ -1,29 +0,0 @@ -logger = $logger ?? new NullLogger; - } -} - -?> ------ - diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/NewInInitializerRectorTest.php b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/NewInInitializerRectorTest.php deleted file mode 100644 index a9c48e734cf..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/NewInInitializerRectorTest.php +++ /dev/null @@ -1,28 +0,0 @@ -doTestFile($filePath); - } - - public static function provideData(): Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/configured_rule.php'; - } -} diff --git a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Source/InstantiableViaNamedConstructor.php b/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Source/InstantiableViaNamedConstructor.php deleted file mode 100644 index 53a05e8f4d2..00000000000 --- a/rules-tests/Php81/Rector/ClassMethod/NewInInitializerRector/Source/InstantiableViaNamedConstructor.php +++ /dev/null @@ -1,17 +0,0 @@ -rule(NewInInitializerRector::class); - - $rectorConfig->phpVersion(PhpVersion::PHP_81); -}; diff --git a/rules/Php81/NodeAnalyzer/CoalescePropertyAssignMatcher.php b/rules/Php81/NodeAnalyzer/CoalescePropertyAssignMatcher.php deleted file mode 100644 index 231dbea28dd..00000000000 --- a/rules/Php81/NodeAnalyzer/CoalescePropertyAssignMatcher.php +++ /dev/null @@ -1,73 +0,0 @@ -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); - } -} diff --git a/rules/Php81/NodeAnalyzer/ComplexNewAnalyzer.php b/rules/Php81/NodeAnalyzer/ComplexNewAnalyzer.php deleted file mode 100644 index 5ebf99840d2..00000000000 --- a/rules/Php81/NodeAnalyzer/ComplexNewAnalyzer.php +++ /dev/null @@ -1,87 +0,0 @@ -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; - } -} diff --git a/rules/Php81/Rector/ClassMethod/NewInInitializerRector.php b/rules/Php81/Rector/ClassMethod/NewInInitializerRector.php index e3db19e0437..9f6b8ce280e 100644 --- a/rules/Php81/Rector/ClassMethod/NewInInitializerRector.php +++ b/rules/Php81/Rector/ClassMethod/NewInInitializerRector.php @@ -5,38 +5,17 @@ namespace Rector\Php81\Rector\ClassMethod; use PhpParser\Node; -use PhpParser\Node\Expr; -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\CoalescePropertyAssignMatcher; +use Rector\Configuration\Deprecation\Contract\DeprecatedInterface; +use Rector\Exception\ShouldNotHappenException; 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 +final class NewInInitializerRector extends AbstractRector implements MinPhpVersionInterface, DeprecatedInterface { - public function __construct( - private readonly ReflectionResolver $reflectionResolver, - private readonly ClassChildAnalyzer $classChildAnalyzer, - private readonly CoalescePropertyAssignMatcher $coalescePropertyAssignMatcher, - private readonly StmtsManipulator $stmtsManipulator - ) { - } - public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Replace property declaration of new state with direct new', [ @@ -81,151 +60,14 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if ($this->shouldSkipClass($node)) { - return null; - } - - $constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); - if (! $constructClassMethod instanceof ClassMethod) { - return null; - } - - $params = $this->resolveParams($constructClassMethod); - if ($params === []) { - return null; - } - - $hasChanged = false; - foreach ((array) $constructClassMethod->stmts as $key => $stmt) { - foreach ($params as $param) { - $paramName = $this->getName($param); - - if ($param->type instanceof NullableType && $param->default === null) { - continue; - } - - $coalesce = $this->coalescePropertyAssignMatcher->matchCoalesceAssignsToLocalPropertyNamed( - $stmt, - $paramName - ); - if (! $coalesce instanceof Coalesce) { - continue; - } - - if ($this->stmtsManipulator->isVariableUsedInNextStmt($constructClassMethod, $key + 1, $paramName)) { - continue; - } - - $param->default = $coalesce->right; - - unset($constructClassMethod->stmts[$key]); - $this->processPropertyPromotion($node, $param, $paramName); - - $hasChanged = true; - } - } - - if ($hasChanged) { - return $node; - } - - return null; + throw new ShouldNotHappenException(sprintf( + '"%s" is deprecated as depends on context. Cannot be automated. Use manually where needed instead', + self::class + )); } 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->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 []; - } - - $params = array_filter( - $classMethod->params, - static fn (Param $param): bool => $param->type instanceof NullableType - ); - - if ($params === []) { - return $params; - } - - $totalParams = count($classMethod->params); - - foreach (array_keys($params) as $key) { - for ($iteration = $key + 1; $iteration < $totalParams; ++$iteration) { - if (isset($classMethod->params[$iteration]) && ! $classMethod->params[$iteration]->default instanceof Expr) { - return []; - } - } - } - - return $params; - } - - private function shouldSkipClass(Class_ $class): bool - { - if ($class->stmts === []) { - return true; - } - - if ($class->isAbstract()) { - return true; - } - - return $class->isAnonymous(); - } } diff --git a/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php b/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php index 50d48af63a8..8255677d41e 100644 --- a/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php +++ b/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php @@ -10,10 +10,15 @@ use PHPStan\Type\MixedType; use PHPStan\Type\Type; +/** + * @api + */ final readonly class ClassChildAnalyzer { /** * Look both parent class and interface, yes, all PHP interface methods are abstract + * + * @api rector-symfony */ public function hasAbstractParentClassMethod(ClassReflection $classReflection, string $methodName): bool {