diff --git a/config/set/php85.php b/config/set/php85.php index 7536382f3b3..a81f317de0a 100644 --- a/config/set/php85.php +++ b/config/set/php85.php @@ -7,9 +7,9 @@ use PhpParser\Node\Expr\Cast\Int_; use PhpParser\Node\Expr\Cast\String_; use Rector\Config\RectorConfig; -use Rector\Php85\Rector\Const_\DeprecatedAnnotationToDeprecatedAttributeRector; use Rector\Php85\Rector\ArrayDimFetch\ArrayFirstLastRector; use Rector\Php85\Rector\ClassMethod\NullDebugInfoReturnRector; +use Rector\Php85\Rector\Const_\DeprecatedAnnotationToDeprecatedAttributeRector; use Rector\Php85\Rector\FuncCall\RemoveFinfoBufferContextArgRector; use Rector\Removing\Rector\FuncCall\RemoveFuncCallArgRector; use Rector\Removing\ValueObject\RemoveFuncCallArg; @@ -20,6 +20,8 @@ use Rector\Renaming\ValueObject\MethodCallRename; use Rector\Renaming\ValueObject\RenameCast; use Rector\Renaming\ValueObject\RenameClassAndConstFetch; +use Rector\Transform\Rector\FuncCall\WrapFuncCallWithPhpVersionIdCheckerRector; +use Rector\Transform\ValueObject\WrapFuncCallWithPhpVersionIdChecker; return static function (RectorConfig $rectorConfig): void { $rectorConfig->rules( @@ -180,4 +182,16 @@ new RenameCast(String_::class, String_::KIND_BINARY, String_::KIND_STRING), ] ); + + // https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_no-op_functions_from_the_resource_to_object_conversion + $rectorConfig->ruleWithConfiguration( + WrapFuncCallWithPhpVersionIdCheckerRector::class, + [ + new WrapFuncCallWithPhpVersionIdChecker('curl_close', 80500), + new WrapFuncCallWithPhpVersionIdChecker('curl_share_close', 80500), + new WrapFuncCallWithPhpVersionIdChecker('finfo_close', 80500), + new WrapFuncCallWithPhpVersionIdChecker('imagedestroy', 80500), + new WrapFuncCallWithPhpVersionIdChecker('xml_parser_free', 80500), + ] + ); }; diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc new file mode 100644 index 00000000000..e8ad4d4693d --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc @@ -0,0 +1,8 @@ + diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check.php.inc new file mode 100644 index 00000000000..0e9f813fd62 --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check.php.inc @@ -0,0 +1,12 @@ + diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check_with_function_exists.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check_with_function_exists.php.inc new file mode 100644 index 00000000000..6bad9d59da8 --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check_with_function_exists.php.inc @@ -0,0 +1,12 @@ + diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc new file mode 100644 index 00000000000..a09f15bdd31 --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc @@ -0,0 +1,7 @@ + diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc new file mode 100644 index 00000000000..241bcbe23bb --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc @@ -0,0 +1,9 @@ + diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc new file mode 100644 index 00000000000..52d509c0a10 --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc @@ -0,0 +1,21 @@ + +----- + diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php new file mode 100644 index 00000000000..6a3fbf7ad80 --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php @@ -0,0 +1,26 @@ +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/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php new file mode 100644 index 00000000000..0a49f668967 --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php @@ -0,0 +1,13 @@ +withConfiguredRule( + WrapFuncCallWithPhpVersionIdCheckerRector::class, + [new WrapFuncCallWithPhpVersionIdChecker('no_op_function', 80500)] + ); diff --git a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php new file mode 100644 index 00000000000..850646f0321 --- /dev/null +++ b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php @@ -0,0 +1,179 @@ +> + */ + public function getNodeTypes(): array + { + return [StmtsAwareInterface::class]; + } + + /** + * @param StmtsAwareInterface $node + */ + public function refactor(Node $node): null|Node|int + { + if ($node->stmts === null) { + return null; + } + + if ($this->isWrappedFuncCall($node)) { + return NodeVisitor::DONT_TRAVERSE_CHILDREN; + } + + $hasChanged = false; + foreach ($node->stmts as $key => $stmt) { + if (! $stmt instanceof Expression || ! $stmt->expr instanceof FuncCall) { + continue; + } + + $funcCall = $stmt->expr; + + foreach ($this->wrapFuncCallWithPhpVersionIdCheckers as $wrapFuncCallWithPhpVersionIdChecker) { + if ($this->getName($funcCall) !== $wrapFuncCallWithPhpVersionIdChecker->getFunctionName()) { + continue; + } + + $phpVersionIdConst = new ConstFetch(new Name('PHP_VERSION_ID')); + $if = new If_(new BooleanAnd( + new FuncCall(new Name('function_exists'), [new Arg(new String_( + $wrapFuncCallWithPhpVersionIdChecker->getFunctionName() + ))]), + new Smaller($phpVersionIdConst, new Int_($wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId())), + )); + $if->stmts = [$stmt]; + + $node->stmts[$key] = $if; + + $hasChanged = true; + } + } + + if ($hasChanged) { + return $node; + } + + return null; + } + + public function configure(array $configuration): void + { + Assert::allIsInstanceOf($configuration, WrapFuncCallWithPhpVersionIdChecker::class); + + $this->wrapFuncCallWithPhpVersionIdCheckers = $configuration; + } + + private function isWrappedFuncCall(StmtsAwareInterface $node): bool + { + if (! $node instanceof If_) { + return false; + } + + $phpVersionId = $this->getPhpVersionId($node->cond); + if ($phpVersionId === null) { + return false; + } + + if (count($node->stmts) !== 1) { + return false; + } + + $childStmt = $node->stmts[0]; + + if (! $childStmt instanceof Expression || ! $childStmt->expr instanceof FuncCall) { + return false; + } + + foreach ($this->wrapFuncCallWithPhpVersionIdCheckers as $wrapFuncCallWithPhpVersionIdChecker) { + if ( + $this->getName($childStmt->expr) !== $wrapFuncCallWithPhpVersionIdChecker->getFunctionName() + || $phpVersionId->value !== $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId() + ) { + continue; + } + + return true; + } + + return false; + } + + private function getPhpVersionId(Expr $expr): ?Int_ + { + if ($expr instanceof BooleanAnd) { + return $this->getPhpVersionId($expr->left) ?? $this->getPhpVersionId($expr->right); + } + + if (! $expr instanceof Smaller) { + return null; + } + + if (! $expr->left instanceof ConstFetch || ! $this->isName($expr->left->name, 'PHP_VERSION_ID')) { + return null; + } + + if (! $expr->right instanceof Int_) { + return null; + } + + return $expr->right; + } +} diff --git a/rules/Transform/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php b/rules/Transform/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php new file mode 100644 index 00000000000..cc100a6ccec --- /dev/null +++ b/rules/Transform/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php @@ -0,0 +1,24 @@ +functionName; + } + + public function getPhpVersionId(): int + { + return $this->phpVersionId; + } +}