From 09898ccbc87b5e297bcfab4527a3705bd3b4d5a4 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 2 Dec 2023 17:45:25 +0300 Subject: add phpunit code coverage driver --- .../ExecutableLinesFindingVisitor.php | 449 +++++++++++++-------- 1 file changed, 271 insertions(+), 178 deletions(-) (limited to 'vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php') diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php index ae0b08ae7..506f27524 100644 --- a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php +++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php @@ -9,46 +9,19 @@ */ 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\Node\Expr\Array_; -use PhpParser\Node\Expr\ArrayDimFetch; -use PhpParser\Node\Expr\ArrayItem; -use PhpParser\Node\Expr\Assign; -use PhpParser\Node\Expr\BinaryOp; -use PhpParser\Node\Expr\CallLike; -use PhpParser\Node\Expr\Cast; -use PhpParser\Node\Expr\Closure; -use PhpParser\Node\Expr\Match_; -use PhpParser\Node\Expr\MethodCall; -use PhpParser\Node\Expr\NullsafePropertyFetch; -use PhpParser\Node\Expr\PropertyFetch; -use PhpParser\Node\Expr\StaticPropertyFetch; -use PhpParser\Node\Expr\Ternary; -use PhpParser\Node\MatchArm; -use PhpParser\Node\Scalar\Encapsed; -use PhpParser\Node\Stmt\Break_; -use PhpParser\Node\Stmt\Case_; -use PhpParser\Node\Stmt\Catch_; -use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Continue_; -use PhpParser\Node\Stmt\Do_; -use PhpParser\Node\Stmt\Echo_; -use PhpParser\Node\Stmt\Else_; -use PhpParser\Node\Stmt\ElseIf_; -use PhpParser\Node\Stmt\Expression; -use PhpParser\Node\Stmt\Finally_; -use PhpParser\Node\Stmt\For_; -use PhpParser\Node\Stmt\Foreach_; -use PhpParser\Node\Stmt\Goto_; -use PhpParser\Node\Stmt\If_; -use PhpParser\Node\Stmt\Property; -use PhpParser\Node\Stmt\Return_; -use PhpParser\Node\Stmt\Switch_; -use PhpParser\Node\Stmt\Throw_; -use PhpParser\Node\Stmt\TryCatch; -use PhpParser\Node\Stmt\Unset_; -use PhpParser\Node\Stmt\While_; use PhpParser\NodeVisitorAbstract; /** @@ -57,217 +30,337 @@ use PhpParser\NodeVisitorAbstract; final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract { /** - * @psalm-var array + * @var int */ - private $executableLines = []; + private $nextBranch = 0; /** - * @psalm-var array + * @var string */ - private $propertyLines = []; + private $source; /** - * @psalm-var array + * @var array */ - private $returns = []; + private $executableLinesGroupedByBranch = []; - public function enterNode(Node $node): void + /** + * @var array + */ + private $unsets = []; + + /** + * @var array + */ + private $commentsToCheckForUnset = []; + + public function __construct(string $source) { - $this->savePropertyLines($node); + $this->source = $source; + } - if (!$this->isExecutable($node)) { - return; - } + public function enterNode(Node $node): void + { + foreach ($node->getComments() as $comment) { + $commentLine = $comment->getStartLine(); - foreach ($this->getLines($node) as $line) { - if (isset($this->propertyLines[$line])) { - return; + if (!isset($this->executableLinesGroupedByBranch[$commentLine])) { + continue; } - $this->executableLines[$line] = $line; + foreach (explode("\n", $comment->getText()) as $text) { + $this->commentsToCheckForUnset[$commentLine] = $text; + $commentLine++; + } } - } - /** - * @psalm-return array - */ - public function executableLines(): array - { - $this->computeReturns(); + if ($node instanceof Node\Scalar\String_ || + $node instanceof Node\Scalar\EncapsedStringPart) { + $startLine = $node->getStartLine() + 1; + $endLine = $node->getEndLine() - 1; - sort($this->executableLines); + if ($startLine <= $endLine) { + foreach (range($startLine, $endLine) as $line) { + unset($this->executableLinesGroupedByBranch[$line]); + } + } - return $this->executableLines; - } + return; + } - private function savePropertyLines(Node $node): void - { - if (!$node instanceof Property && !$node instanceof Node\Stmt\ClassConst) { + 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\Interface_ || + $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\ComplexType || + $node instanceof Node\Const_ || + $node instanceof Node\Identifier || + $node instanceof Node\Name || + $node instanceof Node\Param || + $node instanceof Node\Scalar) { return; } - foreach (range($node->getStartLine(), $node->getEndLine()) as $index) { - $this->propertyLines[$index] = $index; + if ($node instanceof Node\Stmt\Throw_) { + $this->setLineBranch($node->expr->getEndLine(), $node->expr->getEndLine(), ++$this->nextBranch); + + return; } - } - private function computeReturns(): void - { - foreach ($this->returns as $return) { - foreach (range($return->getStartLine(), $return->getEndLine()) as $loc) { - if (isset($this->executableLines[$loc])) { - continue 2; + 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_) { + $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; + } + } } } - $line = $return->getEndLine(); + 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()) { + return; + } + + $this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch); - if ($return->expr !== null) { - $line = $return->expr->getStartLine(); + return; } - $this->executableLines[$line] = $line; + return; } - } - /** - * @return int[] - */ - private function getLines(Node $node): array - { - if ($node instanceof Cast || - $node instanceof PropertyFetch || - $node instanceof NullsafePropertyFetch || - $node instanceof StaticPropertyFetch) { - return [$node->getEndLine()]; - } + if ($node instanceof Node\Expr\ArrowFunction) { + $startLine = max( + $node->getStartLine() + 1, + $node->expr->getStartLine() + ); - if ($node instanceof ArrayDimFetch) { - if (null === $node->dim) { - return []; + $endLine = $node->expr->getEndLine(); + + if ($endLine < $startLine) { + return; } - return [$node->dim->getStartLine()]; + $this->setLineBranch($startLine, $endLine, ++$this->nextBranch); + + return; } - if ($node instanceof Array_) { - $startLine = $node->getStartLine(); + 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 (isset($this->executableLines[$startLine])) { - return []; + if ($node->getStartLine() !== $node->else->getEndLine()) { + $this->setLineBranch($node->else->getStartLine(), $node->else->getEndLine(), ++$this->nextBranch); } - if ([] === $node->items) { - return [$node->getEndLine()]; + return; + } + + if ($node instanceof Node\Expr\BinaryOp\Coalesce) { + if ($node->getStartLine() !== $node->getEndLine()) { + $this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch); } - if ($node->items[0] instanceof ArrayItem) { - return [$node->items[0]->getStartLine()]; + 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 ClassMethod) { - if ($node->name->name !== '__construct') { - 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); } - $existsAPromotedProperty = false; + if ([] !== $node->cond) { + if (null === $startLine) { + $startLine = $node->cond[0]->getStartLine(); + } + + end($node->cond); + + $endLine = current($node->cond)->getEndLine(); - foreach ($node->getParams() as $param) { - if (0 !== ($param->flags & Class_::VISIBILITY_MODIFIER_MASK)) { - $existsAPromotedProperty = true; + reset($node->cond); + } - break; + if ([] !== $node->loop) { + if (null === $startLine) { + $startLine = $node->loop[0]->getStartLine(); } + + end($node->loop); + + $endLine = current($node->loop)->getEndLine(); + + reset($node->loop); } - if ($existsAPromotedProperty) { - // Only the line with `function` keyword should be listed here - // but `nikic/php-parser` doesn't provide a way to fetch it - return range($node->getStartLine(), $node->name->getEndLine()); + if (null === $startLine || null === $endLine) { + return; } - return []; - } + $this->setLineBranch( + $startLine, + $endLine, + ++$this->nextBranch + ); - if ($node instanceof MethodCall) { - return [$node->name->getStartLine()]; + return; } - if ($node instanceof Ternary) { - $lines = [$node->cond->getStartLine()]; + if ($node instanceof Node\Stmt\Foreach_) { + $this->setLineBranch( + $node->expr->getStartLine(), + $node->valueVar->getEndLine(), + ++$this->nextBranch + ); - if (null !== $node->if) { - $lines[] = $node->if->getStartLine(); - } + return; + } - $lines[] = $node->else->getStartLine(); + if ($node instanceof Node\Stmt\While_ || + $node instanceof Node\Stmt\Do_) { + $this->setLineBranch( + $node->cond->getStartLine(), + $node->cond->getEndLine(), + ++$this->nextBranch + ); - return $lines; + return; } - if ($node instanceof Match_) { - return [$node->cond->getStartLine()]; + 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 MatchArm) { - return [$node->body->getStartLine()]; + 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 ($node instanceof Expression && ( - $node->expr instanceof Cast || - $node->expr instanceof Match_ || - $node->expr instanceof MethodCall - )) { - return []; + if (isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) { + return; } - if ($node instanceof Return_) { - $this->returns[] = $node; + $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++; - return []; + 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]); + } } - return [$node->getStartLine()]; + $this->executableLinesGroupedByBranch = array_diff_key( + $this->executableLinesGroupedByBranch, + $this->unsets + ); } - private function isExecutable(Node $node): bool + public function executableLinesGroupedByBranch(): array { - return $node instanceof Assign || - $node instanceof ArrayDimFetch || - $node instanceof Array_ || - $node instanceof BinaryOp || - $node instanceof Break_ || - $node instanceof CallLike || - $node instanceof Case_ || - $node instanceof Cast || - $node instanceof Catch_ || - $node instanceof ClassMethod || - $node instanceof Closure || - $node instanceof Continue_ || - $node instanceof Do_ || - $node instanceof Echo_ || - $node instanceof ElseIf_ || - $node instanceof Else_ || - $node instanceof Encapsed || - $node instanceof Expression || - $node instanceof Finally_ || - $node instanceof For_ || - $node instanceof Foreach_ || - $node instanceof Goto_ || - $node instanceof If_ || - $node instanceof Match_ || - $node instanceof MatchArm || - $node instanceof MethodCall || - $node instanceof NullsafePropertyFetch || - $node instanceof PropertyFetch || - $node instanceof Return_ || - $node instanceof StaticPropertyFetch || - $node instanceof Switch_ || - $node instanceof Ternary || - $node instanceof Throw_ || - $node instanceof TryCatch || - $node instanceof Unset_ || - $node instanceof While_; + return $this->executableLinesGroupedByBranch; + } + + private function setLineBranch(int $start, int $end, int $branch): void + { + foreach (range($start, $end) as $line) { + $this->executableLinesGroupedByBranch[$line] = $branch; + } } } -- cgit v1.2.3