class RawCodeCoverageData
@internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
@psalm-import-type XdebugFunctionsCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver @psalm-import-type XdebugCodeCoverageWithoutPathCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver @psalm-import-type XdebugCodeCoverageWithPathCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver
Hierarchy
- class \SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData
Expanded class hierarchy of RawCodeCoverageData
5 files declare their use of RawCodeCoverageData
- CodeCoverage.php in vendor/
phpunit/ php-code-coverage/ src/ CodeCoverage.php - Driver.php in vendor/
phpunit/ php-code-coverage/ src/ Driver/ Driver.php - PcovDriver.php in vendor/
phpunit/ php-code-coverage/ src/ Driver/ PcovDriver.php - PhptTestCase.php in vendor/
phpunit/ phpunit/ src/ Runner/ PhptTestCase.php - XdebugDriver.php in vendor/
phpunit/ php-code-coverage/ src/ Driver/ XdebugDriver.php
File
-
vendor/
phpunit/ php-code-coverage/ src/ Data/ RawCodeCoverageData.php, line 37
Namespace
SebastianBergmann\CodeCoverage\DataView source
final class RawCodeCoverageData {
/**
* @var array<string, array<int>>
*/
private static array $emptyLineCache = [];
/**
* @psalm-var XdebugCodeCoverageWithoutPathCoverageType
*/
private array $lineCoverage;
/**
* @psalm-var array<string, XdebugFunctionsCoverageType>
*/
private array $functionCoverage;
/**
* @psalm-param XdebugCodeCoverageWithoutPathCoverageType $rawCoverage
*/
public static function fromXdebugWithoutPathCoverage(array $rawCoverage) : self {
return new self($rawCoverage, []);
}
/**
* @psalm-param XdebugCodeCoverageWithPathCoverageType $rawCoverage
*/
public static function fromXdebugWithPathCoverage(array $rawCoverage) : self {
$lineCoverage = [];
$functionCoverage = [];
foreach ($rawCoverage as $file => $fileCoverageData) {
// Xdebug annotates the function name of traits, strip that off
foreach ($fileCoverageData['functions'] as $existingKey => $data) {
if (str_ends_with($existingKey, '}') && !str_starts_with($existingKey, '{')) {
// don't want to catch {main}
$newKey = preg_replace('/\\{.*}$/', '', $existingKey);
$fileCoverageData['functions'][$newKey] = $data;
unset($fileCoverageData['functions'][$existingKey]);
}
}
$lineCoverage[$file] = $fileCoverageData['lines'];
$functionCoverage[$file] = $fileCoverageData['functions'];
}
return new self($lineCoverage, $functionCoverage);
}
public static function fromUncoveredFile(string $filename, FileAnalyser $analyser) : self {
$lineCoverage = [];
foreach ($analyser->executableLinesIn($filename) as $line => $branch) {
$lineCoverage[$line] = Driver::LINE_NOT_EXECUTED;
}
return new self([
$filename => $lineCoverage,
], []);
}
/**
* @psalm-param XdebugCodeCoverageWithoutPathCoverageType $lineCoverage
* @psalm-param array<string, XdebugFunctionsCoverageType> $functionCoverage
*/
private function __construct(array $lineCoverage, array $functionCoverage) {
$this->lineCoverage = $lineCoverage;
$this->functionCoverage = $functionCoverage;
$this->skipEmptyLines();
}
public function clear() : void {
$this->lineCoverage = $this->functionCoverage = [];
}
/**
* @psalm-return XdebugCodeCoverageWithoutPathCoverageType
*/
public function lineCoverage() : array {
return $this->lineCoverage;
}
/**
* @psalm-return array<string, XdebugFunctionsCoverageType>
*/
public function functionCoverage() : array {
return $this->functionCoverage;
}
public function removeCoverageDataForFile(string $filename) : void {
unset($this->lineCoverage[$filename], $this->functionCoverage[$filename]);
}
/**
* @param int[] $lines
*/
public function keepLineCoverageDataOnlyForLines(string $filename, array $lines) : void {
if (!isset($this->lineCoverage[$filename])) {
return;
}
$this->lineCoverage[$filename] = array_intersect_key($this->lineCoverage[$filename], array_flip($lines));
}
/**
* @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
*/
public function keepFunctionCoverageDataOnlyForLines(string $filename, array $lines) : void {
if (!isset($this->functionCoverage[$filename])) {
return;
}
foreach ($this->functionCoverage[$filename] as $functionName => $functionData) {
foreach ($functionData['branches'] as $branchId => $branch) {
if (count(array_diff(range($branch['line_start'], $branch['line_end']), $lines)) > 0) {
unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]);
foreach ($functionData['paths'] as $pathId => $path) {
if (in_array($branchId, $path['path'], true)) {
unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]);
}
}
}
}
}
}
/**
* @param int[] $lines
*/
public function removeCoverageDataForLines(string $filename, array $lines) : void {
if (empty($lines)) {
return;
}
if (!isset($this->lineCoverage[$filename])) {
return;
}
$this->lineCoverage[$filename] = array_diff_key($this->lineCoverage[$filename], array_flip($lines));
if (isset($this->functionCoverage[$filename])) {
foreach ($this->functionCoverage[$filename] as $functionName => $functionData) {
foreach ($functionData['branches'] as $branchId => $branch) {
if (count(array_intersect($lines, range($branch['line_start'], $branch['line_end']))) > 0) {
unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]);
foreach ($functionData['paths'] as $pathId => $path) {
if (in_array($branchId, $path['path'], true)) {
unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]);
}
}
}
}
}
}
}
/**
* At the end of a file, the PHP interpreter always sees an implicit return. Where this occurs in a file that has
* e.g. a class definition, that line cannot be invoked from a test and results in confusing coverage. This engine
* implementation detail therefore needs to be masked which is done here by simply ensuring that all empty lines
* are skipped over for coverage purposes.
*
* @see https://github.com/sebastianbergmann/php-code-coverage/issues/799
*/
private function skipEmptyLines() : void {
foreach ($this->lineCoverage as $filename => $coverage) {
foreach ($this->getEmptyLinesForFile($filename) as $emptyLine) {
unset($this->lineCoverage[$filename][$emptyLine]);
}
}
}
private function getEmptyLinesForFile(string $filename) : array {
if (!isset(self::$emptyLineCache[$filename])) {
self::$emptyLineCache[$filename] = [];
if (is_file($filename)) {
$sourceLines = explode("\n", file_get_contents($filename));
foreach ($sourceLines as $line => $source) {
if (trim($source) === '') {
self::$emptyLineCache[$filename][] = $line + 1;
}
}
}
}
return self::$emptyLineCache[$filename];
}
}
Members
Title Sort descending | Modifiers | Object type | Summary |
---|---|---|---|
RawCodeCoverageData::$emptyLineCache | private static | property | |
RawCodeCoverageData::$functionCoverage | private | property | @psalm-var array<string, XdebugFunctionsCoverageType> |
RawCodeCoverageData::$lineCoverage | private | property | @psalm-var XdebugCodeCoverageWithoutPathCoverageType |
RawCodeCoverageData::clear | public | function | |
RawCodeCoverageData::fromUncoveredFile | public static | function | |
RawCodeCoverageData::fromXdebugWithoutPathCoverage | public static | function | @psalm-param XdebugCodeCoverageWithoutPathCoverageType $rawCoverage |
RawCodeCoverageData::fromXdebugWithPathCoverage | public static | function | @psalm-param XdebugCodeCoverageWithPathCoverageType $rawCoverage |
RawCodeCoverageData::functionCoverage | public | function | @psalm-return array<string, XdebugFunctionsCoverageType> |
RawCodeCoverageData::getEmptyLinesForFile | private | function | |
RawCodeCoverageData::keepFunctionCoverageDataOnlyForLines | public | function | |
RawCodeCoverageData::keepLineCoverageDataOnlyForLines | public | function | |
RawCodeCoverageData::lineCoverage | public | function | @psalm-return XdebugCodeCoverageWithoutPathCoverageType |
RawCodeCoverageData::markExecutableLineByBranch | public | function | |
RawCodeCoverageData::removeCoverageDataForFile | public | function | |
RawCodeCoverageData::removeCoverageDataForLines | public | function | |
RawCodeCoverageData::skipEmptyLines | private | function | At the end of a file, the PHP interpreter always sees an implicit return. Where this occurs in a file that has e.g. a class definition, that line cannot be invoked from a test and results in confusing coverage. This engine implementation detail… |
RawCodeCoverageData::__construct | private | function | @psalm-param XdebugCodeCoverageWithoutPathCoverageType $lineCoverage @psalm-param array<string, XdebugFunctionsCoverageType> $functionCoverage |