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/phpunit.xml b/phpunit.xml index 8607c5b0eb2..9aaa4f6b337 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,6 +6,7 @@ colors="true" executionOrder="defects" defaultTestSuite="main" + stopOnError="true" displayDetailsOnTestsThatTriggerWarnings="true" displayDetailsOnPhpunitDeprecations="true" > diff --git a/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Fixture/skip_complex_assign.php b/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Fixture/skip_complex_assign.php new file mode 100644 index 00000000000..1cf85e7444f --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Fixture/skip_complex_assign.php @@ -0,0 +1,13 @@ +name = $name; + } +} diff --git a/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/elseif.php.inc b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/elseif.php.inc new file mode 100644 index 00000000000..cad374eaeb0 --- /dev/null +++ b/rules-tests/Php83/Rector/BooleanAnd/JsonValidateRector/Fixture/elseif.php.inc @@ -0,0 +1,17 @@ + +----- + \ 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..c9841c136c3 --- /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; + } +} \ No newline at end of file diff --git a/rules/Php85/Rector/FuncCall/ChrArgModuloRector.php b/rules/Php85/Rector/FuncCall/ChrArgModuloRector.php index 73af63c27ab..8486b164dee 100644 --- a/rules/Php85/Rector/FuncCall/ChrArgModuloRector.php +++ b/rules/Php85/Rector/FuncCall/ChrArgModuloRector.php @@ -94,4 +94,4 @@ public function provideMinPhpVersion(): int { return PhpVersionFeature::DEPRECATE_OUTSIDE_INTERVEL_VAL_IN_CHR_FUNCTION; } -} +} \ No newline at end of file diff --git a/src/Configuration/RectorConfigBuilder.php b/src/Configuration/RectorConfigBuilder.php index 9e45ec5276e..a1c433722fb 100644 --- a/src/Configuration/RectorConfigBuilder.php +++ b/src/Configuration/RectorConfigBuilder.php @@ -1240,4 +1240,4 @@ public function withSetProviders(string ...$setProviders): self return $this; } -} +} \ No newline at end of file diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index b76487f4a62..826fdc2de44 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -701,6 +701,12 @@ final class PhpVersionFeature * @var int */ public const DYNAMIC_CLASS_CONST_FETCH = 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/deprecate-implicitly-nullable-types diff --git a/src/ValueObject/PolyfillPackage.php b/src/ValueObject/PolyfillPackage.php index 06ca9334736..a3772b256ab 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 */