🍖

Rector で始める自動リファクタリング入門

2020/11/01に公開
3

はじめに

みなさん, リファクタリングはしていますでしょうか。
リファクタリングと一口に言っても様々な性格のものが存在しますが, その中でもある一定のルールに基づく機械的な作業が要求されるものがあるかと思います。

例えば,

  • 全ての関数 A を関数 B に変えたい
  • クラス A を継承している全てのクラスの親クラスをクラス B に変えたい
  • コンストラクタ内でクラスをインスタンス化している処理を DI パターンに変えたい

などなど……。

小規模なものであれば手作業でおこなっても問題のないことが多いですが, 大規模になるとうっかりミスが発生しがちだったり単純作業ゆえの精神的な苦痛が伴うこともあったりなど, あまり好ましくありません。

本記事で紹介する Rector はそういった機械的な作業からエンジニアを開放するとてもすごいツールです。

是非本記事で Rector の基本的な使い方を覚え, 日々の業務を楽にしてみてください。

サンプルプロジェクトの構築

本記事では予め用意されたサンプルコードに対して Rector を実際に実行することで理解を深めていきたいと思います。

推奨環境

git clone

作業ディレクトリから以下のコマンドを実行しサンプルプロジェクトをローカル環境に構築します。

$ git clone git@github.com:YAhiru/rector-tutorial.git
$ cd rector-tutorial

サンプルプロジェクトの確認

サンプルプロジェクトは以下の構成になっています。

.
├── rector    # Rector 用のディレクトリ
│   ├── src   # 記事の終盤で作成する独自ルール用のディレクトリ
│   └── tests # 独自ルールのテストディレクトリ
├── src       # サンプルのアプリケーションコードディレクトリ
└── tests     # サンプルのアプリケーションコードのテストディレクトリ

理由は後述しますが Rector 用のディレクトリとサンプルのアプリケーションコードを分けています

ここでいうアプリケーションコードとは Web アプリケーションであればコントローラーやエンティティなどに当たるもので, リファクタリングの対象となるコードです。
今回は src ディレクトリに単純なクラスを 1 つだけ用意しています。

rector ディレクトリは本記事の後半で実装する独自ルールのためのディレクトリとなっていますので, 最低限の設定のみでほぼ空の状態となっています。

インストール

Rector は composer req --dev rector/rector を実行して利用する他に phardocker などの形式が用意されています。
Rector の依存関係 を確認するとわかる通り依存するライブラリが非常に多いため, ルールを自作したいなどのニーズがない場合は phar あるいは docker 形式での利用をおすすめします

今回は最終的にルールを自作したいため rector/rector を composer 経由でダウンロードするわけですが, 前述の通り依存関係が多いため既存のプロダクトにインストールすることができないといった問題の発生が予想されます。
そういった場合に有効なのが Rector 用のサブディレクトリを作成しその中で Rector 関連のコードを完結させることです。[1]
今回のサンプルプロジェクトでもそのアプローチを採用しています。

また、その他の構成については こちらの記事 も非常に参考になります。

では, 以下のコマンドで Rector のインストールをします。

$ cd rector
$ composer req rector/rector

ルールを適用してみる

それでは Rector を試していきたいと思います。
今回自動リファクタリングをしてみるファイルは src/User.php です。

src/User.php
final class User
{
    /** @var string */
    private $screenName;
    /** @var int */
    private $age;

    public function __construct(string $screenName, int $age)
    {
        $this->screenName = $screenName;
        $this->age = $age;
    }

    // ...
}

User クラスのプロパティである $screenName$age を見てみると Typed Property が適用されていないことがわかります。
Typed Property が適用されていない場合 PHP Doc に記述された型以外が代入された場合でもエラーが発生せず, 思わぬバグを引き起こす可能性を秘めています。
あなたの所属するチーム内でこのことが問題視されたという設定で, Rector を使ってこのクラスを自動リファクタリングしてみましょう。

