Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. RawCodeCoverageData.php

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\Data
View 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&lt;string, XdebugFunctionsCoverageType&gt;
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&lt;string, XdebugFunctionsCoverageType&gt;
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&lt;string, XdebugFunctionsCoverageType&gt; $functionCoverage
RSS feed
Powered by Drupal