From 377043f447fa52aa2799ee7aef8fb3ceb53e062d Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 3 Sep 2025 17:07:04 +0700 Subject: [PATCH 1/3] [DowngradePhp83] Add DowngradeJsonValidateRector --- .../DowngradeJsonValidateRectorTest.php | 28 +++ .../Fixture/in_echo.php.inc | 44 +++++ .../config/configured_rule.php | 10 ++ .../FuncCall/DowngradeJsonValidateRector.php | 162 ++++++++++++++++++ .../snippet/json_validate_closure.php.inc | 24 +++ 5 files changed, 268 insertions(+) create mode 100644 rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/DowngradeJsonValidateRectorTest.php create mode 100644 rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/Fixture/in_echo.php.inc create mode 100644 rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/config/configured_rule.php create mode 100644 rules/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector.php create mode 100644 rules/DowngradePhp83/snippet/json_validate_closure.php.inc diff --git a/rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/DowngradeJsonValidateRectorTest.php b/rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/DowngradeJsonValidateRectorTest.php new file mode 100644 index 00000000..ee0a4819 --- /dev/null +++ b/rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/DowngradeJsonValidateRectorTest.php @@ -0,0 +1,28 @@ +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/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/Fixture/in_echo.php.inc b/rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/Fixture/in_echo.php.inc new file mode 100644 index 00000000..923faabc --- /dev/null +++ b/rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/Fixture/in_echo.php.inc @@ -0,0 +1,44 @@ + +----- + $maxDepth) { + throw new \ValueError(sprintf('json_validate(): Argument #2 ($depth) must be less than %d', $maxDepth)); + } + json_decode($json, true, $depth, $flags); + return \JSON_ERROR_NONE === json_last_error(); + }; + echo (int) $jsonValidate($json); + } +} + +?> diff --git a/rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/config/configured_rule.php b/rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/config/configured_rule.php new file mode 100644 index 00000000..313a5171 --- /dev/null +++ b/rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(DowngradeJsonValidateRector::class); +}; diff --git a/rules/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector.php b/rules/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector.php new file mode 100644 index 00000000..2e52991a --- /dev/null +++ b/rules/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector.php @@ -0,0 +1,162 @@ + $maxDepth) { + throw new \ValueError(sprintf('json_validate(): Argument #2 ($depth) must be less than %d', $maxDepth)); + } + + json_decode($json, true, $depth, $flags); + return \JSON_ERROR_NONE === json_last_error(); +}; +$jsonValidate('{"foo": "bar"}'); +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [StmtsAwareInterface::class, Switch_::class, Return_::class, Expression::class, Echo_::class]; + } + + /** + * @param StmtsAwareInterface|Switch_|Return_|Expression|Echo_ $node + * @return Node[]|null + */ + public function refactor(Node $node): ?array + { + $expr = $this->exprInTopStmtMatcher->match( + $node, + function (Node $subNode): bool { + if (! $subNode instanceof FuncCall) { + return false; + } + + // need pull Scope from target traversed sub Node + return ! $this->shouldSkip($subNode); + } + ); + + if (! $expr instanceof FuncCall) { + return null; + } + + $variable = new Variable('jsonValidate'); + + $function = $this->createClosure(); + $expression = new Expression(new Assign($variable, $function)); + + $expr->name = $variable; + + return [$expression, $node]; + } + + private function createClosure(): Closure + { + if ($this->cachedClosure instanceof Closure) { + return clone $this->cachedClosure; + } + + $stmts = $this->inlineCodeParser->parseFile(__DIR__ . '/../../snippet/json_validate_closure.php.inc'); + + /** @var Expression $expression */ + $expression = $stmts[0]; + + $expr = $expression->expr; + if (! $expr instanceof Closure) { + throw new ShouldNotHappenException(); + } + + $this->cachedClosure = $expr; + + return $expr; + } + + private function shouldSkip(CallLike $callLike): bool + { + if (! $callLike instanceof FuncCall) { + return false; + } + + if (! $this->isName($callLike, 'json_validate')) { + return true; + } + + $scope = $callLike->getAttribute(AttributeKey::SCOPE); + if ($scope instanceof Scope && $scope->isInFunctionExists('json_validate')) { + return true; + } + + if ($callLike->isFirstClassCallable()) { + return true; + } + + $args = $callLike->getArgs(); + return count($args) < 1; + } +} diff --git a/rules/DowngradePhp83/snippet/json_validate_closure.php.inc b/rules/DowngradePhp83/snippet/json_validate_closure.php.inc new file mode 100644 index 00000000..c41ec8a3 --- /dev/null +++ b/rules/DowngradePhp83/snippet/json_validate_closure.php.inc @@ -0,0 +1,24 @@ + $maxDepth) { + throw new \ValueError(sprintf('json_validate(): Argument #2 ($depth) must be less than %d', $maxDepth)); + } + + json_decode($json, true, $depth, $flags); + return \JSON_ERROR_NONE === json_last_error(); +}; From 3bc492795c1e328ca4d0bc48400af02d6f110a67 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 3 Sep 2025 17:08:11 +0700 Subject: [PATCH 2/3] [DowngradePhp83] skip different function --- .../Fixture/skip_different_function.php.inc | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/Fixture/skip_different_function.php.inc diff --git a/rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/Fixture/skip_different_function.php.inc b/rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/Fixture/skip_different_function.php.inc new file mode 100644 index 00000000..9aaa12d7 --- /dev/null +++ b/rules-tests/DowngradePhp83/Rector/FuncCall/DowngradeJsonValidateRector/Fixture/skip_different_function.php.inc @@ -0,0 +1,11 @@ + Date: Wed, 3 Sep 2025 17:08:38 +0700 Subject: [PATCH 3/3] register --- config/set/downgrade-php83.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/set/downgrade-php83.php b/config/set/downgrade-php83.php index af0d5d88..ef73d235 100644 --- a/config/set/downgrade-php83.php +++ b/config/set/downgrade-php83.php @@ -7,6 +7,7 @@ use Rector\ValueObject\PhpVersion; use Rector\DowngradePhp83\Rector\ClassConst\DowngradeTypedClassConstRector; use Rector\DowngradePhp83\Rector\ClassConstFetch\DowngradeDynamicClassConstFetchRector; +use Rector\DowngradePhp83\Rector\FuncCall\DowngradeJsonValidateRector; return static function (RectorConfig $rectorConfig): void { $rectorConfig->phpVersion(PhpVersion::PHP_82); @@ -14,5 +15,6 @@ DowngradeTypedClassConstRector::class, DowngradeReadonlyAnonymousClassRector::class, DowngradeDynamicClassConstFetchRector::class, + DowngradeJsonValidateRector::class, ]); };