Rector を実行する

Rector の実行は簡単で vendor/bin/rector process {{ディレクトリ}} --set {{ルールセット}} のように, process コマンドに対してターゲットとなるディレクトリや適用したい ルールセット, ルール を渡せば良いだけです。

名前 説明
ルール リファクタリングの内容が定義された PHP のクラス
ルールセット 関心ごとが近い複数のルールがまとめられたもの

今回は Typed Property だけを適用してくれればよいので, php74 というルールセットの中にある Rector\Php74\Rector\Property\TypedPropertyRector というルールのみを適用したいと思います。

rector ディレクトリ内で以下のコマンドを実行してみましょう。

rector/
$ vendor/bin/rector process ../src \
    --set php74 \
    --only 'Rector\Php74\Rector\Property\TypedPropertyRector'

修正されたファイルを確認する

コマンドを実行するとそれっぽさのある出力がなされたかと思いますが, 実際の User クラスを確認してみると以下のように $screenName$age に Typed Property が適用されていることがわかります。

src/User.php
final class User
{
    private string $screenName;
    private int $age;

    public function __construct(string $screenName, int $age)
    {
        $this->screenName = $screenName;
        $this->age = $age;
    }

    // ...
}

どうですか!!!!!!!!すごくないですか!?!?!?!?!?!?!?!?(唐突な興奮)

Rector を使ってリファクタリングを自動化することで修正規模によっては工数を大幅に削減することが出来るため, あなたの上司もニッコリです。

ルールの自作

リファクタリングの要件はプロダクトによって様々なので Rector が用意してくれているルールだけでは要件を満たせないことがあります。
そういった場合に有効なのがルールの自作です。

独自のルールを作ることが出来ればプロダクト固有の要件であってもリファクタリングを自動化することができます。

今回はあなたの所属するチーム内で「PHPUnit のテストメソッドは, メソッド名に test という prefix を付ける形式ではなく @test アノテーションを使う形式にしたい」という要望が出たという設定で進めていきましょう。[2]

独自ルールのテストを書く

早速独自のルールを作っていきたいのですが, まずはどのようなコードをどのようにリファクタリングしたいのかということを明確にする必要があります。
Rector はテスト基盤も整っていますので, テストを書くことでルールのイメージを固めていきましょう。

ルールのテストでは必要なものが 2 つあります。

  1. テストクラス
  2. Fixture

テストクラス

ここでいうテストクラスとは PHPUnit\Framework\TestCase を継承した任意のクラスのことです。
Rector には PHPUnit\Framework\TestCase を拡張した Rector\Core\Testing\PHPUnit\AbstractRectorTestCase [3] が用意されており, これによってルールのテストがとても簡単になるため今回はこれを利用します。

rector ディレクトリ内で以下のコマンドを実行してテストクラス用のファイルを作成します。

rector/
$ mkdir -p tests/AddTestAnnotationRector
$ touch tests/AddTestAnnotationRector/AddTestAnnotationRectorTest.php

ファイルが作成されたら, ファイルに以下の内容を書き込みます。

rector/tests/AddTestAnnotationRector/AddTestAnnotationRectorTest.php
<?php
declare(strict_types=1);
namespace Yahiru\RectorTutorialRector\AddTestAnnotationRector;

use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
use Yahiru\RectorTutorialRector\AddTestAnnotationRector;

final class AddTestAnnotationRectorTest extends AbstractRectorTestCase
{
    /**
     * @dataProvider provideData()
     */
    public function test(SmartFileInfo $fileInfo) : void
    {
        $this->doTestFileInfo($fileInfo);
    }

    /**
     * @return Iterator<SmartFileInfo>
     */
    public function provideData() : Iterator
    {
        return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
    }

    protected function getRectorClass() : string
    {
        return AddTestAnnotationRector::class;
    }
}

getRectorClass()テスト対象となるルールのクラス名を返却します。

