Missing dependancies

This commit is contained in:
tokslaw7
2023-06-21 12:19:22 +00:00
parent fbf3f180c2
commit 421f25c80d
2356 changed files with 342670 additions and 4743 deletions
@@ -485,9 +485,16 @@ final class CodeCoverage
continue;
}
$linesToBranchMap = $this->analyser()->executableLinesIn($filename);
$data->keepLineCoverageDataOnlyForLines(
$filename,
$this->analyser()->executableLinesIn($filename)
array_keys($linesToBranchMap)
);
$data->markExecutableLineByBranch(
$filename,
$linesToBranchMap
);
}
}
@@ -643,7 +650,7 @@ final class CodeCoverage
} catch (\ReflectionException $e) {
throw new ReflectionException(
$e->getMessage(),
(int) $e->getCode(),
$e->getCode(),
$e
);
}
@@ -91,7 +91,7 @@ final class RawCodeCoverageData
{
$lineCoverage = [];
foreach ($analyser->executableLinesIn($filename) as $line) {
foreach ($analyser->executableLinesIn($filename) as $line => $branch) {
$lineCoverage[$line] = Driver::LINE_NOT_EXECUTED;
}
@@ -141,6 +141,42 @@ final class RawCodeCoverageData
);
}
/**
* @param int[] $linesToBranchMap
*/
public function markExecutableLineByBranch(string $filename, array $linesToBranchMap): void
{
if (!isset($this->lineCoverage[$filename])) {
return;
}
$linesByBranch = [];
foreach ($linesToBranchMap as $line => $branch) {
$linesByBranch[$branch][] = $line;
}
foreach ($this->lineCoverage[$filename] as $line => $lineStatus) {
if (!isset($linesToBranchMap[$line])) {
continue;
}
$branch = $linesToBranchMap[$line];
if (!isset($linesByBranch[$branch])) {
continue;
}
foreach ($linesByBranch[$branch] as $lineInBranch) {
$this->lineCoverage[$filename][$lineInBranch] = $lineStatus;
}
if (Driver::LINE_EXECUTED === $lineStatus) {
unset($linesByBranch[$branch]);
}
}
}
/**
* @param int[] $lines
*/
@@ -134,7 +134,7 @@ final class File extends Renderer
[
'items' => $this->renderItems($node),
'lines' => $this->renderSourceWithLineCoverage($node),
'legend' => '<p><span class="success"><strong>Executed</strong></span><span class="danger"><strong>Not Executed</strong></span><span class="warning"><strong>Dead Code</strong></span></p>',
'legend' => '<p><span class="legend covered-by-small-tests">Covered by small (and larger) tests</span><span class="legend covered-by-medium-tests">Covered by medium (and large) tests</span><span class="legend covered-by-large-tests">Covered by large tests (and tests of unknown size)</span><span class="legend not-covered">Not covered</span><span class="legend not-coverable">Not coverable</span></p>',
'structure' => '',
]
);
@@ -1,6 +1,6 @@
body {
font-family: sans-serif;
font-size: 2em;
font-size: 1em;
font-kerning: normal;
font-variant-ligatures: common-ligatures;
text-rendering: optimizeLegibility;
@@ -128,3 +128,31 @@ table + .structure-heading {
border-top: 1px solid lightgrey;
padding-top: 0.5em;
}
.legend {
font-weight: bold;
margin-right: 2px;
padding-left: 10px;
padding-right: 10px;
text-align: center;
}
.covered-by-small-tests {
background-color: #99cb84;
}
.covered-by-medium-tests {
background-color: #c3e3b5;
}
.covered-by-large-tests {
background-color: #dff0d8;
}
.not-covered {
background-color: #f2dede;
}
.not-coverable {
background-color: #fcf8e3;
}
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<title>Dashboard for {{full_path}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{path_to_root}}_css/bootstrap.min.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/nv.d3.min.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/bootstrap.min.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/nv.d3.min.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/custom.css" rel="stylesheet" type="text/css">
</head>
<body>
@@ -137,9 +137,9 @@
</p>
</footer>
</div>
<script src="{{path_to_root}}_js/jquery.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/d3.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/nv.d3.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/jquery.min.js?v={{version}}" type="text/javascript"></script>
<script src="{{path_to_root}}_js/d3.min.js?v={{version}}" type="text/javascript"></script>
<script src="{{path_to_root}}_js/nv.d3.min.js?v={{version}}" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
nv.addGraph(function() {
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<title>Dashboard for {{full_path}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{path_to_root}}_css/bootstrap.min.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/nv.d3.min.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/bootstrap.min.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/nv.d3.min.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/custom.css" rel="stylesheet" type="text/css">
</head>
<body>
@@ -137,9 +137,9 @@
</p>
</footer>
</div>
<script src="{{path_to_root}}_js/jquery.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/d3.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/nv.d3.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/jquery.min.js?v={{version}}" type="text/javascript"></script>
<script src="{{path_to_root}}_js/d3.min.js?v={{version}}" type="text/javascript"></script>
<script src="{{path_to_root}}_js/nv.d3.min.js?v={{version}}" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
nv.addGraph(function() {
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<title>Code Coverage for {{full_path}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{path_to_root}}_css/bootstrap.min.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/octicons.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/bootstrap.min.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/octicons.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/custom.css" rel="stylesheet" type="text/css">
</head>
<body>
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<title>Code Coverage for {{full_path}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{path_to_root}}_css/bootstrap.min.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/octicons.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/bootstrap.min.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/octicons.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/custom.css" rel="stylesheet" type="text/css">
</head>
<body>
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<title>Code Coverage for {{full_path}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{path_to_root}}_css/bootstrap.min.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/octicons.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/bootstrap.min.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/octicons.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/custom.css" rel="stylesheet" type="text/css">
</head>
<body>
@@ -57,9 +57,9 @@
</a>
</footer>
</div>
<script src="{{path_to_root}}_js/jquery.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/popper.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/bootstrap.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/file.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/jquery.min.js?v={{version}}" type="text/javascript"></script>
<script src="{{path_to_root}}_js/popper.min.js?v={{version}}" type="text/javascript"></script>
<script src="{{path_to_root}}_js/bootstrap.min.js?v={{version}}" type="text/javascript"></script>
<script src="{{path_to_root}}_js/file.js?v={{version}}" type="text/javascript"></script>
</body>
</html>
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<title>Code Coverage for {{full_path}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{path_to_root}}_css/bootstrap.min.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/octicons.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/bootstrap.min.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/octicons.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/style.css?v={{version}}" rel="stylesheet" type="text/css">
<link href="{{path_to_root}}_css/custom.css" rel="stylesheet" type="text/css">
</head>
<body>
@@ -59,9 +59,9 @@
</a>
</footer>
</div>
<script src="{{path_to_root}}_js/jquery.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/popper.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/bootstrap.min.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/file.js" type="text/javascript"></script>
<script src="{{path_to_root}}_js/jquery.min.js?v={{version}}" type="text/javascript"></script>
<script src="{{path_to_root}}_js/popper.min.js?v={{version}}" type="text/javascript"></script>
<script src="{{path_to_root}}_js/bootstrap.min.js?v={{version}}" type="text/javascript"></script>
<script src="{{path_to_root}}_js/file.js?v={{version}}" type="text/javascript"></script>
</body>
</html>
@@ -12,7 +12,6 @@ namespace SebastianBergmann\CodeCoverage\Report;
use function dirname;
use function file_put_contents;
use function serialize;
use function sprintf;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
@@ -21,14 +20,8 @@ final class PHP
{
public function process(CodeCoverage $coverage, ?string $target = null): string
{
$buffer = sprintf(
"<?php
return \unserialize(<<<'END_OF_COVERAGE_SERIALIZATION'%s%s%sEND_OF_COVERAGE_SERIALIZATION%s);",
PHP_EOL,
serialize($coverage),
PHP_EOL,
PHP_EOL
);
$buffer = "<?php
return \unserialize(<<<'END_OF_COVERAGE_SERIALIZATION'" . PHP_EOL . serialize($coverage) . PHP_EOL . 'END_OF_COVERAGE_SERIALIZATION' . PHP_EOL . ');';
if ($target !== null) {
Filesystem::createDirectory(dirname($target));
@@ -26,6 +26,7 @@ use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\UnionType;
use PhpParser\NodeAbstract;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use SebastianBergmann\Complexity\CyclomaticComplexityCalculatingVisitor;
@@ -180,8 +181,12 @@ final class CodeUnitFindingVisitor extends NodeVisitorAbstract
return '?' . $type->type;
}
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return $this->unionOrIntersectionAsString($type);
if ($type instanceof UnionType) {
return $this->unionTypeAsString($type);
}
if ($type instanceof IntersectionType) {
return $this->intersectionTypeAsString($type);
}
return $type->toString();
@@ -298,29 +303,43 @@ final class CodeUnitFindingVisitor extends NodeVisitorAbstract
return trim(rtrim($namespacedName, $name), '\\');
}
/**
* @psalm-param UnionType|IntersectionType $type
*/
private function unionOrIntersectionAsString(ComplexType $type): string
private function unionTypeAsString(UnionType $node): string
{
if ($type instanceof UnionType) {
$separator = '|';
} else {
$separator = '&';
}
$types = [];
foreach ($type->types as $_type) {
if ($_type instanceof Name) {
$types[] = $_type->toCodeString();
} else {
assert($_type instanceof Identifier);
foreach ($node->types as $type) {
if ($type instanceof IntersectionType) {
$types[] = '(' . $this->intersectionTypeAsString($type) . ')';
$types[] = $_type->toString();
continue;
}
$types[] = $this->typeAsString($type);
}
return implode($separator, $types);
return implode('|', $types);
}
private function intersectionTypeAsString(IntersectionType $node): string
{
$types = [];
foreach ($node->types as $type) {
$types[] = $this->typeAsString($type);
}
return implode('&', $types);
}
/**
* @psalm-param Identifier|Name $node $node
*/
private function typeAsString(NodeAbstract $node): string
{
if ($node instanceof Name) {
return $node->toCodeString();
}
return $node->toString();
}
}
@@ -9,46 +9,19 @@
*/
namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
use function array_reverse;
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 sort;
use function reset;
use function sprintf;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\CallLike;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\Match_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\NullsafePropertyFetch;
use PhpParser\Node\Expr\Print_;
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\ElseIf_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\For_;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Goto_;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\Throw_;
use PhpParser\Node\Stmt\Unset_;
use PhpParser\Node\Stmt\While_;
use PhpParser\NodeAbstract;
use PhpParser\NodeVisitorAbstract;
/**
@@ -57,285 +30,344 @@ use PhpParser\NodeVisitorAbstract;
final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
{
/**
* @psalm-var array<int, int>
* @var int
*/
private $executableLines = [];
private $nextBranch = 0;
/**
* @psalm-var array<int, int>
* @var string
*/
private $propertyLines = [];
private $source;
/**
* @psalm-var array<int, Function_|ClassMethod|Return_|Expression|Assign|Array_>
* @var array<int, int>
*/
private $returns = [];
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
{
if (!$node instanceof NodeAbstract) {
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;
}
$this->savePropertyLines($node);
if ($node instanceof Node\Stmt\Interface_) {
foreach (range($node->getStartLine(), $node->getEndLine()) as $line) {
$this->unsets[$line] = true;
}
if (!$this->isExecutable($node)) {
return;
}
foreach ($this->getLines($node, false) as $line) {
if (isset($this->propertyLines[$line])) {
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\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;
}
if ($node instanceof Node\Stmt\Throw_) {
$this->setLineBranch($node->expr->getEndLine(), $node->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_) {
$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;
}
$this->executableLines[$line] = $line;
$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);
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
{
$this->computeReturns();
$lines = explode("\n", $this->source);
sort($this->executableLines);
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 array<int, int>
*/
public function executableLines(): array
public function executableLinesGroupedByBranch(): array
{
return $this->executableLines;
return $this->executableLinesGroupedByBranch;
}
private function savePropertyLines(Node $node): void
private function setLineBranch(int $start, int $end, int $branch): void
{
if ($node instanceof Property) {
foreach (range($node->getStartLine(), $node->getEndLine()) as $index) {
$this->propertyLines[$index] = $index;
}
foreach (range($start, $end) as $line) {
$this->executableLinesGroupedByBranch[$line] = $branch;
}
}
private function computeReturns(): void
{
foreach (array_reverse($this->returns) as $node) {
foreach (range($node->getStartLine(), $node->getEndLine()) as $index) {
if (isset($this->executableLines[$index])) {
continue;
}
}
foreach ($this->getLines($node, true) as $line) {
$this->executableLines[$line] = $line;
}
}
}
/**
* @return int[]
*/
private function getLines(NodeAbstract $node, bool $fromReturns): array
{
if ($node instanceof Function_ ||
$node instanceof ClassMethod ||
$node instanceof Return_ ||
$node instanceof Expression ||
$node instanceof Assign ||
$node instanceof Array_
) {
if (!$fromReturns) {
$this->returns[] = $node;
if ($node instanceof ClassMethod && $node->name->name === '__construct') {
$existsAPromotedProperty = false;
foreach ($node->getParams() as $param) {
if (0 !== ($param->flags & Class_::VISIBILITY_MODIFIER_MASK)) {
$existsAPromotedProperty = true;
break;
}
}
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());
}
}
return [];
}
// ugly fix for non-fully AST based processing
// self::afterTraverse()/self::computeReturns() should be rewritten using self::leaveNode()
foreach (range($node->getStartLine(), $node->getEndLine()) as $index) {
if (isset($this->executableLines[$index]) && !($node instanceof Assign)) {
return [];
}
}
// empty function
if ($node instanceof Function_) {
return [$node->getEndLine()];
}
// empty method
if ($node instanceof ClassMethod) {
if (null === $node->stmts) { // method without body (interface prototype)
return [];
}
return [$node->getEndLine()];
}
}
if ($node instanceof Return_) {
if ($node->expr === null) {
return [$node->getEndLine()];
}
return $this->getLines($node->expr, $fromReturns);
}
if ($node instanceof Expression) {
return $this->getLines($node->expr, $fromReturns);
}
if ($node instanceof Assign) {
return [$this->getNodeStartLine($node->var)];
}
if ($node instanceof BinaryOp) {
return $fromReturns ? $this->getLines($node->right, $fromReturns) : [];
}
if ($node instanceof PropertyFetch ||
$node instanceof NullsafePropertyFetch ||
$node instanceof StaticPropertyFetch) {
return [$this->getNodeStartLine($node->name)];
}
if ($node instanceof ArrayDimFetch && null !== $node->dim) {
return [$this->getNodeStartLine($node->dim)];
}
if ($node instanceof MethodCall) {
return [$this->getNodeStartLine($node->name)];
}
if ($node instanceof Ternary) {
$lines = [$this->getNodeStartLine($node->cond)];
if (null !== $node->if) {
$lines[] = $this->getNodeStartLine($node->if);
}
$lines[] = $this->getNodeStartLine($node->else);
return $lines;
}
if ($node instanceof Match_) {
return [$this->getNodeStartLine($node->cond)];
}
if ($node instanceof MatchArm) {
return [$this->getNodeStartLine($node->body)];
}
// TODO this concept should be extended for every statement class like Foreach_, For_, ...
if ($node instanceof If_ ||
$node instanceof ElseIf_ ||
$node instanceof While_ ||
$node instanceof Do_) {
return [$this->getNodeStartLine($node->cond)];
}
if ($node instanceof Case_) {
if (null === $node->cond) { // default case
return [];
}
return [$this->getNodeStartLine($node->cond)];
}
if ($node instanceof Catch_) {
return [$this->getNodeStartLine($node->types[0])];
}
return [$this->getNodeStartLine($node)];
}
private function getNodeStartLine(NodeAbstract $node): int
{
if ($node instanceof Node\Expr\Cast ||
$node instanceof Node\Expr\BooleanNot ||
$node instanceof Node\Expr\UnaryMinus ||
$node instanceof Node\Expr\UnaryPlus
) {
return $this->getNodeStartLine($node->expr);
}
if ($node instanceof BinaryOp) {
return $this->getNodeStartLine($node->right);
}
if ($node instanceof Node\Scalar\String_ && (
$node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC ||
$node->getAttribute('kind') === Node\Scalar\String_::KIND_NOWDOC
)) {
return $node->getStartLine() + 1;
}
if ($node instanceof Array_) {
if ([] === $node->items || $node->items[0] === null) {
return $node->getEndLine();
}
return $this->getNodeStartLine($node->items[0]->value);
}
if ($node instanceof Assign) {
return $this->getNodeStartLine($node->expr);
}
return $node->getStartLine(); // $node should be only a scalar here
}
private function isExecutable(Node $node): bool
{
return $node instanceof Assign ||
$node instanceof ArrayDimFetch ||
$node instanceof BinaryOp ||
$node instanceof Break_ ||
$node instanceof CallLike ||
$node instanceof Case_ ||
$node instanceof Catch_ ||
$node instanceof ClassMethod ||
$node instanceof Closure ||
$node instanceof Continue_ ||
$node instanceof Do_ ||
$node instanceof Echo_ ||
$node instanceof ElseIf_ ||
$node instanceof Encapsed ||
$node instanceof Expression ||
$node instanceof For_ ||
$node instanceof Foreach_ ||
$node instanceof Function_ ||
$node instanceof Goto_ ||
$node instanceof If_ ||
$node instanceof Match_ ||
$node instanceof MatchArm ||
$node instanceof MethodCall ||
$node instanceof NullsafePropertyFetch ||
$node instanceof Print_ ||
$node instanceof PropertyFetch ||
$node instanceof Return_ ||
$node instanceof StaticPropertyFetch ||
$node instanceof Ternary ||
$node instanceof Throw_ ||
$node instanceof Unset_ ||
$node instanceof While_;
}
}
@@ -15,6 +15,7 @@ use function assert;
use function file_get_contents;
use function is_array;
use function max;
use function range;
use function sort;
use function sprintf;
use function substr_count;
@@ -155,7 +156,7 @@ final class ParsingFileAnalyser implements FileAnalyser
$codeUnitFindingVisitor = new CodeUnitFindingVisitor;
$lineCountingVisitor = new LineCountingVisitor($linesOfCode);
$ignoredLinesFindingVisitor = new IgnoredLinesFindingVisitor($this->useAnnotationsForIgnoringCode, $this->ignoreDeprecatedCode);
$executableLinesFindingVisitor = new ExecutableLinesFindingVisitor;
$executableLinesFindingVisitor = new ExecutableLinesFindingVisitor($source);
$traverser->addVisitor(new NameResolver);
$traverser->addVisitor(new ParentConnectingVisitor);
@@ -174,7 +175,7 @@ final class ParsingFileAnalyser implements FileAnalyser
$filename,
$error->getMessage()
),
(int) $error->getCode(),
$error->getCode(),
$error
);
}
@@ -183,7 +184,7 @@ final class ParsingFileAnalyser implements FileAnalyser
$this->classes[$filename] = $codeUnitFindingVisitor->classes();
$this->traits[$filename] = $codeUnitFindingVisitor->traits();
$this->functions[$filename] = $codeUnitFindingVisitor->functions();
$this->executableLines[$filename] = $executableLinesFindingVisitor->executableLines();
$this->executableLines[$filename] = $executableLinesFindingVisitor->executableLinesGroupedByBranch();
$this->ignoredLines[$filename] = [];
$this->findLinesIgnoredByLineBasedAnnotations($filename, $source, $this->useAnnotationsForIgnoringCode);
@@ -208,45 +209,44 @@ final class ParsingFileAnalyser implements FileAnalyser
private function findLinesIgnoredByLineBasedAnnotations(string $filename, string $source, bool $useAnnotationsForIgnoringCode): void
{
$ignore = false;
$stop = false;
if (!$useAnnotationsForIgnoringCode) {
return;
}
$start = false;
foreach (token_get_all($source) as $token) {
if (!is_array($token)) {
if (!is_array($token) ||
!(T_COMMENT === $token[0] || T_DOC_COMMENT === $token[0])) {
continue;
}
switch ($token[0]) {
case T_COMMENT:
case T_DOC_COMMENT:
if (!$useAnnotationsForIgnoringCode) {
break;
}
$comment = trim($token[1]);
$comment = trim($token[1]);
if ($comment === '// @codeCoverageIgnore' ||
$comment === '//@codeCoverageIgnore') {
$ignore = true;
$stop = true;
} elseif ($comment === '// @codeCoverageIgnoreStart' ||
$comment === '//@codeCoverageIgnoreStart') {
$ignore = true;
} elseif ($comment === '// @codeCoverageIgnoreEnd' ||
$comment === '//@codeCoverageIgnoreEnd') {
$stop = true;
}
break;
}
if ($ignore) {
if ($comment === '// @codeCoverageIgnore' ||
$comment === '//@codeCoverageIgnore') {
$this->ignoredLines[$filename][] = $token[2];
if ($stop) {
$ignore = false;
$stop = false;
continue;
}
if ($comment === '// @codeCoverageIgnoreStart' ||
$comment === '//@codeCoverageIgnoreStart') {
$start = $token[2];
continue;
}
if ($comment === '// @codeCoverageIgnoreEnd' ||
$comment === '//@codeCoverageIgnoreEnd') {
if (false === $start) {
$start = $token[2];
}
$this->ignoredLines[$filename] = array_merge(
$this->ignoredLines[$filename],
range($start, $token[2])
);
}
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ final class Version
public static function id(): string
{
if (self::$version === null) {
self::$version = (new VersionId('9.2.19', dirname(__DIR__)))->getVersion();
self::$version = (new VersionId('9.2.26', dirname(__DIR__)))->getVersion();
}
return self::$version;