PHP: Phan (PHP静的解析ツール) の プラグイン

参考

プラグイン開発についての詳細は Writing Plugins for Phan を参照

デモプラグインと設定ファイル

公式ページには、デモプラグインが含まれる。(master/.phan/plugins)

設定ファイル

.phan/config.php
<?php

use \Phan\Issue;

return [

    // snip...

    'plugins' => [
        '.phan/plugins/DemoPlugin.php', //< デモ
        '.phan/plugins/DollarDollarPlugin.php',  //< $$var (可変変数)
        '.phan/plugins/DuplicateArrayKeyPlugin.php', //< `key => value` と `value` を混在させた配列をチェック
        '.phan/plugins/UnusedSuppressionPlugin.php', //< 未使用の `@suppress`アノテーションをチェック
    ],
];

プラグイン 開発

テンプレート

<?php declare(strict_types=1);

use Phan\AST\AnalysisVisitor;
use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Plugin;
use Phan\Plugin\PluginImplementation;
use ast\Node;

// プラグイン名は `Sample` とする(ファイル名は、SamplePlugin.php )
class SamplePlugin extends PluginImplementation
{
    public function analyzeNode(CodeBase $code_base, Context $context, Node $node, Node $parent_node = null)
    {
        (new SampleVisitor($code_base, $context, $this))($node);
    }
}

class SampleVisitor extends AnalysisVisitor
{
    public function __construct(CodeBase $code_base, Context $context, Plugin $plugin)
    {
        parent::__construct($code_base, $context);
        $this->plugin = $plugin;
    }
    public function visit(Node $node)
    {
        // ここで全ての処理を行っても良いが、通常特定のvisitメソッドをオーバーライドする
    }
    // visit{{ノードタイプ}} メソッドが親クラスでいっぱい定義されている
    // 例として `switch` をオーバーライドしてみる
    public function visitSwitch(Node $node)
    {
        // エラーの放出
        $this->plugin->emitIssue($this->code_base, $this->context, 'PhanPluginSample', 'エラーメッセージ');
        return parent::visitSwitch($node);
    }
    /** @var Plugin */
    private $plugin;
}
return new SamplePlugin;

visitメソッド({{ノードタイプ}}) の種類

phan/src/Phan/AST/Visitor/KindVisitor.php を参照


php-ast が提供するダンプツール

php-ast で公開されている util.php を使うと \ast\Node の整形されたダンプが出来る。

<?php

require 'path/to/util.php';

class SampleVisitor extends AnalysisVisitor
{
    // snip...

    public function visitVar(Node $node)
    {
        echo ast_dump($node), "\n";
        // var_dump($node);
    }
    /** @var Plugin */
    private $plugin;
}

/* 出力例
AST_STMT_LIST
    0: AST_ASSIGN
        var: AST_VAR
            name: "var"
        expr: 42
*/

ノード情報を取得したい

  • Phan\AST\ContextNode\ContextNode

    ノード情報

    <?php
    // ノードの変数名を取得する
    $name = (new \Phan\AST\ContextNode\ContextNode($this->code_base, $this->context, $node))->getVariableName();
    
  • Phan\Language\UnionType

    ノードの型情報

    <?php
    // 調べたいノードからUnionTypeを取得する
    $utype = \Phan\Language\UnionType::fromNode($this->context, $this->code_base, $node);
    if ($utype->serialize() === 'string') {
        // この段階で string 型になる場合
    }
    if ($utype->hasType(\Phan\Language\Type::fromInternalTypeName('bool'))) {
        // この段階で bool 型になる可能性がある場合
    }