🐶
さわって慣れるPHP Parser - Nameノードを内包しているノード探し
さわって慣れるPHP Parser - Nameノードを内包しているノード探し(訂正版)を書きました。以下は訂正前の内容です。
さわって慣れるPHP Parser - NameResolverの続編です。
Nameノードを内包しているノードにはどのようなものがあるのか、気になったので調べてみました。
宣言された関数、クラス、インターフェース、トレイト、定数を使用する側にはこれだけのバリエーションがあるのですね。
namespace | ノード[1] | コンストラクタの引数[2] |
---|---|---|
PhpParser\Node\Expr | ClassConstFetch | $class |
PhpParser\Node\Expr | ConstFetch | $name |
PhpParser\Node\Expr | FuncCall | $name |
PhpParser\Node\Expr | Instanceof_ | $class |
PhpParser\Node\Expr | New_ | $class |
PhpParser\Node\Expr | StaticCall | $class |
PhpParser\Node\Expr | StaticPropertyFetch | $class |
PhpParser\Node\Stmt\TraitUseAdaptation | Alias | $trait |
PhpParser\Node\Stmt\TraitUseAdaptation | Precedence | $insteadof |
PhpParser\Node\Stmt\TraitUseAdaptation | Precedence | $trait |
PhpParser\Node\Stmt | Catch_ | $types |
PhpParser\Node\Stmt | GroupUse | $prefix |
PhpParser\Node\Stmt | Namespace_ | $name |
PhpParser\Node\Stmt | Property | $type |
PhpParser\Node\Stmt | TraitUse | $traits |
PhpParser\Node\Stmt | UseUse | $name |
PhpParser\Node | Attribute | $name |
PhpParser\Node | IntersectionType | $types |
PhpParser\Node | Name | $name |
PhpParser\Node | NullableType | $type |
PhpParser\Node | Param | $type |
PhpParser\Node | UnionType | $types |
抽出方法
考え方
-
Name
ノードを内包しているノードを探したい。
→Name
型のプロパティを持つノードのクラスを探したい。 - 原則、コンストラクタでプロパティに初期値を設定するはず。
→ コンストラクタの引数から型を評価できるはず。 - ノードを表すクラスは
vendor/nikic/php-parser/lib/PhpParser/Node
にある。
前提
- Rector 0.15.23
- PHPStan 1.10.10
- PHP Parser 4.15.4
実装
これはRectorの本来の使い方ではありませんが、車輪の再発明なしに型やノードがとれるので、簡易ツールの実装に利用させてもらいました。
NameNodeHolderFinder.php
<?php declare(strict_types=1);
namespace Utils\Rector\Rector;
use PhpParser\Node;
use PhpParser\Node\Expr\Error;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Namespace_;
use PHPStan\Type\ArrayType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\MethodName;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
final class NameNodeHolderFinder extends AbstractRector
{
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
$constructor = $node->getMethod(MethodName::CONSTRUCT);
if ($constructor === null) {
return null;
}
foreach ($constructor->params as $param) {
$nameNodeHolder = $this->getNodeHolderIfHeldNodeTypeIsName(
$node,
$param
);
if ($nameNodeHolder !== '') {
echo $nameNodeHolder . PHP_EOL;
}
}
return null;
}
private function getNodeHolderIfHeldNodeTypeIsName(Class_ $class, Param $param): string
{
$type = $this->arrayToNonArray($this->getType($param));
if ($type instanceof UnionType) {
foreach ($type->getTypes() as $innerType) {
$nameNodeHolder = $this->stringifyNameNodeHolder($class, $param, $innerType);
if ($nameNodeHolder !== '') {
return $nameNodeHolder;
}
}
}
return $this->stringifyNameNodeHolder($class, $param, $type);
}
private function stringifyNameNodeHolder(Class_ $class, Param $param, Type $type): string
{
if (!$this->arrayToNonArray($type)->equals(new ObjectType('PhpParser\Node\Name'))) {
return '';
}
$namespace = $class->getAttribute(AttributeKey::PARENT_NODE);
if (!$namespace instanceof Namespace_) {
return '';
}
if ($param->var instanceof Error) {
return '';
}
return $namespace->name?->toString() . ',' . $class->name?->toString() . ',' . '$' . $this->getName($param->var);
}
private function arrayToNonArray(Type $type): Type
{
return ($type instanceof ArrayType) ? $type->getItemType() : $type;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('', []);
}
}
rector.php
<?php declare(strict_types=1);
use Utils\Rector\Rector\NameNodeHolderFinder;
use Rector\Config\RectorConfig;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__ . '/vendor/nikic/php-parser/lib/PhpParser/Node',
]);
$rectorConfig->rules([
NameNodeHolderFinder::class,
]);
};
実装備忘録
- コンストラクタではなくプロパティを分析対象にすると型がとれない。
- 型指定のバリエーションは、
Name
とName[]
、それぞれについて単体で宣言する場合とユニオンの場合がある。
-
ノードの種類に関する参考資料: PHP Parserで学ぶPHP ↩︎
-
プロパティとほぼ一致するが
Name
ノードに関しては、$name
を$parts
プロパティに詰め替える。 ↩︎
Discussion