diff --git a/config/set/php74.php b/config/set/php74.php index 5c4121de157..f8683feba25 100644 --- a/config/set/php74.php +++ b/config/set/php74.php @@ -13,6 +13,7 @@ use Rector\Php74\Rector\FuncCall\MbStrrposEncodingArgumentPositionRector; use Rector\Php74\Rector\FuncCall\MoneyFormatToNumberFormatRector; use Rector\Php74\Rector\FuncCall\RestoreIncludePathToIniRestoreRector; +use Rector\Php74\Rector\If_\IfToNullCoalescingAssignRector; use Rector\Php74\Rector\Property\RestoreDefaultNullToNullableTypePropertyRector; use Rector\Php74\Rector\StaticCall\ExportToReflectionFunctionRector; use Rector\Php74\Rector\Ternary\ParenthesizeNestedTernaryRector; @@ -34,6 +35,7 @@ ExportToReflectionFunctionRector::class, MbStrrposEncodingArgumentPositionRector::class, NullCoalescingOperatorRector::class, + IfToNullCoalescingAssignRector::class, ClosureToArrowFunctionRector::class, RestoreDefaultNullToNullableTypePropertyRector::class, CurlyToSquareBracketArrayStringRector::class, diff --git a/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/is_null_property.php.inc b/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/is_null_property.php.inc new file mode 100644 index 00000000000..a6648ddae73 --- /dev/null +++ b/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/is_null_property.php.inc @@ -0,0 +1,37 @@ +instance)) { + $this->instance = new self(); + } + + return $this->instance; + } +} + +?> +----- +instance ??= new self(); + + return $this->instance; + } +} + +?> diff --git a/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/isset_array.php.inc b/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/isset_array.php.inc new file mode 100644 index 00000000000..8d54d059cd9 --- /dev/null +++ b/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/isset_array.php.inc @@ -0,0 +1,19 @@ + +----- + diff --git a/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/null_identical_variable.php.inc b/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/null_identical_variable.php.inc new file mode 100644 index 00000000000..9902bfea83f --- /dev/null +++ b/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/null_identical_variable.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/skip_different_target.php.inc b/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/skip_different_target.php.inc new file mode 100644 index 00000000000..085087b93db --- /dev/null +++ b/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/Fixture/skip_different_target.php.inc @@ -0,0 +1,10 @@ +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/Php74/Rector/If_/IfToNullCoalescingAssignRector/config/configured_rule.php b/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/config/configured_rule.php new file mode 100644 index 00000000000..cb61257fa8d --- /dev/null +++ b/rules-tests/Php74/Rector/If_/IfToNullCoalescingAssignRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([IfToNullCoalescingAssignRector::class]); diff --git a/rules/DeadCode/Rector/ConstFetch/RemovePhpVersionIdCheckRector.php b/rules/DeadCode/Rector/ConstFetch/RemovePhpVersionIdCheckRector.php index 4821373442b..4ed4a62f3e2 100644 --- a/rules/DeadCode/Rector/ConstFetch/RemovePhpVersionIdCheckRector.php +++ b/rules/DeadCode/Rector/ConstFetch/RemovePhpVersionIdCheckRector.php @@ -84,12 +84,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): null|array|int { - /** - * $this->phpVersionProvider->provide() fallback is here as $currentFileProvider must be accessed after initialization - */ - if ($this->phpVersion === null) { - $this->phpVersion = $this->phpVersionProvider->provide(); - } + $this->phpVersion ??= $this->phpVersionProvider->provide(); if (! $node->cond instanceof BinaryOp) { return null; diff --git a/rules/Naming/Guard/PropertyConflictingNameGuard/MatchPropertyTypeConflictingNameGuard.php b/rules/Naming/Guard/PropertyConflictingNameGuard/MatchPropertyTypeConflictingNameGuard.php index e46fbcc6563..ac27f76b650 100644 --- a/rules/Naming/Guard/PropertyConflictingNameGuard/MatchPropertyTypeConflictingNameGuard.php +++ b/rules/Naming/Guard/PropertyConflictingNameGuard/MatchPropertyTypeConflictingNameGuard.php @@ -33,10 +33,7 @@ private function resolve(ClassLike $classLike): array $expectedNames = []; foreach ($classLike->getProperties() as $property) { $expectedName = $this->matchPropertyTypeExpectedNameResolver->resolve($property, $classLike); - if ($expectedName === null) { - // fallback to existing name - $expectedName = $this->nodeNameResolver->getName($property); - } + $expectedName ??= $this->nodeNameResolver->getName($property); $expectedNames[] = $expectedName; } diff --git a/rules/Php74/Rector/If_/IfToNullCoalescingAssignRector.php b/rules/Php74/Rector/If_/IfToNullCoalescingAssignRector.php new file mode 100644 index 00000000000..546259b43f3 --- /dev/null +++ b/rules/Php74/Rector/If_/IfToNullCoalescingAssignRector.php @@ -0,0 +1,153 @@ +> + */ + public function getNodeTypes(): array + { + return [If_::class]; + } + + /** + * @param If_ $node + */ + public function refactor(Node $node): ?Expression + { + if ($node->else instanceof Else_) { + return null; + } + + if ($node->elseifs !== []) { + return null; + } + + if (count($node->stmts) !== 1) { + return null; + } + + $onlyStmt = $node->stmts[0]; + if (! $onlyStmt instanceof Expression) { + return null; + } + + $assign = $onlyStmt->expr; + if (! $assign instanceof Assign) { + return null; + } + + $testedExpr = $this->matchNullGuardedExpr($node->cond); + if (! $testedExpr instanceof Expr) { + return null; + } + + if (! $this->nodeComparator->areNodesEqual($assign->var, $testedExpr)) { + return null; + } + + // the assigned value must not reference the target, e.g. $x = $x + 1 + $selfReference = $this->betterNodeFinder->findFirst( + $assign->expr, + fn (Node $subNode): bool => $this->nodeComparator->areNodesEqual($subNode, $assign->var) + ); + if ($selfReference instanceof Node) { + return null; + } + + return new Expression(new AssignCoalesce($assign->var, $assign->expr)); + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::NULL_COALESCE_ASSIGN; + } + + private function matchNullGuardedExpr(Expr $expr): ?Expr + { + // ! isset($value) + if ($expr instanceof BooleanNot && $expr->expr instanceof Isset_) { + if (count($expr->expr->vars) !== 1) { + return null; + } + + return $expr->expr->vars[0]; + } + + // is_null($value) + if ($expr instanceof FuncCall && $this->isName($expr, 'is_null')) { + if ($expr->isFirstClassCallable()) { + return null; + } + + if (count($expr->getArgs()) !== 1) { + return null; + } + + return $expr->getArgs()[0] + ->value; + } + + // null === $value or $value === null + if ($expr instanceof Identical) { + if ($this->valueResolver->isNull($expr->left)) { + return $expr->right; + } + + if ($this->valueResolver->isNull($expr->right)) { + return $expr->left; + } + } + + return null; + } +} diff --git a/src/BetterPhpDocParser/PhpDocParser/ClassAnnotationMatcher.php b/src/BetterPhpDocParser/PhpDocParser/ClassAnnotationMatcher.php index ffa903192fd..5802b15d199 100644 --- a/src/BetterPhpDocParser/PhpDocParser/ClassAnnotationMatcher.php +++ b/src/BetterPhpDocParser/PhpDocParser/ClassAnnotationMatcher.php @@ -46,9 +46,7 @@ public function resolveTagFullyQualifiedName(string $tag, Node $node): string $uses = $this->useImportsResolver->resolve(); $fullyQualifiedClass = $this->resolveFullyQualifiedClass($uses, $node, $tag); - if ($fullyQualifiedClass === null) { - $fullyQualifiedClass = $tag; - } + $fullyQualifiedClass ??= $tag; $this->fullyQualifiedNameByHash[$uniqueId] = $fullyQualifiedClass; diff --git a/src/BetterPhpDocParser/Printer/PhpDocInfoPrinter.php b/src/BetterPhpDocParser/Printer/PhpDocInfoPrinter.php index e556cf17118..8ee9a536319 100644 --- a/src/BetterPhpDocParser/Printer/PhpDocInfoPrinter.php +++ b/src/BetterPhpDocParser/Printer/PhpDocInfoPrinter.php @@ -256,9 +256,7 @@ private function printEnd(string $output): string ->getPhpDocNode() ->getAttribute(PhpDocAttributeKey::LAST_PHP_DOC_TOKEN_POSITION); - if ($lastTokenPosition === null) { - $lastTokenPosition = $this->currentTokenPosition; - } + $lastTokenPosition ??= $this->currentTokenPosition; if ($lastTokenPosition === 0) { return $output . "\n */"; diff --git a/src/Console/Style/SymfonyStyleFactory.php b/src/Console/Style/SymfonyStyleFactory.php index aa9ae877b0e..261575652df 100644 --- a/src/Console/Style/SymfonyStyleFactory.php +++ b/src/Console/Style/SymfonyStyleFactory.php @@ -22,10 +22,7 @@ public function __construct( */ public function create(): RectorStyle { - // to prevent missing argv indexes - if (! isset($_SERVER['argv'])) { - $_SERVER['argv'] = []; - } + $_SERVER['argv'] ??= []; $argvInput = new ArgvInput(); $consoleOutput = new ConsoleOutput(); diff --git a/src/PhpParser/Printer/BetterStandardPrinter.php b/src/PhpParser/Printer/BetterStandardPrinter.php index b854b668ed9..4b176b3c7f1 100644 --- a/src/PhpParser/Printer/BetterStandardPrinter.php +++ b/src/PhpParser/Printer/BetterStandardPrinter.php @@ -90,9 +90,7 @@ public function printFormatPreserving(array $stmts, array $origStmts, array $ori public function print(Node|array|null $node): string { - if ($node === null) { - $node = []; - } + $node ??= []; if (! is_array($node)) { $node = [$node]; diff --git a/src/ValueObject/ProcessResult.php b/src/ValueObject/ProcessResult.php index 1395b5f874e..d15bc4ce559 100644 --- a/src/ValueObject/ProcessResult.php +++ b/src/ValueObject/ProcessResult.php @@ -100,9 +100,7 @@ public function getRuleApplicationCounts(): array foreach ($this->fileDiffs as $fileDiff) { foreach ($fileDiff->getRectorClasses() as $rectorClass) { - if (! isset($ruleCounts[$rectorClass])) { - $ruleCounts[$rectorClass] = 0; - } + $ruleCounts[$rectorClass] ??= 0; ++$ruleCounts[$rectorClass]; }