rector/tests/AddTestAnnotationRector/AddTestAnnotationRectorTest.php
    protected function getRectorClass(): string
    {
        return AddTestAnnotationRector::class;
    }

ここで返却したルールを Rector\Core\Testing\PHPUnit\AbstractRectorTestCase 内でよしなに扱ってくれます。
AddTestAnnotationRector クラスはまだ存在していませんが, テストを実装した後に作成する予定です。

provideData() はこの後作成する予定の Fixture ファイルを読み込み, test() は読み込まれた Fixture を利用してテストを実行します。

rector/tests/AddTestAnnotationRector/AddTestAnnotationRectorTest.php
    /**
     * @dataProvider provideData()
     */
    public function test(SmartFileInfo $fileInfo): void
    {
        $this->doTestFileInfo($fileInfo);
    }

    /**
     * @return Iterator<SmartFileInfo>
     */
    public function provideData(): Iterator
    {
        return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
    }

Fixture

Fixture とは, 以下のフォーマットで リファクタリング前のコードリファクタリング後のコード が記述されたファイルのことです。

<?php
リファクタリング前のコード
?>
-----
<?php
リファクタリング後のコード
?>

実際の Fixture を見た方が理解が早いので, 早速 Fixture を作ってみましょう。
rector ディレクトリ内で以下のコマンドを実行してファイルを作成します。

rector/
$ mkdir -p tests/AddTestAnnotationRector/Fixture
$ touch tests/AddTestAnnotationRector/Fixture/fixture.php.inc

今回は test という prefix がついているテストメソッドを @test アノテーション形式に変換するというのが要件ですので, 作成したファイルに以下の内容を書き込みます。

rector/tests/AddTestAnnotationRector/Fixture/fixture.php.inc
<?php
namespace Yahiru\RectorTutorialRector\AddTestAnnotationRector\Fixture;

class SomeTest extends \PHPUnit\Framework\TestCase
{
    public function testSome() : void
    {
        // do test
    }
}

?>
-----
<?php
namespace Yahiru\RectorTutorialRector\AddTestAnnotationRector\Fixture;

class SomeTest extends \PHPUnit\Framework\TestCase
{
    /**
     * @test
     */
    public function some() : void
    {
        // do test
    }
}

?>

書き込んだ内容が前述のフォーマット通りになっていることを確認してください。
使用しているエディターによっては上記のコードをコピペした際にリファクタリング前と後の区切りを表す ----- の直前にスペースが入り込むことがあるので注意してください。

作成した Fixture の差分は以下の通りで, テストメソッドが @test アノテーション形式に変わっています。

+   /**
+    * @test
+    */
+   public function some() : void
-   public function testSome() : void
    {
        // do test
    }

注意点

Fixture を作成する上での重要な注意点ですが, Rector はリファクタリング対象のコードを動的に読み込みます

つまり今作成した Fixture に定義されているクラスも読み込まれるということなので, Fixture にうっかり namespace を書き忘れてしまうと, 他のテストの Fixture や既存クラスとクラス名が衝突する可能性が増します

なので絶対に Fixture には namespace を書くようにしましょう。

Fixture を複数用意する

テストの準備自体はこれで完了ですが, テスト項目が足りていないのでもう少し Fixture を増やしたいと思います。

今回の独自ルールにおいて, メソッドがリファクタリング対象であると判断する基準は以下とします。

  1. クラスが PHPUnit\Framework\TestCase を継承していること
  2. メソッド名が test で始まっていること
  3. メソッドの可視性が public であること

正常系は先ほどの Fixture でテストできているので, 次は上記の条件以外ではリファクタリングされないことを保証する Fixture を追加します。

rector ディレクトリ内で以下のコマンドを実行してファイルを作成してください。

rector/
$ touch tests/AddTestAnnotationRector/Fixture/{no-test-prefix.php.inc,not-public-method.php.inc,not-test-class.php.inc}

コードの変更がない場合の Fixture はリファクタリング後のコードを省くことが可能ですので, それぞれのファイルに以下の内容を書き込みんでください。

