632 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			PHP
		
	
	
			
		
		
	
	
			632 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			PHP
		
	
	
<?php declare(strict_types=1);
 | 
						|
/*
 | 
						|
 * This file is part of phpunit/php-code-coverage.
 | 
						|
 *
 | 
						|
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 | 
						|
 *
 | 
						|
 * For the full copyright and license information, please view the LICENSE
 | 
						|
 * file that was distributed with this source code.
 | 
						|
 */
 | 
						|
namespace SebastianBergmann\CodeCoverage;
 | 
						|
 | 
						|
use function array_diff;
 | 
						|
use function array_diff_key;
 | 
						|
use function array_flip;
 | 
						|
use function array_keys;
 | 
						|
use function array_merge;
 | 
						|
use function array_merge_recursive;
 | 
						|
use function array_unique;
 | 
						|
use function count;
 | 
						|
use function explode;
 | 
						|
use function is_array;
 | 
						|
use function is_file;
 | 
						|
use function sort;
 | 
						|
use ReflectionClass;
 | 
						|
use SebastianBergmann\CodeCoverage\Data\ProcessedCodeCoverageData;
 | 
						|
use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData;
 | 
						|
use SebastianBergmann\CodeCoverage\Driver\Driver;
 | 
						|
use SebastianBergmann\CodeCoverage\Node\Builder;
 | 
						|
use SebastianBergmann\CodeCoverage\Node\Directory;
 | 
						|
use SebastianBergmann\CodeCoverage\StaticAnalysis\CachingFileAnalyser;
 | 
						|
use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser;
 | 
						|
use SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingFileAnalyser;
 | 
						|
use SebastianBergmann\CodeCoverage\Test\TestSize\TestSize;
 | 
						|
use SebastianBergmann\CodeCoverage\Test\TestStatus\TestStatus;
 | 
						|
use SebastianBergmann\CodeUnitReverseLookup\Wizard;
 | 
						|
 | 
						|
/**
 | 
						|
 * Provides collection functionality for PHP code coverage information.
 | 
						|
 *
 | 
						|
 * @psalm-type TestType = array{
 | 
						|
 *     size: string,
 | 
						|
 *     status: string,
 | 
						|
 * }
 | 
						|
 */
 | 
						|
