🐶

さわって慣れるPHP Parser - Nameノードを内包しているノード探し(訂正版)

2023/12/10に公開

さわって慣れるPHP Parser - Nameノードを内包しているノード探しの訂正版です。

Nameノードを内包しているノードをPHPStanのカスタムルールを用いて調べ直してみました。

namespace ノード
PhpParser\Node\Expr ArrowFunction
PhpParser\Node\Expr ClassConstFetch
PhpParser\Node\Expr ConstFetch
PhpParser\Node\Expr Closure
PhpParser\Node\Expr FuncCall
PhpParser\Node\Expr Instanceof_
PhpParser\Node\Expr New_
PhpParser\Node\Expr StaticCall
PhpParser\Node\Expr StaticPropertyFetch
PhpParser\Node\Stmt\TraitUseAdaptation Precedence
PhpParser\Node\Stmt Catch_
PhpParser\Node\Stmt Class_
PhpParser\Node\Stmt ClassLike
PhpParser\Node\Stmt ClassMethod
PhpParser\Node\Stmt Enum_
PhpParser\Node\Stmt Function_
PhpParser\Node\Stmt GroupUse
PhpParser\Node\Stmt Interface_
PhpParser\Node\Stmt Namespace_
PhpParser\Node\Stmt Property
PhpParser\Node\Stmt TraitUseAdaptation
PhpParser\Node\Stmt TraitUse
PhpParser\Node\Stmt UseUse
PhpParser\Node Attribute
PhpParser\Node Const_
PhpParser\Node IntersectionType
PhpParser\Node NullableType
PhpParser\Node Param
PhpParser\Node UnionType

抽出方法

考え方

  • Nameノードを内包しているがコンストラクタから渡さないものがあった。
    Name型のプロパティを持つノードのクラスを探す。
  • 配列型の場合、配列の要素の型を評価する必要がある。
  • mixed型は偽陽性になるので除外する。
  • Union型にName型が含まれるケースは$propertyType->isSuperTypeOf(new ObjectType('PhpParser\Node\Name'));で判定できる。
  • NameノードはNameノードを内包するわけではない。

前提

  • PHPStan 1.10.25
  • PHP Parser 4.16.0

実装

<?php

namespace Rules;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Node\InClassNode;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;

class NameNodeHolderFindRule implements Rule
{
    public function getNodeType(): string
    {
        return InClassNode::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        if (!$node instanceof InClassNode) {
            return [];
        }

        $classLike = $node->getOriginalNode();
        $properties = $classLike->getProperties();
        $classReflection = $scope->getClassReflection();
        foreach ($properties as $property) {
            foreach($property->props as $prop) {
                $propertyName = $prop->name->toString();
                $propertyReflection = $classReflection->getNativeProperty($propertyName);
                $propertyType = $propertyReflection->getReadableType();
                if ($propertyType instanceof ArrayType) {
                    $propertyType = $propertyType->getItemType();
                }
                if ($propertyType instanceof MixedType) {
                    continue;
                }
                $trinary = $propertyType->isSuperTypeOf(new ObjectType('PhpParser\Node\Name'));
                if (!$trinary->no()) {
                    return [
                        RuleErrorBuilder::message(
                            $classLike->namespacedName->toString()
                        )->build(),
                    ];
                }
            }
        }
        return [];
    }
}

実装備忘録

  • ノードはクラスとして定義されているので分析したいのはClass_だがプロパティの型をとるためにReflectionが欲しいので仮想ノードのInClassNodeを分析対象にする。

Discussion