From 1367ddf7369aec0c32193465c560f44817cda3be Mon Sep 17 00:00:00 2001 From: Arshid Date: Thu, 4 Sep 2025 11:54:57 +0530 Subject: [PATCH] [php 8.3] Add json_validate rule. --- config/set/php83.php | 2 + rules-tests/Php83/Rector/BooleanAnd/.DS_Store | Bin 0 -> 6148 bytes .../JsonValidateRector/Fixture/elseif.php.inc | 17 ++ .../Fixture/inline_string.php.inc | 13 ++ .../Fixture/json_flag.php.inc | 17 ++ .../Fixture/json_validate.php.inc | 17 ++ .../Fixture/json_validate_base.php.inc | 17 ++ .../Fixture/json_validate_named.php.inc | 15 ++ .../Fixture/json_validate_named_base.php.inc | 15 ++ .../Fixture/json_validate_yoda.php.inc | 17 ++ .../Fixture/json_validate_yoda_base.php.inc | 17 ++ ...p_json_validate_invalid_depth.php copy.inc | 8 + .../skip_json_validate_invalid_flag.php.inc | 8 + .../JsonValidateRectorTest.php | 28 +++ .../config/configured_rule.php | 13 ++ .../Rector/BooleanAnd/JsonValidateRector.php | 171 ++++++++++++++++++ src/ValueObject/PhpVersionFeature.php | 6 + src/ValueObject/PolyfillPackage.php | 5 + 18 files changed, 386 insertions(+) create mode 100644 rules-tests/Php83/Rector/BooleanAnd/.DS_Store create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/elseif.php.inc create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/inline_string.php.inc create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_flag.php.inc create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate.php.inc create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_base.php.inc create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_named.php.inc create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_named_base.php.inc create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_yoda.php.inc create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_yoda_base.php.inc create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/skip_json_validate_invalid_depth.php copy.inc create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/skip_json_validate_invalid_flag.php.inc create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/JsonValidateRectorTest.php create mode 100644 rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/config/configured_rule.php create mode 100644 rules/Php83/Rector/BooleanAnd/JsonValidateRector.php diff --git a/config/set/php83.php b/config/set/php83.php index 8e0522228fb..1a056a87c05 100644 --- a/config/set/php83.php +++ b/config/set/php83.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Rector\Config\RectorConfig; +use Rector\Php83\Rector\BooleanAnd\JsonValidateRector; use Rector\Php83\Rector\Class_\ReadOnlyAnonymousClassRector; use Rector\Php83\Rector\ClassConst\AddTypeToConstRector; use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector; @@ -18,5 +19,6 @@ RemoveGetClassGetParentClassNoArgsRector::class, ReadOnlyAnonymousClassRector::class, DynamicClassConstFetchRector::class, + JsonValidateRector::class, ]); }; diff --git a/rules-tests/Php83/Rector/BooleanAnd/.DS_Store b/rules-tests/Php83/Rector/BooleanAnd/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..080b4b70e0ad90e0c5259de207e6f967459e7863 GIT binary patch literal 6148 zcmeHKO;6iE5S}_GL=-?78!Zg?2(Po=k%pgH04kf~ zm}1H)qZBCeFEGH*?l~P$Nrnpg_4!paq(kT_RahJC=jJrYO#**@S+=1sZ>+$2WR7G~ zp7|VC$yuJ2RkwSs8Vk+EXG_6SupE4ezL`l>#nrg%$D>dD>&TcqncH!4oDC;~_KO`; zRB=`eM>Zf!hj97)F)LCt>6>wpmNqtW3qdPr4ccqdY47dpj^2FNn|1W`eXrZmTbplY zvsSRWzVYF3@IC)gmM@-?fpWJwL=$9$~Pa%@2q@>qU`R-udO?&(4maNfH3eo1Kj&T+Fbwl9`66GlMKRu zFz|mepqjhUZXaj!we`*^xz=jv36zEXYKK2VVAxU&UoORKP%D_%Yyd-#wL{E6 +----- + \ No newline at end of file diff --git a/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate.php.inc b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate.php.inc new file mode 100644 index 00000000000..c5a679544a1 --- /dev/null +++ b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate.php.inc @@ -0,0 +1,17 @@ + +----- + \ No newline at end of file diff --git a/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_base.php.inc b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_base.php.inc new file mode 100644 index 00000000000..13201b3bf84 --- /dev/null +++ b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_base.php.inc @@ -0,0 +1,17 @@ + +----- + \ No newline at end of file diff --git a/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_named.php.inc b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_named.php.inc new file mode 100644 index 00000000000..7d9e3cda76e --- /dev/null +++ b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_named.php.inc @@ -0,0 +1,15 @@ + +----- + diff --git a/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_named_base.php.inc b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_named_base.php.inc new file mode 100644 index 00000000000..dd40a1364a7 --- /dev/null +++ b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_named_base.php.inc @@ -0,0 +1,15 @@ + +----- + diff --git a/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_yoda.php.inc b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_yoda.php.inc new file mode 100644 index 00000000000..6f29ea59a90 --- /dev/null +++ b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_yoda.php.inc @@ -0,0 +1,17 @@ + +----- + \ No newline at end of file diff --git a/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_yoda_base.php.inc b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_yoda_base.php.inc new file mode 100644 index 00000000000..60629369fbc --- /dev/null +++ b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/json_validate_yoda_base.php.inc @@ -0,0 +1,17 @@ + +----- + \ No newline at end of file diff --git a/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/skip_json_validate_invalid_depth.php copy.inc b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/skip_json_validate_invalid_depth.php copy.inc new file mode 100644 index 00000000000..6e7e9a9f274 --- /dev/null +++ b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/skip_json_validate_invalid_depth.php copy.inc @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/skip_json_validate_invalid_flag.php.inc b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/skip_json_validate_invalid_flag.php.inc new file mode 100644 index 00000000000..8359236e996 --- /dev/null +++ b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/skip_json_validate_invalid_flag.php.inc @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/JsonValidateRectorTest.php b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/JsonValidateRectorTest.php new file mode 100644 index 00000000000..27333d5d312 --- /dev/null +++ b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/JsonValidateRectorTest.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/Php83/Rector/BooleanAnd/JsonValidateRector/config/configured_rule.php b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/config/configured_rule.php new file mode 100644 index 00000000000..dd7fc250a5d --- /dev/null +++ b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/config/configured_rule.php @@ -0,0 +1,13 @@ +phpVersion(PhpVersion::PHP_83); + + $rectorConfig->rule(JsonValidateRector::class); +}; diff --git a/rules/Php83/Rector/BooleanAnd/JsonValidateRector.php b/rules/Php83/Rector/BooleanAnd/JsonValidateRector.php new file mode 100644 index 00000000000..eea87297052 --- /dev/null +++ b/rules/Php83/Rector/BooleanAnd/JsonValidateRector.php @@ -0,0 +1,171 @@ +> + */ + public function getNodeTypes(): array + { + return [BooleanAnd::class]; + } + + /** + * @param BooleanAnd $node + */ + public function refactor(Node $node): ?Node + { + $funcCall = $this->matchJsonValidateArg($node); + + if (! $funcCall instanceof FuncCall) { + return null; + } + + if ($funcCall->isFirstClassCallable()) { + return null; + } + + $args = $funcCall->getArgs(); + + if (count($args) < 1 ){ + return null; + } + + if (! $this->validateArgs($funcCall)) { + return null; + } + $funcCall->name = new Name('json_validate'); + $funcCall->args = $args; + + return $funcCall; + } + + public function providePolyfillPackage(): string + { + return PolyfillPackage::PHP_83; + } + + public function matchJsonValidateArg(BooleanAnd $booleanAnd): ?FuncCall + { + // match: json_decode(...) !== null OR null !== json_decode(...) + if (! ($booleanAnd->left instanceof NotIdentical)) { + return null; + } + + $decodeMatch = $this->binaryOpManipulator->matchFirstAndSecondConditionNode( + $booleanAnd->left, + fn ($node) => $node instanceof FuncCall && $this->isName($node->name, 'json_decode'), + fn ($node) => $node instanceof ConstFetch && $this->isName($node->name, 'null') + ); + + if (! $decodeMatch instanceof TwoNodeMatch) { + return null; + } + + // match: json_last_error() === JSON_ERROR_NONE OR JSON_ERROR_NONE === json_last_error() + if (! ($booleanAnd->right instanceof Identical)) { + return null; + } + + $errorMatch = $this->binaryOpManipulator->matchFirstAndSecondConditionNode( + $booleanAnd->right, + fn ($node) => $node instanceof FuncCall && $this->isName($node->name, 'json_last_error'), + fn ($node) => $node instanceof ConstFetch && $this->isName($node->name, 'JSON_ERROR_NONE') + ); + + if (! $errorMatch instanceof TwoNodeMatch) { + return null; + } + + // always return the json_decode(...) call + $funcCall = $decodeMatch->getFirstExpr(); + if (! $funcCall instanceof FuncCall) { + return null; + } + return $funcCall; + } + + protected function validateArgs(FuncCall $funcCall): bool + { + $depth = $funcCall->getArg('depth', 2); + $flags = $funcCall->getArg('flags', 3); + + if ($flags instanceof Arg) { + $flagsValue = $this->valueResolver->getValue($flags); + if ($flagsValue !== JSON_INVALID_UTF8_IGNORE) { + return false; + } + } + + if ($depth instanceof Arg) { + $depthValue = $this->valueResolver->getValue($depth); + if ($depthValue <= 0 || $depthValue > self::JSON_MAX_DEPTH) { + return false; + } + } + + return true; + } +} diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index b76487f4a62..cf0b48bd481 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -641,6 +641,12 @@ final class PhpVersionFeature */ public const READONLY_ANONYMOUS_CLASS = PhpVersion::PHP_83; + /** + * @var int + * @see https://wiki.php.net/rfc/json_validate + */ + public const JSON_VALIDATE = PhpVersion::PHP_83; + /** * @see https://wiki.php.net/rfc/mixed_type_v2 * @var int diff --git a/src/ValueObject/PolyfillPackage.php b/src/ValueObject/PolyfillPackage.php index 06ca9334736..dd9c1e0d8a1 100644 --- a/src/ValueObject/PolyfillPackage.php +++ b/src/ValueObject/PolyfillPackage.php @@ -9,6 +9,11 @@ */ final class PolyfillPackage { + /** + * @var string + */ + public const PHP_83 = 'symfony/polyfill-php83'; + /** * @var string */