rector/tests/AddTestAnnotationRector/Fixture/no-test-prefix.php.inc
<?php
namespace Yahiru\RectorTutorialRector\AddTestAnnotationRector\Fixture;

class NotTestPrefix extends \PHPUnit\Framework\TestCase
{
    public function noPrefix() : void
    {
    }
}
rector/tests/AddTestAnnotationRector/Fixture/not-public-method.php.inc
<?php
namespace Yahiru\RectorTutorialRector\AddTestAnnotationRector\Fixture;

class NotPublicMethodTest extends \PHPUnit\Framework\TestCase
{
    protected function testProtected() : void
    {
    }

    private function testPrivate() : void
    {
    }
}
rector/tests/AddTestAnnotationRector/Fixture/not-test-class.php.inc
<?php
namespace Yahiru\RectorTutorialRector\AddTestAnnotationRector\Fixture;

class NotTest
{
    public function testSome() : void
    {
    }
}

Fixture の準備はこれで完了です。
蛇足ですが, テストしたい内容を明確にするために Fixture は適切な粒度で分けることを意識すると良いでしょう。

独自ルールを作る

次は独自ルールを作成します。
ルールは基本的に Rector\Core\Rector\AbstractRector を継承して作成します。

rector ディレクトリ内で以下のコマンドを実行してファイルを作成してください。

rector/
$ touch src/AddTestAnnotationRector.php

作成したファイルに以下の内容を書き込んでください。

rector/src/AddTestAnnotationRector.php
<?php
declare(strict_types=1);
namespace Yahiru\RectorTutorialRector;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PHPUnit\Framework\TestCase;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\NodeTypeResolver\Node\AttributeKey;

final class AddTestAnnotationRector extends AbstractRector
{
    public function getDefinition() : RectorDefinition
    {
        return new RectorDefinition('Add @test annotation', [
            new CodeSample(
                <<<'CODE_SAMPLE'
                class SomeTest extends \PHPUnit\Framework\TestCase
                {
                    public function testSome() : void
                    {
                        // do test
                    }
                }
                CODE_SAMPLE
                ,
                <<<'CODE_SAMPLE'
                class SomeTest extends \PHPUnit\Framework\TestCase
                {
                    /**
                     * @test
                     */
                    public function some() : void
                    {
                        // do test
                    }
                }
                CODE_SAMPLE
            ),
        ]);
    }

    /**
     * @phpstan-return array<class-string<Node>>
     *
     * @return array<string>
     */
    public function getNodeTypes() : array
    {
        return [ClassMethod::class];
    }

    /**
     * @param ClassMethod $node
     */
    public function refactor(Node $node) : ?Node
    {
        if (! $this->shouldRefactor($node)) {
            return null;
        }

        $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
        if ($phpDocInfo === null) {
            $phpDocInfo = $this->phpDocInfoFactory->createEmpty($node);
        }

        $phpDocInfo->addBareTag('@test');

        $testName = \lcfirst(
            (string) \preg_replace('/\Atest/', '', (string) $this->getName($node))
        );
        $node->name = new Node\Identifier($testName);

        return $node;
    }

    private function shouldRefactor(ClassMethod $node) : bool
    {
        $class = $node->getAttribute(AttributeKey::CLASS_NODE);

        return $node->isPublic()
            && $this->isName($node, 'test*')
            && $this->isObjectType($class, TestCase::class);
    }
}

getDefinition

getDefinition()リファクタリング前後のイメージを返します。

rector/src/AddTestAnnotationRector.php
    public function getDefinition() : RectorDefinition
    {
        return new RectorDefinition('Add @test annotation', [
            new CodeSample(
                <<<'CODE_SAMPLE'
                class SomeTest extends \PHPUnit\Framework\TestCase
                {
                    public function testSome() : void
                    {
                        // do test
                    }
                }
                CODE_SAMPLE
                ,
                <<<'CODE_SAMPLE'
                class SomeTest extends \PHPUnit\Framework\TestCase
                {
                    /**
                     * @test
                     */
                    public function some() : void
                    {
                        // do test
                    }
                }
                CODE_SAMPLE
            ),
        ]);
    }

