diff --git a/config/set/coding-style/coding-style.yaml b/config/set/coding-style/coding-style.yaml index 555b97c54d09..679225987c37 100644 --- a/config/set/coding-style/coding-style.yaml +++ b/config/set/coding-style/coding-style.yaml @@ -29,3 +29,8 @@ services: Rector\CodingStyle\Rector\Class_\AddArrayDefaultToArrayPropertyRector: ~ Rector\CodingStyle\Rector\ClassMethod\MakeInheritedMethodVisibilitySameAsParentRector: ~ Rector\CodingStyle\Rector\FuncCall\CallUserFuncCallToVariadicRector: ~ + Rector\CodingStyle\Rector\FuncCall\VersionCompareFuncCallToConstantRector: ~ + Rector\CodingStyle\Rector\FuncCall\FunctionCallToConstantRector: + $functionsToConstants: + php_sapi_name: PHP_SAPI + pi: M_PI diff --git a/packages/CodingStyle/src/Rector/FuncCall/FunctionCallToConstantRector.php b/packages/CodingStyle/src/Rector/FuncCall/FunctionCallToConstantRector.php new file mode 100644 index 000000000000..e1935c989c1c --- /dev/null +++ b/packages/CodingStyle/src/Rector/FuncCall/FunctionCallToConstantRector.php @@ -0,0 +1,99 @@ +functionsToConstants = $functionsToConstants; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Changes use of function calls to use constants', [ + new CodeSample( + <<<'EOS' +class SomeClass +{ + public function run() + { + $value = php_sapi_name(); + } +} +EOS + , + <<<'EOS' +class SomeClass +{ + public function run() + { + $value = PHP_SAPI; + } +} +EOS + ), + new CodeSample( + <<<'EOS' +class SomeClass +{ + public function run() + { + $value = pi(); + } +} +EOS + , + <<<'EOS' +class SomeClass +{ + public function run() + { + $value = M_PI; + } +} +EOS + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node): ?Node + { + $functionName = $this->getName($node); + if (! $functionName || ! array_key_exists($functionName, $this->functionsToConstants)) { + return null; + } + + return new ConstFetch(new Name($this->functionsToConstants[$functionName])); + } +} diff --git a/packages/CodingStyle/src/Rector/FuncCall/VersionCompareFuncCallToConstantRector.php b/packages/CodingStyle/src/Rector/FuncCall/VersionCompareFuncCallToConstantRector.php new file mode 100644 index 000000000000..564e30d66338 --- /dev/null +++ b/packages/CodingStyle/src/Rector/FuncCall/VersionCompareFuncCallToConstantRector.php @@ -0,0 +1,142 @@ + Identical::class, + '==' => Identical::class, + 'eq' => Identical::class, + '!=' => NotIdentical::class, + '<>' => NotIdentical::class, + 'ne' => NotIdentical::class, + '>' => Greater::class, + 'gt' => Greater::class, + '<' => Smaller::class, + 'lt' => Smaller::class, + '>=' => GreaterOrEqual::class, + 'ge' => GreaterOrEqual::class, + '<=' => SmallerOrEqual::class, + 'le' => SmallerOrEqual::class, + ]; + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Changes use of call to version compare function to use of PHP version constant', [ + new CodeSample( + <<<'EOS' +class SomeClass +{ + public function run() + { + version_compare(PHP_VERSION, '5.3.0', '<'); + } +} +EOS + , + <<<'EOS' +class SomeClass +{ + public function run() + { + PHP_VERSION_ID < 50300; + } +} +EOS + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node): ?Node + { + if (!$this->isName($node, 'version_compare')) { + return null; + } + + if (count($node->args) !== 3) { + return null; + } + + if (!$this->isPhpVersionConstant($node->args[0]->value) && !$this->isPhpVersionConstant($node->args[1]->value)) { + return null; + } + + $left = $this->getNewNodeForArg($node->args[0]->value); + $right = $this->getNewNodeForArg($node->args[1]->value); + + /** @var String_ $operator */ + $operator = $node->args[2]->value; + $comparison = $this->operatorToComparison[$operator->value]; + + return new $comparison($left, $right); + } + + private function isPhpVersionConstant(Expr $value): bool + { + if ($value instanceof ConstFetch && $value->name->toString() === 'PHP_VERSION') { + return true; + } + + return false; + } + + private function getNewNodeForArg(Expr $value): Node + { + if ($this->isPhpVersionConstant($value)) { + return new ConstFetch(new Name('PHP_VERSION_ID')); + } + + return $this->getVersionNumberFormVersionString($value); + } + + private function getVersionNumberFormVersionString(Expr $value): LNumber + { + if (! $value instanceof String_) { + throw new ShouldNotHappenException(); + } + + if (! preg_match('/^\d+\.\d+\.\d+$/', $value->value)) { + throw new ShouldNotHappenException(); + } + + $versionParts = explode('.', $value->value); + + return new LNumber((int) $versionParts[0] * 10000 + (int) $versionParts[1] * 100 + (int) $versionParts[2]); + } +} diff --git a/packages/CodingStyle/tests/Rector/FuncCall/FunctionCallToConstantRector/Fixture/fixture.php.inc b/packages/CodingStyle/tests/Rector/FuncCall/FunctionCallToConstantRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..c9daf5cd58c5 --- /dev/null +++ b/packages/CodingStyle/tests/Rector/FuncCall/FunctionCallToConstantRector/Fixture/fixture.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/packages/CodingStyle/tests/Rector/FuncCall/FunctionCallToConstantRector/FunctionCallToConstantRectorTest.php b/packages/CodingStyle/tests/Rector/FuncCall/FunctionCallToConstantRector/FunctionCallToConstantRectorTest.php new file mode 100644 index 000000000000..ce572941a1db --- /dev/null +++ b/packages/CodingStyle/tests/Rector/FuncCall/FunctionCallToConstantRector/FunctionCallToConstantRectorTest.php @@ -0,0 +1,39 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + yield [__DIR__ . '/Fixture/fixture.php.inc']; + + } + + protected function getRectorsWithConfiguration(): array + { + return [ + FunctionCallToConstantRector::class => [ + '$functionsToConstants' => [ + 'php_sapi_name' => 'PHP_SAPI', + 'pi' => 'M_PI' + ], + ], + ]; + } +} diff --git a/packages/CodingStyle/tests/Rector/FuncCall/VersionCompareFuncCallToConstantRector/Fixture/skip-version-compare.php.inc b/packages/CodingStyle/tests/Rector/FuncCall/VersionCompareFuncCallToConstantRector/Fixture/skip-version-compare.php.inc new file mode 100644 index 000000000000..e3f04fbf9a43 --- /dev/null +++ b/packages/CodingStyle/tests/Rector/FuncCall/VersionCompareFuncCallToConstantRector/Fixture/skip-version-compare.php.inc @@ -0,0 +1,13 @@ +version, '5.3.0', '<'); + } +} + +?> diff --git a/packages/CodingStyle/tests/Rector/FuncCall/VersionCompareFuncCallToConstantRector/Fixture/version-compare.php.inc b/packages/CodingStyle/tests/Rector/FuncCall/VersionCompareFuncCallToConstantRector/Fixture/version-compare.php.inc new file mode 100644 index 000000000000..a14560273291 --- /dev/null +++ b/packages/CodingStyle/tests/Rector/FuncCall/VersionCompareFuncCallToConstantRector/Fixture/version-compare.php.inc @@ -0,0 +1,39 @@ +'); + version_compare(PHP_VERSION, '5.3.0', '='); + + version_compare('5.3.0', PHP_VERSION, 'lt'); + version_compare('5.3.0', PHP_VERSION, 'gt'); + version_compare('5.3.0', PHP_VERSION, 'eq'); + } +} + +?> +----- + 50300; + PHP_VERSION_ID === 50300; + + 50300 < PHP_VERSION_ID; + 50300 > PHP_VERSION_ID; + 50300 === PHP_VERSION_ID; + } +} + +?> diff --git a/packages/CodingStyle/tests/Rector/FuncCall/VersionCompareFuncCallToConstantRector/VersionCompareFuncCallToConstantRectorTest.php b/packages/CodingStyle/tests/Rector/FuncCall/VersionCompareFuncCallToConstantRector/VersionCompareFuncCallToConstantRectorTest.php new file mode 100644 index 000000000000..f22a8a84bdbb --- /dev/null +++ b/packages/CodingStyle/tests/Rector/FuncCall/VersionCompareFuncCallToConstantRector/VersionCompareFuncCallToConstantRectorTest.php @@ -0,0 +1,32 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + yield [__DIR__ . '/Fixture/version-compare.php.inc']; + yield [__DIR__ . '/Fixture/skip-version-compare.php.inc']; + + } + + protected function getRectorClass(): string + { + return VersionCompareFuncCallToConstantRector::class; + } +}