diff --git a/config/set/php-version-based.php b/config/set/php-version-based.php index 89a3c2c4c2f..b43898b5210 100644 --- a/config/set/php-version-based.php +++ b/config/set/php-version-based.php @@ -7,9 +7,7 @@ use PhpParser\Node\Expr\Cast\Int_; use PhpParser\Node\Expr\Cast\String_; use Rector\Arguments\Rector\ClassMethod\ArgumentAdderRector; -use Rector\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector; use Rector\Arguments\ValueObject\ArgumentAdder; -use Rector\Arguments\ValueObject\ReplaceFuncCallArgumentDefaultValue; use Rector\CodeQuality\Rector\ClassMethod\OptionalParametersAfterRequiredRector; use Rector\CodingStyle\Rector\ArrowFunction\ArrowFunctionDelegatingCallToFirstClassCallableRector; use Rector\CodingStyle\Rector\Closure\ClosureDelegatingCallToFirstClassCallableRector; @@ -95,6 +93,8 @@ use Rector\Php80\Rector\ClassMethod\FinalPrivateToPrivateVisibilityRector; use Rector\Php80\Rector\ClassMethod\SetStateToStaticRector; use Rector\Php80\Rector\FuncCall\ClassOnObjectRector; +use Rector\Php80\Rector\FuncCall\GetHeadersArgumentFormatRector; +use Rector\Php80\Rector\FuncCall\VersionCompareFuncCallArgumentRector; use Rector\Php80\Rector\Identical\StrEndsWithRector; use Rector\Php80\Rector\Identical\StrStartsWithRector; use Rector\Php80\Rector\NotIdentical\StrContainsRector; @@ -270,6 +270,8 @@ ClassOnThisVariableObjectRector::class, ConsistentImplodeRector::class, OptionalParametersAfterRequiredRector::class, + VersionCompareFuncCallArgumentRector::class, + GetHeadersArgumentFormatRector::class, // PHP 8.1 ReturnNeverTypeRector::class, @@ -468,18 +470,6 @@ 'pg_setclientencoding' => 'pg_set_client_encoding', ], PhpVersion::PHP_80); - $rectorConfig->ruleWithConfigurationPhpVersionBound(FunctionArgumentDefaultValueReplacerRector::class, [ - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'gte', 'ge'), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '', '!='), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '!', '!='), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'g', 'gt'), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'l', 'lt'), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'n', 'ne'), - new ReplaceFuncCallArgumentDefaultValue('get_headers', 1, 0, false), - new ReplaceFuncCallArgumentDefaultValue('get_headers', 1, 1, true), - ], PhpVersion::PHP_80); - // PHP 8.5 $rectorConfig->ruleWithConfigurationPhpVersionBound(RemoveFuncCallArgRector::class, [ // https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_key_length_parameter_of_openssl_pkey_derive diff --git a/config/set/php80.php b/config/set/php80.php index 502b7650c0e..74f9cff736f 100644 --- a/config/set/php80.php +++ b/config/set/php80.php @@ -3,9 +3,7 @@ declare(strict_types=1); use Rector\Arguments\Rector\ClassMethod\ArgumentAdderRector; -use Rector\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector; use Rector\Arguments\ValueObject\ArgumentAdder; -use Rector\Arguments\ValueObject\ReplaceFuncCallArgumentDefaultValue; use Rector\CodeQuality\Rector\ClassMethod\OptionalParametersAfterRequiredRector; use Rector\CodingStyle\Rector\FuncCall\ConsistentImplodeRector; use Rector\Config\RectorConfig; @@ -18,6 +16,8 @@ use Rector\Php80\Rector\ClassMethod\FinalPrivateToPrivateVisibilityRector; use Rector\Php80\Rector\ClassMethod\SetStateToStaticRector; use Rector\Php80\Rector\FuncCall\ClassOnObjectRector; +use Rector\Php80\Rector\FuncCall\GetHeadersArgumentFormatRector; +use Rector\Php80\Rector\FuncCall\VersionCompareFuncCallArgumentRector; use Rector\Php80\Rector\Identical\StrEndsWithRector; use Rector\Php80\Rector\Identical\StrStartsWithRector; use Rector\Php80\Rector\NotIdentical\StrContainsRector; @@ -47,6 +47,8 @@ ClassOnThisVariableObjectRector::class, ConsistentImplodeRector::class, OptionalParametersAfterRequiredRector::class, + VersionCompareFuncCallArgumentRector::class, + GetHeadersArgumentFormatRector::class, ]); $rectorConfig @@ -91,17 +93,4 @@ 'pg_result' => 'pg_fetch_result', 'pg_setclientencoding' => 'pg_set_client_encoding', ]); - - $rectorConfig - ->ruleWithConfiguration(FunctionArgumentDefaultValueReplacerRector::class, [ - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'gte', 'ge'), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '', '!='), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '!', '!='), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'g', 'gt'), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'l', 'lt'), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'n', 'ne'), - new ReplaceFuncCallArgumentDefaultValue('get_headers', 1, 0, false), - new ReplaceFuncCallArgumentDefaultValue('get_headers', 1, 1, true), - ]); }; diff --git a/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/skip_first_class_callable.php.inc b/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/skip_first_class_callable.php.inc deleted file mode 100644 index bd73d0f451b..00000000000 --- a/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/skip_first_class_callable.php.inc +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/some_function.php.inc b/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/some_function.php.inc deleted file mode 100644 index 40715c5b390..00000000000 --- a/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/some_function.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/version_compare.php.inc b/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/version_compare.php.inc deleted file mode 100644 index 4dbbec111b9..00000000000 --- a/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/version_compare.php.inc +++ /dev/null @@ -1,29 +0,0 @@ - ------ - diff --git a/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/config/configured_rule.php b/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/config/configured_rule.php deleted file mode 100644 index e11407caf02..00000000000 --- a/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/config/configured_rule.php +++ /dev/null @@ -1,21 +0,0 @@ -ruleWithConfiguration(FunctionArgumentDefaultValueReplacerRector::class, [ - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'), - new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '', '!='), - new ReplaceFuncCallArgumentDefaultValue( - 'some_function', - 0, - true, - 'Symfony\Component\Yaml\Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE' - ), - ]); -}; diff --git a/rules-tests/Php80/Rector/FuncCall/GetHeadersArgumentFormatRector/Fixture/format_argument.php.inc b/rules-tests/Php80/Rector/FuncCall/GetHeadersArgumentFormatRector/Fixture/format_argument.php.inc new file mode 100644 index 00000000000..1e77994e441 --- /dev/null +++ b/rules-tests/Php80/Rector/FuncCall/GetHeadersArgumentFormatRector/Fixture/format_argument.php.inc @@ -0,0 +1,29 @@ + +----- + diff --git a/rules-tests/Php80/Rector/FuncCall/GetHeadersArgumentFormatRector/Fixture/skip_get_headers.php.inc b/rules-tests/Php80/Rector/FuncCall/GetHeadersArgumentFormatRector/Fixture/skip_get_headers.php.inc new file mode 100644 index 00000000000..64bd3a426f8 --- /dev/null +++ b/rules-tests/Php80/Rector/FuncCall/GetHeadersArgumentFormatRector/Fixture/skip_get_headers.php.inc @@ -0,0 +1,14 @@ +withRules([GetHeadersArgumentFormatRector::class]); diff --git a/rules-tests/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector/Fixture/operator_aliases.php.inc b/rules-tests/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector/Fixture/operator_aliases.php.inc new file mode 100644 index 00000000000..9b87640e43b --- /dev/null +++ b/rules-tests/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector/Fixture/operator_aliases.php.inc @@ -0,0 +1,39 @@ + +----- + diff --git a/rules-tests/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector/Fixture/skip_version_compare.php.inc b/rules-tests/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector/Fixture/skip_version_compare.php.inc new file mode 100644 index 00000000000..b659c72e194 --- /dev/null +++ b/rules-tests/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector/Fixture/skip_version_compare.php.inc @@ -0,0 +1,14 @@ +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/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector/config/configured_rule.php b/rules-tests/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector/config/configured_rule.php new file mode 100644 index 00000000000..faa6fbfc635 --- /dev/null +++ b/rules-tests/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([VersionCompareFuncCallArgumentRector::class]); diff --git a/rules/Arguments/ArgumentDefaultValueReplacer.php b/rules/Arguments/ArgumentDefaultValueReplacer.php index 1c981749009..579b7899c72 100644 --- a/rules/Arguments/ArgumentDefaultValueReplacer.php +++ b/rules/Arguments/ArgumentDefaultValueReplacer.php @@ -27,6 +27,9 @@ use Rector\Reflection\ReflectionResolver; use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; +/** + * @api + */ final readonly class ArgumentDefaultValueReplacer { public function __construct( diff --git a/rules/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector.php b/rules/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector.php index c62c86554ad..6445e7b1865 100644 --- a/rules/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector.php +++ b/rules/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector.php @@ -6,29 +6,18 @@ use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; -use Rector\Arguments\ArgumentDefaultValueReplacer; use Rector\Arguments\ValueObject\ReplaceFuncCallArgumentDefaultValue; -use Rector\Contract\Rector\ConfigurableRectorInterface; +use Rector\Configuration\Deprecation\Contract\DeprecatedInterface; +use Rector\Exception\ShouldNotHappenException; use Rector\Rector\AbstractRector; use Rector\RuleDoc\CodeSample\ConfiguredCodeSample; use Rector\RuleDoc\RuleDefinition; -use Webmozart\Assert\Assert; /** - * @see \Rector\Tests\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector\FunctionArgumentDefaultValueReplacerRectorTest + * @deprecated This rule is deprecated. Its configuration is weak and relies on magic values. Write a dedicated custom Rector rule instead for type-safe argument replacement. */ -final class FunctionArgumentDefaultValueReplacerRector extends AbstractRector implements ConfigurableRectorInterface +final class FunctionArgumentDefaultValueReplacerRector extends AbstractRector implements DeprecatedInterface { - /** - * @var ReplaceFuncCallArgumentDefaultValue[] - */ - private array $replacedArguments = []; - - public function __construct( - private readonly ArgumentDefaultValueReplacer $argumentDefaultValueReplacer - ) { - } - public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Streamline the operator arguments of `version_compare` function', [ @@ -36,7 +25,6 @@ public function getRuleDefinition(): RuleDefinition <<<'CODE_SAMPLE' version_compare(PHP_VERSION, '5.6', 'gte'); CODE_SAMPLE - , <<<'CODE_SAMPLE' version_compare(PHP_VERSION, '5.6', 'ge'); @@ -58,33 +46,8 @@ public function getNodeTypes(): array /** * @param FuncCall $node */ - public function refactor(Node $node): FuncCall|null - { - $hasChanged = false; - foreach ($this->replacedArguments as $replacedArgument) { - if (! $this->isName($node->name, $replacedArgument->getFunction())) { - continue; - } - - $changedNode = $this->argumentDefaultValueReplacer->processReplaces($node, $replacedArgument); - if ($changedNode instanceof Node) { - $hasChanged = true; - } - } - - if ($hasChanged) { - return $node; - } - - return null; - } - - /** - * @param mixed[] $configuration - */ - public function configure(array $configuration): void + public function refactor(Node $node): ?Node { - Assert::allIsAOf($configuration, ReplaceFuncCallArgumentDefaultValue::class); - $this->replacedArguments = $configuration; + throw new ShouldNotHappenException(sprintf('"%s" rule is deprecated, write a dedicated custom Rector rule instead', self::class)); } } diff --git a/rules/Arguments/ValueObject/ReplaceFuncCallArgumentDefaultValue.php b/rules/Arguments/ValueObject/ReplaceFuncCallArgumentDefaultValue.php index bd16788d7ff..b5a7835457b 100644 --- a/rules/Arguments/ValueObject/ReplaceFuncCallArgumentDefaultValue.php +++ b/rules/Arguments/ValueObject/ReplaceFuncCallArgumentDefaultValue.php @@ -6,6 +6,9 @@ use Rector\Arguments\Contract\ReplaceArgumentDefaultValueInterface; +/** + * @api + */ final readonly class ReplaceFuncCallArgumentDefaultValue implements ReplaceArgumentDefaultValueInterface { public function __construct( diff --git a/rules/Php80/Rector/FuncCall/GetHeadersArgumentFormatRector.php b/rules/Php80/Rector/FuncCall/GetHeadersArgumentFormatRector.php new file mode 100644 index 00000000000..acab3eadce0 --- /dev/null +++ b/rules/Php80/Rector/FuncCall/GetHeadersArgumentFormatRector.php @@ -0,0 +1,86 @@ +> + */ + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node): ?FuncCall + { + if (! $this->isName($node, 'get_headers')) { + return null; + } + + if ($node->isFirstClassCallable()) { + return null; + } + + $formatArg = $node->getArgs()[1] ?? null; + if (! $formatArg instanceof Arg) { + return null; + } + + $format = $this->valueResolver->getValue($formatArg->value); + if ($format === 0) { + $formatArg->value = $this->nodeFactory->createFalse(); + return $node; + } + + if ($format === 1) { + $formatArg->value = $this->nodeFactory->createTrue(); + return $node; + } + + return null; + } + + public function provideMinPhpVersion(): int + { + return PhpVersion::PHP_80; + } +} diff --git a/rules/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector.php b/rules/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector.php new file mode 100644 index 00000000000..cdaf3d3f5ff --- /dev/null +++ b/rules/Php80/Rector/FuncCall/VersionCompareFuncCallArgumentRector.php @@ -0,0 +1,100 @@ + + */ + private const array OPERATOR_MAP = [ + 'gte' => 'ge', + 'lte' => 'le', + '' => '!=', + '!' => '!=', + 'g' => 'gt', + 'l' => 'lt', + 'n' => 'ne', + ]; + + public function __construct( + private readonly ValueResolver $valueResolver + ) { + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Streamline the operator argument of the version_compare() function', [ + new CodeSample( + <<<'CODE_SAMPLE' +version_compare(PHP_VERSION, '5.6', 'gte'); +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +version_compare(PHP_VERSION, '5.6', 'ge'); +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node): ?FuncCall + { + if (! $this->isName($node, 'version_compare')) { + return null; + } + + if ($node->isFirstClassCallable()) { + return null; + } + + $operatorArg = $node->getArgs()[2] ?? null; + if (! $operatorArg instanceof Arg) { + return null; + } + + $operator = $this->valueResolver->getValue($operatorArg->value); + if (! is_string($operator)) { + return null; + } + + if (! isset(self::OPERATOR_MAP[$operator])) { + return null; + } + + $operatorArg->value = new String_(self::OPERATOR_MAP[$operator]); + + return $node; + } + + public function provideMinPhpVersion(): int + { + return PhpVersion::PHP_80; + } +}