123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- <?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 SebastianBergmann\CodeCoverage\StaticAnalysis;
- 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 PhpParser\Node;
- use PhpParser\NodeVisitorAbstract;
- /**
- * @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
- */
- 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\Match_ ||
- $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;
- }
- /*
- * 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;
- }
- }
- }
|