Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. ExecutableLinesFindingVisitor.php

class ExecutableLinesFindingVisitor

@internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage

@psalm-import-type LinesType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser

Hierarchy

  • class \PhpParser\NodeVisitorAbstract implements \PhpParser\NodeVisitor
    • class \SebastianBergmann\CodeCoverage\StaticAnalysis\ExecutableLinesFindingVisitor extends \PhpParser\NodeVisitorAbstract

Expanded class hierarchy of ExecutableLinesFindingVisitor

File

vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php, line 32

Namespace

SebastianBergmann\CodeCoverage\StaticAnalysis
View source
final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract {
    private int $nextBranch = 0;
    private readonly string $source;
    
    /**
     * @psalm-var LinesType
     */
    private array $executableLinesGroupedByBranch = [];
    
    /**
     * @psalm-var array<int, bool>
     */
    private array $unsets = [];
    
    /**
     * @psalm-var array<int, string>
     */
    private array $commentsToCheckForUnset = [];
    public function __construct(string $source) {
        $this->source = $source;
    }
    public function enterNode(Node $node) : void {
        foreach ($node->getComments() as $comment) {
            $commentLine = $comment->getStartLine();
            if (!isset($this->executableLinesGroupedByBranch[$commentLine])) {
                continue;
            }
            foreach (explode("\n", $comment->getText()) as $text) {
                $this->commentsToCheckForUnset[$commentLine] = $text;
                $commentLine++;
            }
        }
        if ($node instanceof Node\Scalar\String_ || $node instanceof Node\Scalar\EncapsedStringPart) {
            $startLine = $node->getStartLine() + 1;
            $endLine = $node->getEndLine() - 1;
            if ($startLine <= $endLine) {
                foreach (range($startLine, $endLine) as $line) {
                    unset($this->executableLinesGroupedByBranch[$line]);
                }
            }
            return;
        }
        if ($node instanceof Node\Stmt\Interface_) {
            foreach (range($node->getStartLine(), $node->getEndLine()) as $line) {
                $this->unsets[$line] = true;
            }
            return;
        }
        if ($node instanceof Node\Stmt\Declare_ || $node instanceof Node\Stmt\DeclareDeclare || $node instanceof Node\Stmt\Else_ || $node instanceof Node\Stmt\EnumCase || $node instanceof Node\Stmt\Finally_ || $node instanceof Node\Stmt\GroupUse || $node instanceof Node\Stmt\Label || $node instanceof Node\Stmt\Namespace_ || $node instanceof Node\Stmt\Nop || $node instanceof Node\Stmt\Switch_ || $node instanceof Node\Stmt\TryCatch || $node instanceof Node\Stmt\Use_ || $node instanceof Node\Stmt\UseUse || $node instanceof Node\Expr\ConstFetch || $node instanceof Node\Expr\Variable || $node instanceof Node\Expr\Throw_ || $node instanceof Node\ComplexType || $node instanceof Node\Const_ || $node instanceof Node\Identifier || $node instanceof Node\Name || $node instanceof Node\Param || $node instanceof Node\Scalar) {
            return;
        }
        if ($node instanceof Node\Expr\Match_) {
            foreach ($node->arms as $arm) {
                $this->setLineBranch($arm->body
                    ->getStartLine(), $arm->body
                    ->getEndLine(), ++$this->nextBranch);
            }
            return;
        }
        
        /*
         * nikic/php-parser ^4.18 represents <code>throw</code> statements
         * as <code>Stmt\Throw_</code> objects
         */
        if ($node instanceof Node\Stmt\Throw_) {
            $this->setLineBranch($node->expr
                ->getEndLine(), $node->expr
                ->getEndLine(), ++$this->nextBranch);
            return;
        }
        
        /*
         * nikic/php-parser ^5 represents <code>throw</code> statements
         * as <code>Stmt\Expression</code> objects that contain an
         * <code>Expr\Throw_</code> object
         */
        if ($node instanceof Node\Stmt\Expression && $node->expr instanceof Node\Expr\Throw_) {
            $this->setLineBranch($node->expr->expr
                ->getEndLine(), $node->expr->expr
                ->getEndLine(), ++$this->nextBranch);
            return;
        }
        if ($node instanceof Node\Stmt\Enum_ || $node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Expr\Closure || $node instanceof Node\Stmt\Trait_) {
            if ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod) {
                $unsets = [];
                foreach ($node->getParams() as $param) {
                    foreach (range($param->getStartLine(), $param->getEndLine()) as $line) {
                        $unsets[$line] = true;
                    }
                }
                unset($unsets[$node->getEndLine()]);
                $this->unsets += $unsets;
            }
            $isConcreteClassLike = $node instanceof Node\Stmt\Enum_ || $node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_;
            if (null !== $node->stmts) {
                foreach ($node->stmts as $stmt) {
                    if ($stmt instanceof Node\Stmt\Nop) {
                        continue;
                    }
                    foreach (range($stmt->getStartLine(), $stmt->getEndLine()) as $line) {
                        unset($this->executableLinesGroupedByBranch[$line]);
                        if ($isConcreteClassLike && !$stmt instanceof Node\Stmt\ClassMethod) {
                            $this->unsets[$line] = true;
                        }
                    }
                }
            }
            if ($isConcreteClassLike) {
                return;
            }
            $hasEmptyBody = [] === $node->stmts || null === $node->stmts || 1 === count($node->stmts) && $node->stmts[0] instanceof Node\Stmt\Nop;
            if ($hasEmptyBody) {
                if ($node->getEndLine() === $node->getStartLine() && isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) {
                    return;
                }
                $this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch);
                return;
            }
            return;
        }
        if ($node instanceof Node\Expr\ArrowFunction) {
            $startLine = max($node->getStartLine() + 1, $node->expr
                ->getStartLine());
            $endLine = $node->expr
                ->getEndLine();
            if ($endLine < $startLine) {
                return;
            }
            $this->setLineBranch($startLine, $endLine, ++$this->nextBranch);
            return;
        }
        if ($node instanceof Node\Expr\Ternary) {
            if (null !== $node->if && $node->getStartLine() !== $node->if
                ->getEndLine()) {
                $this->setLineBranch($node->if
                    ->getStartLine(), $node->if
                    ->getEndLine(), ++$this->nextBranch);
            }
            if ($node->getStartLine() !== $node->else
                ->getEndLine()) {
                $this->setLineBranch($node->else
                    ->getStartLine(), $node->else
                    ->getEndLine(), ++$this->nextBranch);
            }
            return;
        }
        if ($node instanceof Node\Expr\BinaryOp\Coalesce) {
            if ($node->getStartLine() !== $node->getEndLine()) {
                $this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch);
            }
            return;
        }
        if ($node instanceof Node\Stmt\If_ || $node instanceof Node\Stmt\ElseIf_ || $node instanceof Node\Stmt\Case_) {
            if (null === $node->cond) {
                return;
            }
            $this->setLineBranch($node->cond
                ->getStartLine(), $node->cond
                ->getStartLine(), ++$this->nextBranch);
            return;
        }
        if ($node instanceof Node\Stmt\For_) {
            $startLine = null;
            $endLine = null;
            if ([] !== $node->init) {
                $startLine = $node->init[0]
                    ->getStartLine();
                end($node->init);
                $endLine = current($node->init)
                    ->getEndLine();
                reset($node->init);
            }
            if ([] !== $node->cond) {
                if (null === $startLine) {
                    $startLine = $node->cond[0]
                        ->getStartLine();
                }
                end($node->cond);
                $endLine = current($node->cond)
                    ->getEndLine();
                reset($node->cond);
            }
            if ([] !== $node->loop) {
                if (null === $startLine) {
                    $startLine = $node->loop[0]
                        ->getStartLine();
                }
                end($node->loop);
                $endLine = current($node->loop)
                    ->getEndLine();
                reset($node->loop);
            }
            if (null === $startLine || null === $endLine) {
                return;
            }
            $this->setLineBranch($startLine, $endLine, ++$this->nextBranch);
            return;
        }
        if ($node instanceof Node\Stmt\Foreach_) {
            $this->setLineBranch($node->expr
                ->getStartLine(), $node->valueVar
                ->getEndLine(), ++$this->nextBranch);
            return;
        }
        if ($node instanceof Node\Stmt\While_ || $node instanceof Node\Stmt\Do_) {
            $this->setLineBranch($node->cond
                ->getStartLine(), $node->cond
                ->getEndLine(), ++$this->nextBranch);
            return;
        }
        if ($node instanceof Node\Stmt\Catch_) {
            assert([] !== $node->types);
            $startLine = $node->types[0]
                ->getStartLine();
            end($node->types);
            $endLine = current($node->types)
                ->getEndLine();
            $this->setLineBranch($startLine, $endLine, ++$this->nextBranch);
            return;
        }
        if ($node instanceof Node\Expr\CallLike) {
            if (isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) {
                $branch = $this->executableLinesGroupedByBranch[$node->getStartLine()];
            }
            else {
                $branch = ++$this->nextBranch;
            }
            $this->setLineBranch($node->getStartLine(), $node->getEndLine(), $branch);
            return;
        }
        if (isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) {
            return;
        }
        $this->setLineBranch($node->getStartLine(), $node->getEndLine(), ++$this->nextBranch);
    }
    public function afterTraverse(array $nodes) : void {
        $lines = explode("\n", $this->source);
        foreach ($lines as $lineNumber => $line) {
            $lineNumber++;
            if (1 === preg_match('/^\\s*$/', $line) || isset($this->commentsToCheckForUnset[$lineNumber]) && 1 === preg_match(sprintf('/^\\s*%s\\s*$/', preg_quote($this->commentsToCheckForUnset[$lineNumber], '/')), $line)) {
                unset($this->executableLinesGroupedByBranch[$lineNumber]);
            }
        }
        $this->executableLinesGroupedByBranch = array_diff_key($this->executableLinesGroupedByBranch, $this->unsets);
    }
    
    /**
     * @psalm-return LinesType
     */
    public function executableLinesGroupedByBranch() : array {
        return $this->executableLinesGroupedByBranch;
    }
    private function setLineBranch(int $start, int $end, int $branch) : void {
        foreach (range($start, $end) as $line) {
            $this->executableLinesGroupedByBranch[$line] = $branch;
        }
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
ExecutableLinesFindingVisitor::$commentsToCheckForUnset private property @psalm-var array&lt;int, string&gt;
ExecutableLinesFindingVisitor::$executableLinesGroupedByBranch private property @psalm-var LinesType
ExecutableLinesFindingVisitor::$nextBranch private property
ExecutableLinesFindingVisitor::$source private property
ExecutableLinesFindingVisitor::$unsets private property @psalm-var array&lt;int, bool&gt;
ExecutableLinesFindingVisitor::afterTraverse public function Called once after traversal. Overrides NodeVisitorAbstract::afterTraverse
ExecutableLinesFindingVisitor::enterNode public function Called when entering a node. Overrides NodeVisitorAbstract::enterNode
ExecutableLinesFindingVisitor::executableLinesGroupedByBranch public function @psalm-return LinesType
ExecutableLinesFindingVisitor::setLineBranch private function
ExecutableLinesFindingVisitor::__construct public function
NodeVisitor::DONT_TRAVERSE_CHILDREN public constant If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
of the current node will not be traversed for any visitors.
NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN public constant If NodeVisitor::enterNode() returns DONT_TRAVERSE_CURRENT_AND_CHILDREN, child nodes
of the current node will not be traversed for any visitors.
NodeVisitor::REMOVE_NODE public constant If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
in an array, it will be removed from the array.
NodeVisitor::REPLACE_WITH_NULL public constant If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns REPLACE_WITH_NULL,
the node will be replaced with null. This is not a legal return value if the node is part
of an array, rather than another node.
NodeVisitor::STOP_TRAVERSAL public constant If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns
STOP_TRAVERSAL, traversal is aborted.
NodeVisitorAbstract::beforeTraverse public function Called once before traversal. Overrides NodeVisitor::beforeTraverse 5
NodeVisitorAbstract::leaveNode public function Called when leaving a node. Overrides NodeVisitor::leaveNode 2

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal