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\StaticAnalysisView 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<int, string> | ||
ExecutableLinesFindingVisitor::$executableLinesGroupedByBranch | private | property | @psalm-var LinesType | ||
ExecutableLinesFindingVisitor::$nextBranch | private | property | |||
ExecutableLinesFindingVisitor::$source | private | property | |||
ExecutableLinesFindingVisitor::$unsets | private | property | @psalm-var array<int, bool> | ||
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 |