rector/docs/create_own_rule.md
Tomas Votruba d29663ff64 Updated Rector to commit ad7cdc74eeb9379091e99bda90e95f1166c81147
ad7cdc74ee [docs] cleanup file processors, only internal logic, to keep PHP scope more narrow (#2850)
2022-08-29 18:23:56 +00:00

4.0 KiB

3 Steps to Create Your Own Rector

First, make sure it's not covered by any existing Rectors. Let's say we want to change method calls from set* to change*.

 $user = new User();
-$user->setPassword('123456');
+$user->changePassword('123456');

1. Create a New Rector and Implement Methods

Create a class that extends Rector\Core\Rector\AbstractRector. It will inherit useful methods e.g. to check node type and name. See the source (or type $this-> in an IDE) for a list of available methods.

declare(strict_types=1);

namespace Utils\Rector\Rector;

use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Expr\MethodCall;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

final class MyFirstRector extends AbstractRector
{
    /**
     * @return array<class-string<Node>>
     */
    public function getNodeTypes(): array
    {
        // what node types are we looking for?
        // pick any node from https://github.com/rectorphp/php-parser-nodes-docs/
        return [MethodCall::class];
    }

    /**
     * @param MethodCall $node - we can add "MethodCall" type here, because
     *                         only this node is in "getNodeTypes()"
     */
    public function refactor(Node $node): ?Node
    {
        // we only care about "set*" method names
        if (! $this->isName($node->name, 'set*')) {
            // return null to skip it
            return null;
        }

        $methodCallName = $this->getName($node->name);
        $newMethodCallName = Strings::replace($methodCallName, '#^set#', 'change');

        $node->name = new Identifier($newMethodCallName);

        // return $node if you modified it
        return $node;
    }

    /**
     * This method helps other to understand the rule and to generate documentation.
     */
    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition(
            'Change method calls from set* to change*.', [
                new CodeSample(
                    // code before
                    '$user->setPassword("123456");',
                    // code after
                    '$user->changePassword("123456");'
                ),
            ]
        );
    }
}

File Structure

This is how the file structure for custom rule in your own project will look like:

/src/
    /YourCode.php
/utils
    /rector
        /src
            /Rector
                MyFirstRector.php
rector.php
composer.json

Writing test saves you lot of time in future debugging. Here is a file structure with tests:

/src/
    /YourCode.php
/utils
    /rector
        /src
            /Rector
                MyFirstRector.php
        /tests
            /Rector
                /MyFirstRector
                    /Fixture
                        test_fixture.php.inc
                    /config
                        config.php
                    MyFirstRectorTest.php
rector.php
composer.json

Update composer.json

We also need to load Rector rules in composer.json:

{
    "autoload": {
        "psr-4": {
            "App\\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Utils\\Rector\\": "utils/rector/src",
            "Utils\\Rector\\Tests\\": "utils/rector/tests"
        }
    }
}

After adding this to composer.json, be sure to reload Composer's class map:

composer dump-autoload

2. Register It

use Utils\Rector\Rector\MyFirstRector;
use Rector\Config\RectorConfig;

return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->rule(MyFirstRector::class);
};

3. Let Rector Refactor Your Code

The rector.php configuration is loaded by default, so we can skip it.

# see the diff first
vendor/bin/rector process src --dry-run

# if it's ok, apply
vendor/bin/rector process src

That's it!