Missing dependancies
This commit is contained in:
+50
@@ -2,6 +2,49 @@
|
||||
|
||||
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
|
||||
|
||||
## [9.2.26] - 2023-03-06
|
||||
|
||||
### Changed
|
||||
|
||||
* Improved the legend on the file pages of the HTML code coverage report
|
||||
|
||||
## [9.2.25] - 2023-02-25
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#981](https://github.com/sebastianbergmann/php-code-coverage/issues/981): `CodeUnitFindingVisitor` does not support DNF types
|
||||
|
||||
## [9.2.24] - 2023-01-26
|
||||
|
||||
### Changed
|
||||
|
||||
* [#970](https://github.com/sebastianbergmann/php-code-coverage/issues/970): CSS and JavaScript assets are now referenced using `?v=%s` URLs in the HTML report to avoid cache issues
|
||||
|
||||
## [9.2.23] - 2022-12-28
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#971](https://github.com/sebastianbergmann/php-code-coverage/issues/971): PHP report does not handle serialized code coverage data larger than 2 GB
|
||||
* [#974](https://github.com/sebastianbergmann/php-code-coverage/issues/974): Executable line analysis fails for declarations with enumerations and unions
|
||||
|
||||
## [9.2.22] - 2022-12-18
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#969](https://github.com/sebastianbergmann/php-code-coverage/pull/969): Fixed identifying line with `throw` as executable
|
||||
|
||||
## [9.2.21] - 2022-12-14
|
||||
|
||||
### Changed
|
||||
|
||||
* [#964](https://github.com/sebastianbergmann/php-code-coverage/pull/964): Changed how executable lines are identified
|
||||
|
||||
## [9.2.20] - 2022-12-13
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#960](https://github.com/sebastianbergmann/php-code-coverage/issues/960): New body font-size is way too big
|
||||
|
||||
## [9.2.19] - 2022-11-18
|
||||
|
||||
### Fixed
|
||||
@@ -433,6 +476,13 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt
|
||||
|
||||
* This component is no longer supported on PHP 7.1
|
||||
|
||||
[9.2.26]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.25...9.2.26
|
||||
[9.2.25]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.24...9.2.25
|
||||
[9.2.24]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.23...9.2.24
|
||||
[9.2.23]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.22...9.2.23
|
||||
[9.2.22]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.21...9.2.22
|
||||
[9.2.21]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.20...9.2.21
|
||||
[9.2.20]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.19...9.2.20
|
||||
[9.2.19]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.18...9.2.19
|
||||
[9.2.18]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.17...9.2.18
|
||||
[9.2.17]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.16...9.2.17
|
||||
+21
-25
@@ -1,33 +1,29 @@
|
||||
php-code-coverage
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2009-2022, Sebastian Bergmann <sebastian@phpunit.de>.
|
||||
Copyright (c) 2009-2023, Sebastian Bergmann
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of Sebastian Bergmann nor the names of his
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
+3
-3
@@ -32,7 +32,7 @@
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"nikic/php-parser": "^4.14",
|
||||
"nikic/php-parser": "^4.15",
|
||||
"phpunit/php-file-iterator": "^3.0.3",
|
||||
"phpunit/php-text-template": "^2.0.2",
|
||||
"sebastian/code-unit-reverse-lookup": "^2.0.2",
|
||||
@@ -46,8 +46,8 @@
|
||||
"phpunit/phpunit": "^9.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-pcov": "*",
|
||||
"ext-xdebug": "*"
|
||||
"ext-pcov": "PHP extension that provides line coverage",
|
||||
"ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
|
||||
@@ -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' => '',
|
||||
]
|
||||
);
|
||||
|
||||
+29
-1
@@ -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;
|
||||
}
|
||||
|
||||
Vendored
+6
-6
@@ -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() {
|
||||
|
||||
+6
-6
@@ -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() {
|
||||
|
||||
Vendored
+3
-3
@@ -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>
|
||||
|
||||
+3
-3
@@ -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>
|
||||
|
||||
+7
-7
@@ -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>
|
||||
|
||||
Vendored
+7
-7
@@ -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));
|
||||
|
||||
+38
-19
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+317
-285
@@ -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_;
|
||||
}
|
||||
}
|
||||
|
||||
+33
-33
@@ -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])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user