diff --git a/composer.json b/composer.json index 9c6118b7ceba77cba81d503c8c49e7f2905894df..c6f6f6c34e5ca5e694fc99b19607c88686d82a6b 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "Studip\\Cache\\": "lib/classes/cache/", "Studip\\Calendar\\": "lib/classes/calendar/", "Studip\\Forms\\": "lib/classes/forms/", + "Studip\\Rectors\\": "lib/Rectors/", "Studip\\": [ "lib/classes/", "lib/exceptions/", @@ -73,7 +74,8 @@ "phpstan/phpstan": "^2.0", "symfony/var-dumper": "6.4.7", "maximebf/debugbar": "1.22.3", - "codeception/specify": "^2.0" + "codeception/specify": "^2.0", + "rector/rector": "2.0-rc1" }, "require": { "php": "^8.1", diff --git a/composer.lock b/composer.lock index 7897e22c5d8504ec8ebc26cffd5441c808320524..7d6305153bfbda646b937b8daf4491fd4e061c2b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5bc2e4ef41517f46c14d02cfecdd95d9", + "content-hash": "07482a29c636bb9068a8e831addddca3", "packages": [ { "name": "algo26-matthias/idna-convert", @@ -7044,6 +7044,65 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "rector/rector", + "version": "2.0.0-rc1", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "75f78c934b79a5dd81415683600d85837edef86f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/75f78c934b79a5dd81415683600d85837edef86f", + "reference": "75f78c934b79a5dd81415683600d85837edef86f", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.0.1" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.0.0-rc1" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2024-11-27T18:01:51+00:00" + }, { "name": "sebastian/cli-parser", "version": "1.0.2", @@ -8726,7 +8785,8 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "edu-sharing/auth-plugin": 20 + "edu-sharing/auth-plugin": 20, + "rector/rector": 5 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/lib/Rectors/Studip-6.0-Set.php b/lib/Rectors/Studip-6.0-Set.php new file mode 100644 index 0000000000000000000000000000000000000000..ca019b255472a3251442264a2ddd697124b5403d --- /dev/null +++ b/lib/Rectors/Studip-6.0-Set.php @@ -0,0 +1,51 @@ +<?php +use Rector\Config\RectorConfig; +use Rector\Renaming\Rector\FuncCall\RenameFunctionRector; +use Rector\Renaming\Rector\Name\RenameClassRector; +use Studip\Rectors\Studip60\RemoveFunctionCallRector; +use Studip\Rectors\Studip60\RemoveIncludesRector; + +return RectorConfig::configure() + ->withRules([ + Studip\Rectors\Studip60\RemoveGetConfigRector::class, + Studip\Rectors\Studip60\RemoveSidebarMethodsRector::class + ]) + ->withConfiguredRule(RenameFunctionRector::class, [ + 'studip_json_decode' => 'json_decode', + 'studip_json_encode' => 'json_encode', + ]) + ->withConfiguredRule(RemoveIncludesRector::class, [ + 'vendor/flexi', + 'vendor/trails', + 'app/controllers/authenticated_controller.php', + 'app/controllers/plugin_controller.php', + 'app/controllers/studip_controller.php', + 'app/controllers/studip_controller_properties_trait.php', + 'app/controllers/studip_response.php', + ]) + ->withConfiguredRule(RemoveFunctionCallRector::class, [ + 'smile', + 'transformBeforeSave', + ]) + ->withConfiguredRule(RenameClassRector::class, [ + 'Flexi_PhpTemplate' => 'Flexi\PhpTemplate', + 'Flexi_Template' => 'Flexi\Template', + 'Flexi_TemplateFactory' => 'Flexi\Factory', + + 'StudipCacheFactory' => 'Studip\Cache\Factory', + 'StudipCache' => 'Studip\Cache\Cache', + 'StudipDbCache' => 'Studip\Cache\DbCache', + + 'Trails_Controller' => 'Trails\Controller', + 'Trails_Dispatcher' => 'Trails\Dispatcher', + 'Trails_Exception' => 'Trails\Exception', + 'Trails_Flash' => 'Trails\Flash', + 'Trails_Inflector' => 'Trails\Inflector', + 'Trails_Response' => 'Trails\Response', + 'Trails_DoubleRenderError' => 'Trails\Exceptions\DoubleRenderError', + 'Trails_MissingFile' => 'Trails\Exceptions\MissingFile', + 'Trails_RoutingError' => 'Trails\Exceptions\RoutingError', + 'Trails_SessionRequired' => 'Trails\Exceptions\SessionRequiredException', + 'Trails_UnknownAction' => 'Trails\Exceptions\UnknownAction', + 'Trails_UnknownController' => 'Trails\Exceptions\UnknownController', + ]); diff --git a/lib/Rectors/Studip60/RemoveFunctionCallRector.php b/lib/Rectors/Studip60/RemoveFunctionCallRector.php new file mode 100644 index 0000000000000000000000000000000000000000..1ee6180fc8cad555dfa0c1bdba0a106b77672e3c --- /dev/null +++ b/lib/Rectors/Studip60/RemoveFunctionCallRector.php @@ -0,0 +1,43 @@ +<?php +declare(strict_types=1); + +namespace Studip\Rectors\Studip60; + +use PhpParser\Node; +use PhpParser\Node\Expr\FuncCall; +use Rector\Contract\Rector\ConfigurableRectorInterface; +use Rector\Rector\AbstractRector; +use RectorPrefix202411\Webmozart\Assert\Assert; + +final class RemoveFunctionCallRector extends AbstractRector implements ConfigurableRectorInterface +{ + private array $removes = []; + + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + public function refactor(Node $node) + { + if (!$this->isNames($node, $this->removes)) { + return null; + } + + if (!isset($node->args[0])) { + return null; + } + + return $node->args[0]->value; + } + + /** + * @param mixed[] $configuration + */ + public function configure(array $configuration): void + { + Assert::allString($configuration); + Assert::isList($configuration); + $this->removes = $configuration; + } +} diff --git a/lib/Rectors/Studip60/RemoveGetConfigRector.php b/lib/Rectors/Studip60/RemoveGetConfigRector.php new file mode 100644 index 0000000000000000000000000000000000000000..da77a269ff9c8337f2e21ae2e1e913751a6a4d5d --- /dev/null +++ b/lib/Rectors/Studip60/RemoveGetConfigRector.php @@ -0,0 +1,59 @@ +<?php +namespace Studip\Rectors\Studip60; + +use PhpParser\Node; +use PhpParser\Node\Expr\FuncCall; +use Rector\PhpParser\Node\Value\ValueResolver; +use Rector\Rector\AbstractRector; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; + +final class RemoveGetConfigRector extends AbstractRector +{ + private ValueResolver $valueResolver; + + public function __construct(ValueResolver $valueResolver) + { + $this->valueResolver = $valueResolver; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Replace calls to function "get_config()" with calls to "Config::get()->getValue()', + [ + new CodeSample( + '$value = get_config(\'FOO_BAR\');', + '$value = Config::get()->getValue(\'FOO_BAR\');' + ) + ] + ); + } + + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node) + { + if (!$this->isName($node->name, 'get_config')) { + return $node; + } + + return $this->nodeFactory->createMethodCall( + $this->nodeFactory->createStaticCall( + \Config::class, + 'get' + ), + 'getValue', + array_map( + fn($arg) => $this->valueResolver->getValue($arg), + $node->getArgs() + ) + ); + } +} diff --git a/lib/Rectors/Studip60/RemoveIncludesRector.php b/lib/Rectors/Studip60/RemoveIncludesRector.php new file mode 100644 index 0000000000000000000000000000000000000000..8ba8f92dd5008f040985027053fe57dfeaeb7640 --- /dev/null +++ b/lib/Rectors/Studip60/RemoveIncludesRector.php @@ -0,0 +1,60 @@ +<?php +declare(strict_types=1); + +namespace Studip\Rectors\Studip60; + +use PhpParser\Node; +use PhpParser\Node\Expr\Include_; +use PhpParser\Node\Scalar\String_; +use PhpParser\Node\Stmt\Expression; +use PhpParser\NodeVisitor; +use Rector\Contract\Rector\ConfigurableRectorInterface; +use Rector\Rector\AbstractRector; +use RectorPrefix202411\Webmozart\Assert\Assert; + +final class RemoveIncludesRector extends AbstractRector implements ConfigurableRectorInterface +{ + private array $removeIncludes = []; + + public function getNodeTypes(): array + { + return [Expression::class]; + } + + /** + * @param Expression $node + */ + public function refactor(Node $node): ?int + { + if (!$node->expr instanceof Include_) { + return null; + } + + if (!$this->matches($node->expr->expr)) { + return null; + } + + return self::REMOVE_NODE; + } + + /** + * @param mixed[] $configuration + */ + public function configure(array $configuration): void + { + Assert::allString($configuration); + Assert::isList($configuration); + $this->removeIncludes = $configuration; + } + + private function matches(String_ $expr): bool + { + foreach ($this->removeIncludes as $removeInclude) { + if (str_contains($expr->value, $removeInclude)) { + return true; + } + } + + return false; + } +} diff --git a/lib/Rectors/Studip60/RemoveSidebarMethodsRector.php b/lib/Rectors/Studip60/RemoveSidebarMethodsRector.php new file mode 100644 index 0000000000000000000000000000000000000000..52256880848836d29a9d81ad5ab495ca5dc91f4e --- /dev/null +++ b/lib/Rectors/Studip60/RemoveSidebarMethodsRector.php @@ -0,0 +1,61 @@ +<?php +// TODO: Assignment! +declare(strict_types=1); + +namespace Studip\Rectors\Studip60; + +use PhpParser\Node; +use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Stmt\Expression; +use PhpParser\NodeVisitor; +use Rector\Rector\AbstractRector; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; + +final class RemoveSidebarMethodsRector extends AbstractRector +{ + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Remove deprecated sidebar methods', + [ + new CodeSample( + <<<'CODE_SAMPLE' +Sidebar::setImage('foo.gif'); +$foo = Sidebar::getImage(); +Sidebar::removeImage(); +CODE_SAMPLE, + '' + ) + ] + ); + } + + public function getNodeTypes(): array + { + return [Expression::class]; + } + + /** + * @param Expression $node + * @return int|null + */ + public function refactor(Node $node): ?int + { + $expr = $node->expr; + if (!$expr instanceof StaticCall) { + return null; + } + + if (!$this->isName($expr->class, 'Sidebar')) { + return null; + } + + $methodsToRemove = ['setImage', 'getImage', 'removeImage']; + if ($this->isNames($expr->name, $methodsToRemove)) { + return NodeVisitor::REMOVE_NODE; + } + + return null; + } +} diff --git a/rector-test.php b/rector-test.php new file mode 100644 index 0000000000000000000000000000000000000000..046b79a519a7c041a82e60aaee7cbb568a1a73f1 --- /dev/null +++ b/rector-test.php @@ -0,0 +1,53 @@ +<?php + +include 'vendor/flexi/lib/flexi.php'; +require_once 'vendor/trails/trails.php'; + +include_once 'app/controllers/studip_controller.php'; +require 'app/controllers/plugin_controller.php'; +require 'app/controllers/studip_controller_properties_trait.php'; +require 'app/controllers/studip_response.php'; + +$foo = get_config('FOO_BAR');; +$foo = studip_json_encode($foo); +$foo = studip_json_decode($foo, true); +echo transformBeforeSave(smile($foo)); + +Sidebar::setImage('foo.gif'); +$bar = Sidebar::getImage(); +Sidebar::removeImage(); + +/** @var StudipCache $cache */ +$cache = StudipCacheFactory::getCache(); +if ($cache instanceof StudipDbCache) { + echo 'Cached in database'; +} + +$factory = new Flexi_TemplateFactory(__DIR__); +/** @var Flexi_Template $template */ +$template = $factory->open('foo.php'); +if ($template instanceof Flexi_PhpTemplate) { + echo 'Template is php'; +} + +try { + $dispatcher = new Trails_Dispatcher('', '', ''); + $controller = new Trails_Controller($dispatcher); + $flash = new Trails_Flash(); + $inflector = new Trails_Inflector(); +} catch (Trails_DoubleRenderError $e) { + echo 'double render'; +} catch (Trails_MissingFile $e) { + echo 'missing file'; +} catch (Trails_RoutingError $e) { + echo 'routing error'; +} catch (Trails_SessionRequiredException $e) { + echo 'session required'; +} catch (Trails_UnknownAction $e) { + echo 'unknown action'; +} catch (Trails_UnknownController $e) { + echo 'unknown controller'; +} catch (Trails_Exception $e) { + echo 'some exception'; +} + diff --git a/rector.php b/rector.php new file mode 100644 index 0000000000000000000000000000000000000000..4f025745635becaa5acd33592a44052cf4b61a29 --- /dev/null +++ b/rector.php @@ -0,0 +1,29 @@ +<?php + +declare(strict_types=1); + +use Rector\Config\RectorConfig; + +return RectorConfig::configure() + ->withPaths([ + __DIR__ . '/app/controllers', + __DIR__ . '/cli', + __DIR__ . '/config', + __DIR__ . '/db', + __DIR__ . '/lib', + __DIR__ . '/public/*.php', + __DIR__ . '/tests', + ]) + ->withSkip([ + __DIR__ . '/tests/_data', + __DIR__ . '/tests/_support', + ]) + ->withSets([ + __DIR__ . '/lib/Rectors/Studip-6.0-Set.php' + ]) + // uncomment to reach your current PHP version + ->withPhpSets() +// ->withTypeCoverageLevel(0) +// ->withDeadCodeLevel(0) +// ->withCodeQualityLevel(0) +;