simpleCallableNodeTraverser = $simpleCallableNodeTraverser; $this->nodeNameResolver = $nodeNameResolver; } /** * @return string[] * @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Stmt\Function_|\PhpParser\Node\Expr\Closure $node */ public function resolve($node) : array { $undefinedVariables = []; $this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $node->stmts, function (\PhpParser\Node $node) use(&$undefinedVariables) : ?int { // entering new scope - break! if ($node instanceof \PhpParser\Node\FunctionLike && !$node instanceof \PhpParser\Node\Expr\ArrowFunction) { return \PhpParser\NodeTraverser::STOP_TRAVERSAL; } if ($node instanceof \PhpParser\Node\Stmt\Foreach_) { // handled above return \PhpParser\NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN; } if (!$node instanceof \PhpParser\Node\Expr\Variable) { return null; } if ($this->shouldSkipVariable($node)) { return null; } /** @var string $variableName */ $variableName = $this->nodeNameResolver->getName($node); // defined 100 % /** @var Scope $scope */ $scope = $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::SCOPE); if ($scope->hasVariableType($variableName)->yes()) { return null; } $undefinedVariables[] = $variableName; return null; }); return \array_unique($undefinedVariables); } private function shouldSkipVariable(\PhpParser\Node\Expr\Variable $variable) : bool { $parentNode = $variable->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE); if (!$parentNode instanceof \PhpParser\Node) { return \true; } if ($parentNode instanceof \PhpParser\Node\Stmt\Global_) { return \true; } if ($parentNode instanceof \PhpParser\Node && ($parentNode instanceof \PhpParser\Node\Expr\Assign || $parentNode instanceof \PhpParser\Node\Expr\AssignRef || $this->isStaticVariable($parentNode))) { return \true; } if ($parentNode instanceof \PhpParser\Node\Stmt\Unset_ || $parentNode instanceof \PhpParser\Node\Expr\Cast\Unset_) { return \true; } // list() = | [$values] = defines variables as null if ($this->isListAssign($parentNode)) { return \true; } $nodeScope = $variable->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::SCOPE); if (!$nodeScope instanceof \PHPStan\Analyser\Scope) { return \true; } $variableName = $this->nodeNameResolver->getName($variable); // skip $this, as probably in outer scope if ($variableName === 'this') { return \true; } return $variableName === null; } private function isStaticVariable(\PhpParser\Node $parentNode) : bool { // definition of static variable if ($parentNode instanceof \PhpParser\Node\Stmt\StaticVar) { $parentParentNode = $parentNode->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE); if ($parentParentNode instanceof \PhpParser\Node\Stmt\Static_) { return \true; } } return \false; } private function isListAssign(\PhpParser\Node $node) : bool { $parentNode = $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE); if ($parentNode instanceof \PhpParser\Node\Expr\List_) { return \true; } return $parentNode instanceof \PhpParser\Node\Expr\Array_; } }