diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 0cc7e8a1ee..7bf41174d3 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -52,8 +52,8 @@ use PHPStan\Node\ClassConstantsNode; use PHPStan\Node\ClassMethodsNode; use PHPStan\Node\ClassPropertiesNode; +use PHPStan\Node\ClassStatementsGatherer; use PHPStan\Node\ClosureReturnStatementsNode; -use PHPStan\Node\Constant\ClassConstantFetch; use PHPStan\Node\ExecutionEndNode; use PHPStan\Node\FunctionReturnStatementsNode; use PHPStan\Node\InArrowFunctionNode; @@ -64,8 +64,6 @@ use PHPStan\Node\LiteralArrayItem; use PHPStan\Node\LiteralArrayNode; use PHPStan\Node\MethodReturnStatementsNode; -use PHPStan\Node\Property\PropertyRead; -use PHPStan\Node\Property\PropertyWrite; use PHPStan\Node\ReturnStatement; use PHPStan\Node\UnreachableStatementNode; use PHPStan\Parser\Parser; @@ -74,7 +72,6 @@ use PHPStan\PhpDoc\Tag\ParamTag; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\MethodReflectionWithNode; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\PassedByReference; @@ -574,92 +571,11 @@ private function processStmtNode( throw new \PHPStan\ShouldNotHappenException(); } - $properties = []; - $methods = []; - $methodCalls = []; - $propertyUsages = []; - $constants = []; - $constantFetches = []; - - /** @var \Closure(Node, Scope): void $customCallback2 */ - $customCallback2 = null; - $customCallback1 = function (Node $node, Scope $scope) use ($classReflection, &$properties, &$methods, &$methodCalls, &$propertyUsages, &$constants, &$constantFetches, &$customCallback1, &$customCallback2, $classScope): void { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($scope->getClassReflection()->getName() !== $classReflection->getName()) { - return; - } - if ($node instanceof Node\Stmt\Property && !$scope->isInTrait()) { - $properties[] = $node; - return; - } - if ($node instanceof Node\Stmt\ClassMethod && !$scope->isInTrait()) { - $methods[] = $node; - return; - } - if ($node instanceof Node\Stmt\ClassConst) { - $constants[] = $node; - return; - } - if ($node instanceof MethodCall || $node instanceof StaticCall) { - $methodCalls[] = new \PHPStan\Node\Method\MethodCall($node, $scope); - if ($node instanceof MethodCall && $node->name instanceof Node\Identifier) { - $calleeType = $scope->getType($node->var); - $methodName = $node->name->toString(); - if ($calleeType->hasMethod($methodName)->yes()) { - $methodReflection = $calleeType->getMethod($methodName, $scope); - if ( - $methodReflection instanceof MethodReflectionWithNode - && $methodReflection->getDeclaringClass()->getName() === $classReflection->getName() - && $methodReflection->getNode() !== null - ) { - $this->processNodes([$methodReflection->getNode()], $classScope, $customCallback2); - } - } - } - return; - } - if ($node instanceof Array_ && count($node->items) === 2) { - $methodCalls[] = new \PHPStan\Node\Method\MethodCall($node, $scope); - return; - } - if ($node instanceof Expr\ClassConstFetch) { - $constantFetches[] = new ClassConstantFetch($node, $scope); - return; - } - if (!$node instanceof Expr) { - return; - } - if ($node instanceof Expr\AssignOp\Coalesce) { - $customCallback1($node->var, $scope); - return; - } - if ($node instanceof Node\Scalar\EncapsedStringPart) { - return; - } - $inAssign = $scope->isInExpressionAssign($node); - while ($node instanceof ArrayDimFetch) { - $node = $node->var; - } - if (!$node instanceof PropertyFetch && !$node instanceof StaticPropertyFetch) { - return; - } - - if ($inAssign) { - $propertyUsages[] = new PropertyWrite($node, $scope); - } else { - $propertyUsages[] = new PropertyRead($node, $scope); - } - }; - $customCallback2 = static function (Node $node, Scope $scope) use ($nodeCallback, $customCallback1): void { - $nodeCallback($node, $scope); - $customCallback1($node, $scope); - }; - $this->processStmtNodes($stmt, $stmt->stmts, $classScope, $customCallback2); - $nodeCallback(new ClassPropertiesNode($stmt, $properties, $propertyUsages, $methodCalls), $classScope); - $nodeCallback(new ClassMethodsNode($stmt, $methods, $methodCalls), $classScope); - $nodeCallback(new ClassConstantsNode($stmt, $constants, $constantFetches), $classScope); + $classStatementsGatherer = new ClassStatementsGatherer($classReflection, $classScope, $nodeCallback); + $this->processStmtNodes($stmt, $stmt->stmts, $classScope, $classStatementsGatherer); + $nodeCallback(new ClassPropertiesNode($stmt, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls()), $classScope); + $nodeCallback(new ClassMethodsNode($stmt, $classStatementsGatherer->getMethods(), $classStatementsGatherer->getMethodCalls()), $classScope); + $nodeCallback(new ClassConstantsNode($stmt, $classStatementsGatherer->getConstants(), $classStatementsGatherer->getConstantFetches()), $classScope); } elseif ($stmt instanceof Node\Stmt\Property) { $hasYield = false; foreach ($stmt->props as $prop) { diff --git a/src/Node/ClassStatementsGatherer.php b/src/Node/ClassStatementsGatherer.php new file mode 100644 index 0000000000..41348398a0 --- /dev/null +++ b/src/Node/ClassStatementsGatherer.php @@ -0,0 +1,189 @@ + */ + private array $propertyUsages = []; + + /** @var \PhpParser\Node\Stmt\ClassConst[] */ + private array $constants = []; + + /** @var ClassConstantFetch[] */ + private array $constantFetches = []; + + /** + * @param ClassReflection $classReflection + * @param Scope $classScope + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + */ + public function __construct( + ClassReflection $classReflection, + Scope $classScope, + callable $nodeCallback + ) + { + $this->classReflection = $classReflection; + $this->classScope = $classScope; + $this->nodeCallback = $nodeCallback; + } + + /** + * @return \PhpParser\Node\Stmt\Property[] + */ + public function getProperties(): array + { + return $this->properties; + } + + /** + * @return \PhpParser\Node\Stmt\ClassMethod[] + */ + public function getMethods(): array + { + return $this->methods; + } + + /** + * @return Method\MethodCall[] + */ + public function getMethodCalls(): array + { + return $this->methodCalls; + } + + /** + * @return array + */ + public function getPropertyUsages(): array + { + return $this->propertyUsages; + } + + /** + * @return \PhpParser\Node\Stmt\ClassConst[] + */ + public function getConstants(): array + { + return $this->constants; + } + + /** + * @return ClassConstantFetch[] + */ + public function getConstantFetches(): array + { + return $this->constantFetches; + } + + public function __invoke(\PhpParser\Node $node, Scope $scope): void + { + $nodeCallback = $this->nodeCallback; + $nodeCallback($node, $scope); + $this->gatherNodes($node, $scope); + } + + private function gatherNodes(\PhpParser\Node $node, Scope $scope): void + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($scope->getClassReflection()->getName() !== $this->classReflection->getName()) { + return; + } + if ($node instanceof \PhpParser\Node\Stmt\Property && !$scope->isInTrait()) { + $this->properties[] = $node; + return; + } + if ($node instanceof \PhpParser\Node\Stmt\ClassMethod && !$scope->isInTrait()) { + $this->methods[] = $node; + return; + } + if ($node instanceof \PhpParser\Node\Stmt\ClassConst) { + $this->constants[] = $node; + return; + } + if ($node instanceof MethodCall || $node instanceof StaticCall) { + $this->methodCalls[] = new \PHPStan\Node\Method\MethodCall($node, $scope); + if ($node instanceof MethodCall && $node->name instanceof \PhpParser\Node\Identifier) { + $calleeType = $scope->getType($node->var); + $methodName = $node->name->toString(); + if ($calleeType->hasMethod($methodName)->yes()) { + $methodReflection = $calleeType->getMethod($methodName, $scope); + if ( + $methodReflection instanceof MethodReflectionWithNode + && $methodReflection->getDeclaringClass()->getName() === $this->classReflection->getName() + && $methodReflection->getNode() !== null + ) { + //$this->processNodes([$methodReflection->getNode()], $this->classScope, $this); + } + } + } + return; + } + if ($node instanceof Array_ && count($node->items) === 2) { + $this->methodCalls[] = new \PHPStan\Node\Method\MethodCall($node, $scope); + return; + } + if ($node instanceof Expr\ClassConstFetch) { + $this->constantFetches[] = new ClassConstantFetch($node, $scope); + return; + } + if (!$node instanceof Expr) { + return; + } + if ($node instanceof Expr\AssignOp\Coalesce) { + $this->gatherNodes($node->var, $scope); + return; + } + if ($node instanceof \PhpParser\Node\Scalar\EncapsedStringPart) { + return; + } + $inAssign = $scope->isInExpressionAssign($node); + while ($node instanceof ArrayDimFetch) { + $node = $node->var; + } + if (!$node instanceof PropertyFetch && !$node instanceof StaticPropertyFetch) { + return; + } + + if ($inAssign) { + $this->propertyUsages[] = new PropertyWrite($node, $scope); + } else { + $this->propertyUsages[] = new PropertyRead($node, $scope); + } + } + +}