final class CodeCoverage
 | 
						|
{
 | 
						|
    private const UNCOVERED_FILES = 'UNCOVERED_FILES';
 | 
						|
    private readonly Driver $driver;
 | 
						|
    private readonly Filter $filter;
 | 
						|
    private readonly Wizard $wizard;
 | 
						|
    private bool $checkForUnintentionallyCoveredCode = false;
 | 
						|
    private bool $includeUncoveredFiles              = true;
 | 
						|
    private bool $ignoreDeprecatedCode               = false;
 | 
						|
    private ?string $currentId                       = null;
 | 
						|
    private ?TestSize $currentSize                   = null;
 | 
						|
    private ProcessedCodeCoverageData $data;
 | 
						|
    private bool $useAnnotationsForIgnoringCode = true;
 | 
						|
 | 
						|
    /**
 | 
						|
     * @psalm-var array<string,list<int>>
 | 
						|
     */
 | 
						|
    private array $linesToBeIgnored = [];
 | 
						|
 | 
						|
    /**
 | 
						|
     * @psalm-var array<string, TestType>
 | 
						|
     */
 | 
						|
    private array $tests = [];
 | 
						|
 | 
						|
    /**
 | 
						|
     * @psalm-var list<class-string>
 | 
						|
     */
 | 
						|
    private array $parentClassesExcludedFromUnintentionallyCoveredCodeCheck = [];
 | 
						|
    private ?FileAnalyser $analyser                                         = null;
 | 
						|
    private ?string $cacheDirectory                                         = null;
 | 
						|
    private ?Directory $cachedReport                                        = null;
 | 
						|
 | 
						|
    public function __construct(Driver $driver, Filter $filter)
 | 
						|
    {
 | 
						|
        $this->driver = $driver;
 | 
						|
        $this->filter = $filter;
 | 
						|
        $this->data   = new ProcessedCodeCoverageData;
 | 
						|
        $this->wizard = new Wizard;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the code coverage information as a graph of node objects.
 | 
						|
     */
 | 
						|
    public function getReport(): Directory
 | 
						|
    {
 | 
						|
        if ($this->cachedReport === null) {
 | 
						|
            $this->cachedReport = (new Builder($this->analyser()))->build($this);
 | 
						|
        }
 | 
						|
 | 
						|
        return $this->cachedReport;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Clears collected code coverage data.
 | 
						|
     */
 | 
						|
    public function clear(): void
 | 
						|
    {
 | 
						|
        $this->currentId    = null;
 | 
						|
        $this->currentSize  = null;
 | 
						|
        $this->data         = new ProcessedCodeCoverageData;
 | 
						|
        $this->tests        = [];
 | 
						|
        $this->cachedReport = null;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @internal
 | 
						|
     */
 | 
						|
    public function clearCache(): void
 | 
						|
    {
 | 
						|
        $this->cachedReport = null;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the filter object used.
 | 
						|
     */
 | 
						|
    public function filter(): Filter
 | 
						|
    {
 | 
						|
        return $this->filter;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the collected code coverage data.
 | 
						|
     */
 | 
						|
    public function getData(bool $raw = false): ProcessedCodeCoverageData
 | 
						|
    {
 | 
						|
        if (!$raw) {
 | 
						|
            if ($this->includeUncoveredFiles) {
 | 
						|
                $this->addUncoveredFilesFromFilter();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return $this->data;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Sets the coverage data.
 | 
						|
     */
 | 
						|
    public function setData(ProcessedCodeCoverageData $data): void
 | 
						|
    {
 | 
						|
        $this->data = $data;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @psalm-return array<string, TestType>
 | 
						|
     */
 | 
						|
    public function getTests(): array
 | 
						|
    {
 | 
						|
        return $this->tests;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @psalm-param array<string, TestType> $tests
 | 
						|
     */
 | 
						|
    public function setTests(array $tests): void
 | 
						|
    {
 | 
						|
        $this->tests = $tests;
 | 
						|
    }
 | 
						|
 | 
						|
    public function start(string $id, ?TestSize $size = null, bool $clear = false): void
 | 
						|
    {
 | 
						|
        if ($clear) {
 | 
						|
            $this->clear();
 | 
						|
        }
 | 
						|
 | 
						|
        $this->currentId   = $id;
 | 
						|
        $this->currentSize = $size;
 | 
						|
 | 
						|
        $this->driver->start();
 | 
						|
 | 
						|
        $this->cachedReport = null;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @psalm-param array<string,list<int>> $linesToBeIgnored
 | 
						|
     */
 | 
						|
    public function stop(bool $append = true, ?TestStatus $status = null, array|false $linesToBeCovered = [], array $linesToBeUsed = [], array $linesToBeIgnored = []): RawCodeCoverageData
 | 
						|
    {
 | 
						|
        $data = $this->driver->stop();
 | 
						|
 | 
						|
        $this->linesToBeIgnored = array_merge_recursive(
 | 
						|
            $this->linesToBeIgnored,
 | 
						|
            $linesToBeIgnored,
 | 
						|
        );
 | 
						|
 | 
						|
        $this->append($data, null, $append, $status, $linesToBeCovered, $linesToBeUsed, $linesToBeIgnored);
 | 
						|
 | 
						|
        $this->currentId    = null;
 | 
						|
        $this->currentSize  = null;
 | 
						|
        $this->cachedReport = null;
 | 
						|
 | 
						|
        return $data;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @psalm-param array<string,list<int>> $linesToBeIgnored
 | 
						|
     *
 | 
						|
     * @throws ReflectionException
 | 
						|
     * @throws TestIdMissingException
 | 
						|
     * @throws UnintentionallyCoveredCodeException
 | 
						|
     */
 | 
						|
    public function append(RawCodeCoverageData $rawData, ?string $id = null, bool $append = true, ?TestStatus $status = null, array|false $linesToBeCovered = [], array $linesToBeUsed = [], array $linesToBeIgnored = []): void
 | 
						|
    {
 | 
						|
        if ($id === null) {
 | 
						|
            $id = $this->currentId;
 | 
						|
        }
 | 
						|
 | 
						|
        if ($id === null) {
 | 
						|
            throw new TestIdMissingException;
 | 
						|
        }
 | 
						|
 | 
						|
        $this->cachedReport = null;
 | 
						|
 | 
						|
        if ($status === null) {
 | 
						|
            $status = TestStatus::unknown();
 | 
						|
        }
 | 
						|
 | 
						|
        $size = $this->currentSize;
 | 
						|
 | 
						|
        if ($size === null) {
 | 
						|
            $size = TestSize::unknown();
 | 
						|
        }
 | 
						|
 | 
						|
        $this->applyFilter($rawData);
 | 
						|
 | 
						|
        $this->applyExecutableLinesFilter($rawData);
 | 
						|
 | 
						|
        if ($this->useAnnotationsForIgnoringCode) {
 | 
						|
            $this->applyIgnoredLinesFilter($rawData, $linesToBeIgnored);
 | 
						|
        }
 | 
						|
 | 
						|
        $this->data->initializeUnseenData($rawData);
 | 
						|
 | 
						|
        if (!$append) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        if ($id === self::UNCOVERED_FILES) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        $this->applyCoversAndUsesFilter(
 | 
						|
            $rawData,
 | 
						|
            $linesToBeCovered,
 | 
						|
            $linesToBeUsed,
 | 
						|
            $size,
 | 
						|
        );
 | 
						|
 | 
						|
        if (empty($rawData->lineCoverage())) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        $this->tests[$id] = [
 | 
						|
            'size'   => $size->asString(),
 | 
						|
            'status' => $status->asString(),
 | 
						|
        ];
 | 
						|
 | 
						|
        $this->data->markCodeAsExecutedByTestCase($id, $rawData);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Merges the data from another instance.
 | 
						|
     */
 | 
						|
    public function merge(self $that): void
 | 
						|
    {
 | 
						|
        $this->filter->includeFiles(
 | 
						|
            $that->filter()->files(),
 | 
						|
        );
 | 
						|
 | 
						|
        $this->data->merge($that->data);
 | 
						|
 | 
						|
        $this->tests = array_merge($this->tests, $that->getTests());
 | 
						|
 | 
						|
        $this->cachedReport = null;
 | 
						|
    }
 | 
						|
 | 
						|
    public function enableCheckForUnintentionallyCoveredCode(): void
 | 
						|
    {
 | 
						|
        $this->checkForUnintentionallyCoveredCode = true;
 | 
						|
    }
 | 
						|
 | 
						|
    public function disableCheckForUnintentionallyCoveredCode(): void
 | 
						|
    {
 | 
						|
        $this->checkForUnintentionallyCoveredCode = false;
 | 
						|
    }
 | 
						|
 | 
						|
    public function includeUncoveredFiles(): void
 | 
						|
    {
 | 
						|
        $this->includeUncoveredFiles = true;
 | 
						|
    }
 | 
						|
 | 
						|
    public function excludeUncoveredFiles(): void
 | 
						|
    {
 | 
						|
        $this->includeUncoveredFiles = false;
 | 
						|
    }
 | 
						|
 | 
						|
    public function enableAnnotationsForIgnoringCode(): void
 | 
						|
    {
 | 
						|
        $this->useAnnotationsForIgnoringCode = true;
 | 
						|
    }
 | 
						|
 | 
						|
    public function disableAnnotationsForIgnoringCode(): void
 | 
						|
    {
 | 
						|
        $this->useAnnotationsForIgnoringCode = false;
 | 
						|
    }
 | 
						|
 | 
						|
    public function ignoreDeprecatedCode(): void
 | 
						|
    {
 | 
						|
        $this->ignoreDeprecatedCode = true;
 | 
						|
    }
 | 
						|
 | 
						|
    public function doNotIgnoreDeprecatedCode(): void
 | 
						|
    {
 | 
						|
        $this->ignoreDeprecatedCode = false;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @psalm-assert-if-true !null $this->cacheDirectory
 | 
						|
     */
 | 
						|
    public function cachesStaticAnalysis(): bool
 | 
						|
    {
 | 
						|
        return $this->cacheDirectory !== null;
 | 
						|
    }
 | 
						|
 | 
						|
    public function cacheStaticAnalysis(string $directory): void
 | 
						|
    {
 | 
						|
        $this->cacheDirectory = $directory;
 | 
						|
    }
 | 
						|
 | 
						|
    public function doNotCacheStaticAnalysis(): void
 | 
						|
    {
 | 
						|
        $this->cacheDirectory = null;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @throws StaticAnalysisCacheNotConfiguredException
 | 
						|
     */
 | 
						|
    public function cacheDirectory(): string
 | 
						|
    {
 | 
						|
        if (!$this->cachesStaticAnalysis()) {
 | 
						|
            throw new StaticAnalysisCacheNotConfiguredException(
 | 
						|
                'The static analysis cache is not configured',
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        return $this->cacheDirectory;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @psalm-param class-string $className
 | 
						|
     */
 | 
						|
    public function excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(string $className): void
 | 
						|
    {
 | 
						|
        $this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck[] = $className;
 | 
						|
    }
 | 
						|
 | 
						|
    public function enableBranchAndPathCoverage(): void
 | 
						|
    {
 | 
						|
        $this->driver->enableBranchAndPathCoverage();
 | 
						|
    }
 | 
						|
 | 
						|
    public function disableBranchAndPathCoverage(): void
 | 
						|
    {
 | 
						|
        $this->driver->disableBranchAndPathCoverage();
 | 
						|
    }
 | 
						|
 | 
						|
    public function collectsBranchAndPathCoverage(): bool
 | 
						|
    {
 | 
						|
        return $this->driver->collectsBranchAndPathCoverage();
 | 
						|
    }
 | 
						|
 | 
						|
    public function detectsDeadCode(): bool
 | 
						|
    {
 | 
						|
        return $this->driver->detectsDeadCode();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @throws ReflectionException
 | 
						|
     * @throws UnintentionallyCoveredCodeException
 | 
						|
     */
 | 
						|
    private function applyCoversAndUsesFilter(RawCodeCoverageData $rawData, array|false $linesToBeCovered, array $linesToBeUsed, TestSize $size): void
 | 
						|
    {
 | 
						|
        if ($linesToBeCovered === false) {
 | 
						|
            $rawData->clear();
 | 
						|
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        if (empty($linesToBeCovered)) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        if ($this->checkForUnintentionallyCoveredCode && !$size->isMedium() && !$size->isLarge()) {
 | 
						|
            $this->performUnintentionallyCoveredCodeCheck($rawData, $linesToBeCovered, $linesToBeUsed);
 | 
						|
        }
 | 
						|
 | 
						|
        $rawLineData         = $rawData->lineCoverage();
 | 
						|
        $filesWithNoCoverage = array_diff_key($rawLineData, $linesToBeCovered);
 | 
						|
 | 
						|
        foreach (array_keys($filesWithNoCoverage) as $fileWithNoCoverage) {
 | 
						|
            $rawData->removeCoverageDataForFile($fileWithNoCoverage);
 | 
						|
        }
 | 
						|
 | 
						|
        if (is_array($linesToBeCovered)) {
 | 
						|
            foreach ($linesToBeCovered as $fileToBeCovered => $includedLines) {
 | 
						|
                $rawData->keepLineCoverageDataOnlyForLines($fileToBeCovered, $includedLines);
 | 
						|
                $rawData->keepFunctionCoverageDataOnlyForLines($fileToBeCovered, $includedLines);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private function applyFilter(RawCodeCoverageData $data): void
 | 
						|
    {
 | 
						|
        if ($this->filter->isEmpty()) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        foreach (array_keys($data->lineCoverage()) as $filename) {
 | 
						|
            if ($this->filter->isExcluded($filename)) {
 | 
						|
                $data->removeCoverageDataForFile($filename);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private function applyExecutableLinesFilter(RawCodeCoverageData $data): void
 | 
						|
    {
 | 
						|
        foreach (array_keys($data->lineCoverage()) as $filename) {
 | 
						|
            if (!$this->filter->isFile($filename)) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            $linesToBranchMap = $this->analyser()->executableLinesIn($filename);
 | 
						|
 | 
						|
            $data->keepLineCoverageDataOnlyForLines(
 | 
						|
                $filename,
 | 
						|
                array_keys($linesToBranchMap),
 | 
						|
            );
 | 
						|
 | 
						|
            $data->markExecutableLineByBranch(
 | 
						|
                $filename,
 | 
						|
                $linesToBranchMap,
 | 
						|
            );
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @psalm-param array<string,list<int>> $linesToBeIgnored
 | 
						|
     */
 | 
						|
    private function applyIgnoredLinesFilter(RawCodeCoverageData $data, array $linesToBeIgnored): void
 | 
						|
    {
 | 
						|
        foreach (array_keys($data->lineCoverage()) as $filename) {
 | 
						|
            if (!$this->filter->isFile($filename)) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            if (isset($linesToBeIgnored[$filename])) {
 | 
						|
                $data->removeCoverageDataForLines(
 | 
						|
                    $filename,
 | 
						|
                    $linesToBeIgnored[$filename],
 | 
						|
                );
 | 
						|
            }
 | 
						|
 | 
						|
            $data->removeCoverageDataForLines(
 | 
						|
                $filename,
 | 
						|
                $this->analyser()->ignoredLinesFor($filename),
 | 
						|
            );
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @throws UnintentionallyCoveredCodeException
 | 
						|
     */
 | 
						|
    private function addUncoveredFilesFromFilter(): void
 | 
						|
    {
 | 
						|
        $uncoveredFiles = array_diff(
 | 
						|
            $this->filter->files(),
 | 
						|
            $this->data->coveredFiles(),
 | 
						|
        );
 | 
						|
 | 
						|
        foreach ($uncoveredFiles as $uncoveredFile) {
 | 
						|
            if (is_file($uncoveredFile)) {
 | 
						|
                $this->append(
 | 
						|
                    RawCodeCoverageData::fromUncoveredFile(
 | 
						|
                        $uncoveredFile,
 | 
						|
                        $this->analyser(),
 | 
						|
                    ),
 | 
						|
                    self::UNCOVERED_FILES,
 | 
						|
                    linesToBeIgnored: $this->linesToBeIgnored,
 | 
						|
                );
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @throws ReflectionException
 | 
						|
     * @throws UnintentionallyCoveredCodeException
 | 
						|
     */
 | 
						|
    private function performUnintentionallyCoveredCodeCheck(RawCodeCoverageData $data, array $linesToBeCovered, array $linesToBeUsed): void
 | 
						|
    {
 | 
						|
        $allowedLines = $this->getAllowedLines(
 | 
						|
            $linesToBeCovered,
 | 
						|
            $linesToBeUsed,
 | 
						|
        );
 | 
						|
 | 
						|
        $unintentionallyCoveredUnits = [];
 | 
						|
 | 
						|
        foreach ($data->lineCoverage() as $file => $_data) {
 | 
						|
            foreach ($_data as $line => $flag) {
 | 
						|
                if ($flag === 1 && !isset($allowedLines[$file][$line])) {
 | 
						|
                    $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        $unintentionallyCoveredUnits = $this->processUnintentionallyCoveredUnits($unintentionallyCoveredUnits);
 | 
						|
 | 
						|
        if (!empty($unintentionallyCoveredUnits)) {
 | 
						|
            throw new UnintentionallyCoveredCodeException(
 | 
						|
                $unintentionallyCoveredUnits,
 | 
						|
            );
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed): array
 | 
						|
    {
 | 
						|
        $allowedLines = [];
 | 
						|
 | 
						|
        foreach (array_keys($linesToBeCovered) as $file) {
 | 
						|
            if (!isset($allowedLines[$file])) {
 | 
						|
                $allowedLines[$file] = [];
 | 
						|
            }
 | 
						|
 | 
						|
            $allowedLines[$file] = array_merge(
 | 
						|
                $allowedLines[$file],
 | 
						|
                $linesToBeCovered[$file],
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        foreach (array_keys($linesToBeUsed) as $file) {
 | 
						|
            if (!isset($allowedLines[$file])) {
 | 
						|
                $allowedLines[$file] = [];
 | 
						|
            }
 | 
						|
 | 
						|
            $allowedLines[$file] = array_merge(
 | 
						|
                $allowedLines[$file],
 | 
						|
                $linesToBeUsed[$file],
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        foreach (array_keys($allowedLines) as $file) {
 | 
						|
            $allowedLines[$file] = array_flip(
 | 
						|
                array_unique($allowedLines[$file]),
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        return $allowedLines;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param list<string> $unintentionallyCoveredUnits
 | 
						|
     *
 | 
						|
     * @throws ReflectionException
 | 
						|
     *
 | 
						|
     * @return list<string>
 | 
						|
     */
 | 
						|
    private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits): array
 | 
						|
    {
 | 
						|
        $unintentionallyCoveredUnits = array_unique($unintentionallyCoveredUnits);
 | 
						|
        $processed                   = [];
 | 
						|
 | 
						|
        foreach ($unintentionallyCoveredUnits as $unintentionallyCoveredUnit) {
 | 
						|
            $tmp = explode('::', $unintentionallyCoveredUnit);
 | 
						|
 | 
						|
            if (count($tmp) !== 2) {
 | 
						|
                $processed[] = $unintentionallyCoveredUnit;
 | 
						|
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            try {
 | 
						|
                $class = new ReflectionClass($tmp[0]);
 | 
						|
 | 
						|
                foreach ($this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck as $parentClass) {
 | 
						|
                    if ($class->isSubclassOf($parentClass)) {
 | 
						|
                        continue 2;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            } catch (\ReflectionException $e) {
 | 
						|
                throw new ReflectionException(
 | 
						|
                    $e->getMessage(),
 | 
						|
                    $e->getCode(),
 | 
						|
                    $e,
 | 
						|
                );
 | 
						|
            }
 | 
						|
 | 
						|
            $processed[] = $tmp[0];
 | 
						|
        }
 | 
						|
 | 
						|
        $processed = array_unique($processed);
 | 
						|
 | 
						|
        sort($processed);
 | 
						|
 | 
						|
        return $processed;
 | 
						|
    }
 | 
						|
 | 
						|
    private function analyser(): FileAnalyser
 | 
						|
    {
 | 
						|
        if ($this->analyser !== null) {
 | 
						|
            return $this->analyser;
 | 
						|
        }
 | 
						|
 | 
						|
        $this->analyser = new ParsingFileAnalyser(
 | 
						|
            $this->useAnnotationsForIgnoringCode,
 | 
						|
            $this->ignoreDeprecatedCode,
 | 
						|
        );
 | 
						|
 | 
						|
        if ($this->cachesStaticAnalysis()) {
 | 
						|
            $this->analyser = new CachingFileAnalyser(
 | 
						|
                $this->cacheDirectory,
 | 
						|
                $this->analyser,
 | 
						|
                $this->useAnnotationsForIgnoringCode,
 | 
						|
                $this->ignoreDeprecatedCode,
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        return $this->analyser;
 | 
						|
    }
 | 
						|
}
 |