*/ private array $stack = array(); /** * Resets state before traversing a new set of nodes. * * @param Node[] $nodes Nodes about to be traversed. * @return Node[]|null */ public function beforeTraverse( array $nodes ): ?array { $this->latestDocComment = null; $this->stack = array(); return null; } /** * Tracks the applicable docblock and attaches it to function-call nodes. * * @param Node $node Node being entered. * @return Node|null */ public function enterNode( Node $node ): ?Node { $doc = $node->getDocComment(); if ( null !== $doc ) { // A docblock here documents this node and everything nested within it. $this->stack[] = array( $node, $this->latestDocComment ); $this->latestDocComment = $doc; } elseif ( $node instanceof Stmt ) { // A new statement without its own docblock starts an undocumented scope // for its subtree, so a preceding docblock does not carry into it. $this->stack[] = array( $node, $this->latestDocComment ); $this->latestDocComment = null; } // Attributes are retained for as long as a parsed file is held in memory, so // the docblock is recorded only where it can be read: on a function call, and // only when there is one to record. Readers cannot tell an absent attribute // from a null one, so skipping the write costs them nothing. if ( null !== $this->latestDocComment && $node instanceof FuncCall ) { $node->setAttribute( 'latestDocComment', $this->latestDocComment ); } return null; } /** * Restores the docblock that applied before this node was entered, bounding a * docblock's reach to the node that introduced it. * * @param Node $node Node being left. * @return Node|null */ public function leaveNode( Node $node ): ?Node { $top = end( $this->stack ); if ( false !== $top && $top[0] === $node ) { $this->latestDocComment = $top[1]; array_pop( $this->stack ); } return null; } }