🐶

PHPStanとPHP Parserのノードの違い

2023/10/11に公開

PHPStanは1.10.x、PHP Parserは4.17.xを想定。

以下のコードを解析した時、ノードはいくつあるか?

<?php
class Foo {}

PHP Parserに慣れていると1つ(Node\Stmt\Class_)と思うかもしれないが、PHPStanで解析すると7つある。

  • FileNode
  • Class_ <- PHP Parserのノード
  • InClassNode
  • ClassPropertiesNode
  • ClassMethodsNode
  • ClassConstantsNode
  • CollectedDataNode

PHPStanの公式ドキュメントに書いてあるんだけど、CollectedDataNodecollectorsと呼ばれるもので、これとPHP ParserのノードであるClass_を除いた残りの5つはvirtual nodes(仮想ノード)と呼ばれる。詳細はドキュメントを参照するとして、ざっくり、非仮想ノードとの違いは解析対象のノードに付随するscopeの有無だったり、Constructor Property Promotionを考慮してプロパティを認識するか否かだったり。

カスタムルールに必要な情報を取得するのに適したノードを選ぶとよい。

scopeについてはこちら。ざっくり、解析しているコードについての情報をもっているもの。例えば、ファイルパス、名前空間、変数の型、呼び出したノードがクラスの中にいるか否かなど。僕は"文脈的なもの"と捉えている。雰囲気を掴むにはシグネチャを見るのがよいかもしれない。

interface Scope extends ClassMemberAccessAnswerer, NamespaceAnswerer
{
	public function getFile(): string;
	public function getFileDescription(): string;
	public function isDeclareStrictTypes(): bool;
	public function isInTrait(): bool;
	public function getTraitReflection(): ?ClassReflection;
	public function getFunction();
	public function getFunctionName(): ?string;
	public function getParentScope(): ?self;
	public function hasVariableType(string $variableName): TrinaryLogic;
	public function getVariableType(string $variableName): Type;
	public function canAnyVariableExist(): bool;
	public function getDefinedVariables(): array;
	public function hasConstant(Name $name): bool;
	public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?PropertyReflection;
	public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection;
	public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ConstantReflection;
	public function getIterableKeyType(Type $iteratee): Type;
	public function getIterableValueType(Type $iteratee): Type;
	public function isInAnonymousFunction(): bool;
	public function getAnonymousFunctionReflection(): ?ParametersAcceptor;
	public function getAnonymousFunctionReturnType(): ?Type;
	public function getType(Expr $node): Type;
	public function getNativeType(Expr $expr): Type;
	public function doNotTreatPhpDocTypesAsCertain(): self;
	public function resolveName(Name $name): string;
	public function resolveTypeByName(Name $name): TypeWithClassName;
	public function getTypeFromValue($value): Type;
	public function isSpecified(Expr $node): bool;
	public function hasExpressionType(Expr $node): TrinaryLogic;
	public function isInClassExists(string $className): bool;
	public function isInFunctionExists(string $functionName): bool;
	public function isInClosureBind(): bool;
	public function isParameterValueNullable(Param $parameter): bool;
	public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type;
	public function isInExpressionAssign(Expr $expr): bool;
	public function isUndefinedExpressionAllowed(Expr $expr): bool;
	public function filterByTruthyValue(Expr $expr): self;
	public function filterByFalseyValue(Expr $expr): self;
	public function isInFirstLevelStatement(): bool;
}

以上。

Discussion