独自ルールにおいて getDefinition に正確な内容を記述することは必須ではないですが, ルールの利用者がルールの性質を理解する手助けとなるので書いておいて損はないでしょう。

getNodeTypes

getNodeTypes()リファクタリング対象の Node クラスの配列を返却します。

rector/src/AddTestAnnotationRector.php
    public function getNodeTypes() : array
    {
        return [ClassMethod::class];
    }

Node とは Rector 内部で使用されている nikic/php-parser によって AST (抽象構文木) にパースされた結果の各要素のことです。

今回はクラスメソッド以外には興味がないので PhpParser\Node\Stmt\ClassMethod だけを指定しています。
Node の一覧はこちらから確認できます。

refactor

refactor() はルールの核となるメソッドです。
このメソッドに渡された $node を書き換えることでその内容がファイルに反映されます
また, スキップする場合は null を返却します。

rector/src/AddTestAnnotationRector.php
    /**
     * @param ClassMethod $node
     */
    public function refactor(Node $node) : ?Node
    {
        if (! $this->shouldRefactor($node)) {
            return null;
        }

        // do refactor

        return $node;
    }

今回は getDefinition()PhpParser\Node\Stmt\ClassMethod のみを指定しているため, Rector によって refactor() には PhpParser\Node\Stmt\ClassMethod のインスタンスのみが渡されることが保証されています。

なので PHPDoc は@param PhpParser\Node\Stmt\ClassMethod $nodeとし, $nodePhpParser\Node\Stmt\ClassMethod のインスタンスであることを前提にロジックを組み立てて構いません。

rector/src/AddTestAnnotationRector.php
    public function refactor(Node $node) : ?Node
    {
        // こういうことはしなくて良い
        if (! $node instanceof ClassMethod) {
            return null;
        }
    }

クラスメソッドであれば何でもリファクタリングしていいというわけではないので, refactor() の最初にブロック文を実装しています。

rector/src/AddTestAnnotationRector.php
    public function refactor(Node $node) : ?Node
    {
        if (! $this->shouldRefactor($node)) {
            return null;
        }

        // ...
    }

    private function shouldRefactor(ClassMethod $node) : bool
    {
        // クラスメソッドが実装されているクラス情報を取得
        $class = $node->getAttribute(AttributeKey::CLASS_NODE);

        return
            // メソッドの可視性が public かどうか
            $node->isPublic()
            // メソッド名が `test` から始まっているかどうか
            && $this->isName($node, 'test*')
            // メソッドが実装されているクラスが `PHPUnit\Framework\TestCase` を継承しているかどうか
            && $this->isObjectType($class, TestCase::class);
    }

shouldRefactor() はリファクタリングをすべきかどうかを判断するために実装したメソッドで, AddTestAnnotationRector クラス固有のものです。

ブロック文を抜けたらコードの修正に相当するロジックを実行し $node に変更を加えます。

rector/src/AddTestAnnotationRector.php
    public function refactor(Node $node) : ?Node
    {
        // ...

        $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
        if ($phpDocInfo === null) {
            $phpDocInfo = $this->phpDocInfoFactory->createEmpty($node);
        }

        $phpDocInfo->addBareTag('@test');

        $testName = \lcfirst(
            (string) \preg_replace('/\Atest/', '', (string) $this->getName($node))
        );
        $node->name = new Node\Identifier($testName);

        return $node;
    }

まず最初に PHP Doc に @test アノテーションを追加する処理をしています。

rector/src/AddTestAnnotationRector.php
// メソッドの PHP Doc を取得
$phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
if ($phpDocInfo === null) {
    // PHP Doc がない場合は空の PHP Doc を作成する
    $phpDocInfo = $this->phpDocInfoFactory->createEmpty($node);
}

