public function visitClosedContext(Node $node) : Context { // Make a copy of the internal context so that we don't // leak any changes within the closed context to the // outer scope $context = clone $this->context->withLineNumberStart($node->lineno ?? 0); // Visit the given node populating the code base // with anything we learn and get a new context // indicating the state of the world within the // given node $context = (new PreOrderAnalysisVisitor($this->code_base, $context))($node); assert(!empty($context), 'Context cannot be null'); // We collect all child context so that the // PostOrderAnalysisVisitor can optionally operate on // them $child_context_list = []; $child_context = $context; // With a context that is inside of the node passed // to this method, we analyze all children of the // node. foreach ($node->children ?? [] as $child_node) { // Skip any non Node children. if (!$child_node instanceof Node) { continue; } if (!Analysis::shouldVisit($child_node)) { $child_context->withLineNumberStart($child_node->lineno ?? 0); continue; } // Step into each child node and get an // updated context for the node $child_context = (new BlockAnalysisVisitor($this->code_base, $child_context, $node, $this->depth + 1))($child_node); $child_context_list[] = $child_context; } // For if statements, we need to merge the contexts // of all child context into a single scope based // on any possible branching structure $context = (new ContextMergeVisitor($this->code_base, $context, $child_context_list))($node); // Now that we know all about our context (like what // 'self' means), we can analyze statements like // assignments and method calls. $context = (new PostOrderAnalysisVisitor($this->code_base, $context->withLineNumberStart($node->lineno ?? 0), $this->parent_node))($node); // Return the initial context as we exit return $this->context; }