Файл: vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php
Строк: 614
<?php declare(strict_types=1);
/*
* This file is part of phpunit/php-code-coverage.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmannCodeCoverageStaticAnalysis;
use function array_diff_key;
use function assert;
use function count;
use function current;
use function end;
use function explode;
use function max;
use function preg_match;
use function preg_quote;
use function range;
use function reset;
use function sprintf;
use PhpParserNode;
use PhpParserNodeVisitorAbstract;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
{
/**
* @var int
*/
private $nextBranch = 0;
/**
* @var string
*/
private $source;
/**
* @var array<int, int>
*/
private $executableLinesGroupedByBranch = [];
/**
* @var array<int, bool>
*/
private $unsets = [];
/**
* @var array<int, string>
*/
private $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 NodeScalarString_ ||
$node instanceof NodeScalarEncapsedStringPart) {
$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 NodeStmtDeclare_ ||
$node instanceof NodeStmtDeclareDeclare ||
$node instanceof NodeStmtElse_ ||
$node instanceof NodeStmtEnumCase ||
$node instanceof NodeStmtFinally_ ||
$node instanceof NodeStmtInterface_ ||
$node instanceof NodeStmtLabel ||
$node instanceof NodeStmtNamespace_ ||
$node instanceof NodeStmtNop ||
$node instanceof NodeStmtSwitch_ ||
$node instanceof NodeStmtTryCatch ||
$node instanceof NodeStmtUse_ ||
$node instanceof NodeStmtUseUse ||
$node instanceof NodeExprMatch_ ||
$node instanceof NodeExprVariable ||
$node instanceof NodeComplexType ||
$node instanceof NodeConst_ ||
$node instanceof NodeIdentifier ||
$node instanceof NodeName ||
$node instanceof NodeParam ||
$node instanceof NodeScalar) {
return;
}
if ($node instanceof NodeStmtThrow_) {
$this->setLineBranch($node->expr->getEndLine(), $node->expr->getEndLine(), ++$this->nextBranch);
return;
}
if ($node instanceof NodeStmtEnum_ ||
$node instanceof NodeStmtFunction_ ||
$node instanceof NodeStmtClass_ ||
$node instanceof NodeStmtClassMethod ||
$node instanceof NodeExprClosure ||
$node instanceof NodeStmtTrait_) {
$isConcreteClassLike = $node instanceof NodeStmtEnum_ || $node instanceof NodeStmtClass_ || $node instanceof NodeStmtTrait_;
if (null !== $node->stmts) {
foreach ($node->stmts as $stmt) {
if ($stmt instanceof NodeStmtNop) {
continue;
}
foreach (range($stmt->getStartLine(), $stmt->getEndLine()) as $line) {
unset($this->executableLinesGroupedByBranch[$line]);
if (
$isConcreteClassLike &&
!$stmt instanceof NodeStmtClassMethod
) {
$this->unsets[$line] = true;
}
}
}
}
if ($isConcreteClassLike) {
return;
}
$hasEmptyBody = [] === $node->stmts ||
null === $node->stmts ||
(
1 === count($node->stmts) &&
$node->stmts[0] instanceof NodeStmtNop
);
if ($hasEmptyBody) {
if ($node->getEndLine() === $node->getStartLine()) {
return;
}
$this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch);
return;
}
return;
}
if ($node instanceof NodeExprArrowFunction) {
$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 NodeExprTernary) {
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 NodeExprBinaryOpCoalesce) {
if ($node->getStartLine() !== $node->getEndLine()) {
$this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch);
}
return;
}
if ($node instanceof NodeStmtIf_ ||
$node instanceof NodeStmtElseIf_ ||
$node instanceof NodeStmtCase_) {
if (null === $node->cond) {
return;
}
$this->setLineBranch(
$node->cond->getStartLine(),
$node->cond->getStartLine(),
++$this->nextBranch
);
return;
}
if ($node instanceof NodeStmtFor_) {
$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 NodeStmtForeach_) {
$this->setLineBranch(
$node->expr->getStartLine(),
$node->valueVar->getEndLine(),
++$this->nextBranch
);
return;
}
if ($node instanceof NodeStmtWhile_ ||
$node instanceof NodeStmtDo_) {
$this->setLineBranch(
$node->cond->getStartLine(),
$node->cond->getEndLine(),
++$this->nextBranch
);
return;
}
if ($node instanceof NodeStmtCatch_) {
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 NodeExprCallLike) {
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*%ss*$/', preg_quote($this->commentsToCheckForUnset[$lineNumber], '/')), $line)
)) {
unset($this->executableLinesGroupedByBranch[$lineNumber]);
}
}
$this->executableLinesGroupedByBranch = array_diff_key(
$this->executableLinesGroupedByBranch,
$this->unsets
);
}
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;
}
}
}