$phpDocInfo->addBareTag('@test'); // PHP Doc に `@test` タグを追加する

次に $this->getName($node) で取得したメソッド名から新しいメソッド名を生成し, 設定し直しています。

rector/src/AddTestAnnotationRector.php
// 元のメソッド名から `test` prefix を削除し, 1文字目を小文字に変更した文字列
$testName = \lcfirst(
    (string) \preg_replace('/\Atest/', '', (string) $this->getName($node))
);
// `$testName` を新しいメソッド名として設定しなおす
$node->name = new Node\Identifier($testName);

最後に $node を返却することで変更を Rector に知らせて完了です。

rector/src/AddTestAnnotationRector.php
return $node;

refactor() の挙動をより理解したい場合は nikic/php-parserWalking the AST を読むことをオススメします。

作成したルールをテストする

それではルールが問題なく実装できているか確認しましょう。
rector ディレクトリ内で以下のコマンドを実行して先ほど作成したテストを走らせてください。

rector/
$ composer test

エラーが発生していなければ OK です。
テストが落ちた場合は Fixture の内容が記事の内容と一致しているか改めて確認してください。

独自ルールを実行する

自作したルールを実行するためには, コンフィグから Rector にルールを登録する必要があります。

rector ディレクトリ内で以下のコマンドを実行してコンフィグファイルを作成してください。

rector/
$ vendor/bin/rector init

rector.php というファイルが rector ディレクトリに作成されたかと思うので, 作成されたファイルを以下の内容で書き換えてください。

rector/rector.php
<?php
declare(strict_types=1);

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator) : void {
    $services = $containerConfigurator->services();

    $services->set(\Yahiru\RectorTutorialRector\AddTestAnnotationRector::class);
};

ContainerConfigurator についての詳しい使い方は Symfony のドキュメント を参照していただければと思いますが, $services->set() の部分でルールを Rector に登録しています。

config の作成が完了したら以下のコマンドで Rector を実行します。
config ファイルで登録されたルールはオプションで渡さなくても有効になるので, 引数はディレクトリの指定のみで OK です。

rector/
$ vendor/bin/rector process ../tests

それっぽさのある出力がなされたかと思いますが, 実際に変更されたファイルを見てみましょう。
tests/UserTest.php に以下のような差分が発生していれば成功です!

tests/UserTest.php
+   /**
+    * @test
+    */
+   public function getScreenName() : void
-   public function testGetScreenName() : void
    {
        $this->assertSame('test user', $this->user->getScreenName());
    }

+   /**
+    * @test
+    */
+   public function getAge() : void
-   public function testGetAge() : void
    {
        $this->assertSame(20, $this->user->getAge());
    }
}

Next Step

駆け足でいろいろと解説しましたが, 意外と簡単そうだなという感想を抱いた方も多いのではないでしょうか。
ここまでの知識で自力でルールを作ることも可能になっているかと思われますので, 是非次はご自身でルールを自作してみてください。
きっとより理解を深めることが出来ると思います!!

...とはいえ, そんなに都合よく作りたいルールがあるとは限りませんよね。
その場合は今回作成したルールをさらに改善してみてください。

実は本記事で作成したルールは, 現状の実装のままではあまり好ましくない挙動をするケースがあります。
たとえば以下のようなテストメソッドの場合です。

/**
 * @test
 */
public function testFoo() : void
{
    // do test
}

このようなテストメソッドに今回作成したルールを適用すると PHP Doc に @test アノテーションが重複して書き込まれてしまいます
なので, 既に @test アノテーションがある場合は PHP Doc に変更を加えないような修正をしてみましょう。

独自ルールを作る際のコツ

本記事では最低限の説明になってしまっているので, 実際にルールを自作するとなると戸惑ってしまうことも多いかと思います。
しかも Rector のドキュメントはルールを自作するための情報が少なめのためドキュメントに頼ることもできません。[4]

そこで, ルールを自作するときに知っていると便利なことをいくつか共有したいと思います。

やりたい内容に近いことを行っているルールを探す

ドキュメントがあまりないので最良の教材は既存ルールのコードということになります。
幸い Rector には600を超えるルールが既に実装されているので, ドキュメントがなくとも何とかなります

探し方としては, ルール一覧 からページ内検索をするのが早いと思います。
ルール一覧にはリファクタリング前後の差分が PHP コードとして掲載されているので, それを手掛かりにすると簡単に見つけることができます。

たとえば PHP Doc 関連のリファクタリングがしたい場合は, 上記のページからページ内検索/** のような PHP Doc っぽい文字列で検索してみると案外簡単に見つかります
あまりにも大量にヒットしてしまう場合は差分の表現である +- とセットで +␣␣␣␣/** などと検索をすることで検索結果を絞ることができます。( は半角スペースに読み替えてください)

お目当てのルールが見つかったあとはそのルールのコードを読むことで大体それっぽいことが出来るようになると思います。

よく使うメソッド・クラス

よく使うメソッドやクラスをパッと思いついた範囲で共有します。
この他には既存のルールのコードを読むことで便利なメソッドをいろいろと発見できるかと思われます。

クラス・メソッド 説明
Rector\Core\Rector\AbstractRecto::isName() 第一引数の Node の名前が 第二引数の文字列と一致するか判定してくれる。第二引数には正規表現も使える
Rector\Core\Rector\AbstractRecto::getName() 第一引数に渡した Node の名前を返してくれる
Rector\Core\Rector\AbstractRecto::getShortName() FQN を渡すとショートネームを返してくれる
Rector\Core\Rector\AbstractRecto::isObjectType() 第一引数に渡した Node が第二引数のクラスとマッチするか判定してくれる(Class_ など型情報を持つ Node に使う)
PhpParser\NodeAbstract::getAttribute() Rector\NodeTypeResolver\Node\AttributeKey の定数を使うことで様々な情報を Node から取得できる
Rector\Core\PhpParser\Node\BetterNodeFinder::find*() Node の配列と検索条件を渡すと, 検索条件にマッチする Node だけ返してくれる
Rector\Core\Rector\AbstractPHPUnitRector PhpUnit 関連のルールを作るときに便利。今回作ったルールのテストメソッドの判定ルールも実はこのクラスで既に実装されてる。
Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo::hasByName() 引数に渡した文字列にマッチするタグが PhpDoc に含まれているかどうか判定してくれる
Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo::getTagsByName() 引数に渡した文字列にマッチするタグを取得する

参考になるリンク一覧

おわりに

Rector はすごい!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

筆者が所属する会社では Codeception のテストを PHPUnit に変換するルールを実装するなどして Rector の恩恵を受けています。
みなさんも Rector を活用してリファクタリングを楽にしていきましょう!

脚注
  1. アドバイス ありがとうございます!! ↩︎

  2. あくまで記事の都合上の話であり @test アノテーションを推奨しているわけではありません。 ↩︎

  3. 継承関係には Symplify\PackageBuilder\Tests\AbstractKernelTestCase なども含まれていますが本筋から逸れるためスキップしています。 ↩︎

  4. これしかない ↩︎

Discussion

IsanaIsana

大変参考になる記事でした。ありがとうございます。

ところで、2022年6月現在、独自ルールの作製手順にrector generateコマンドが登場したり、リファクタリング用のメソッドが一部変更されていたりとハンズオン部分の内容が古くなってしまっています。

もし差し支えなければAddTestAnnotationRector.phpを作製するハンズオン部分を2022年版で書き直した記事を私の方で新たに執筆させて頂きたいのですがよろしいでしょうか?

y_ahiruy_ahiru

超長い記事なのに読んでいただいてありがとうございます!
書き直しは構いませんので、是非よろしくお願いします〜!