This commit is contained in:
2025-02-04 12:40:43 +03:00
parent 4bcb4c60dd
commit 50343d5a87
372 changed files with 9019 additions and 6684 deletions

View File

@@ -0,0 +1,74 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [11.0.8] - 2024-12-11
### Changed
* [#1054](https://github.com/sebastianbergmann/php-code-coverage/pull/1054): Use click event for toggling "tests covering this line" popover in HTML report
## [11.0.7] - 2024-10-09
### Changed
* [#1037](https://github.com/sebastianbergmann/php-code-coverage/pull/1037): Upgrade Bootstrap to version 5.3.3 for HTML report
* [#1046](https://github.com/sebastianbergmann/php-code-coverage/pull/1046): CSS fixes for HTML report
### Deprecated
* The `SebastianBergmann\CodeCoverage\Filter::includeUncoveredFiles()`, `SebastianBergmann\CodeCoverage\Filter::excludeUncoveredFiles()`, and `SebastianBergmann\CodeCoverage\Filter::excludeFile()` methods have been deprecated
## [11.0.6] - 2024-08-22
### Changed
* Updated dependencies (so that users that install using Composer's `--prefer-lowest` CLI option also get recent versions)
## [11.0.5] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [11.0.4] - 2024-06-29
### Fixed
* [#967](https://github.com/sebastianbergmann/php-code-coverage/issues/967): Identification of executable lines for `match` expressions does not work correctly
## [11.0.3] - 2024-03-12
### Fixed
* [#1033](https://github.com/sebastianbergmann/php-code-coverage/issues/1033): `@codeCoverageIgnore` annotation does not work on `enum`
## [11.0.2] - 2024-03-09
### Changed
* [#1032](https://github.com/sebastianbergmann/php-code-coverage/pull/1032): Pad lines in code coverage report only when colors are shown
## [11.0.1] - 2024-03-02
### Changed
* Do not use implicitly nullable parameters
## [11.0.0] - 2024-02-02
### Removed
* The `SebastianBergmann\CodeCoverage\Filter::includeDirectory()`, `SebastianBergmann\CodeCoverage\Filter::excludeDirectory()`, and `SebastianBergmann\CodeCoverage\Filter::excludeFile()` methods have been removed
* This component now requires PHP-Parser 5
* This component is no longer supported on PHP 8.1
[11.0.8]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.7...11.0.8
[11.0.7]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.6...11.0.7
[11.0.6]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.5...11.0.6
[11.0.5]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.4...11.0.5
[11.0.4]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.3...11.0.4
[11.0.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.2...11.0.3
[11.0.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.1...11.0.2
[11.0.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.0...11.0.1
[11.0.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/10.1...11.0.0

View File

@@ -1,19 +0,0 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [12.0.0] - 2025-02-07
### Changed
* `CodeCoverage::stop()` and `CodeCoverage::append()` now expect arguments of type `TargetCollection` instead of `array` to configure code coverage targets
### Removed
* Methods `CodeCoverage::includeUncoveredFiles()` and `CodeCoverage::excludeUncoveredFiles()`
* Method `CodeCoverage::detectsDeadCode()`
* Optional argument `$linesToBeUsed` of `CodeCoverage::stop()` and `CodeCoverage::append()` methods
* This component is no longer supported on PHP 8.2
* This component no longer supports Xdebug versions before Xdebug 3.1
[12.0.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0...main

View File

@@ -1,6 +1,6 @@
BSD 3-Clause License
Copyright (c) 2009-2025, Sebastian Bergmann
Copyright (c) 2009-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@@ -22,29 +22,29 @@
},
"config": {
"platform": {
"php": "8.3.0"
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": ">=8.3",
"php": ">=8.2",
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^5.4.0",
"phpunit/php-file-iterator": "^6.0-dev",
"phpunit/php-text-template": "^5.0-dev",
"sebastian/complexity": "^5.0-dev",
"sebastian/environment": "^8.0-dev",
"sebastian/lines-of-code": "^4.0-dev",
"sebastian/version": "^6.0-dev",
"nikic/php-parser": "^5.3.1",
"phpunit/php-file-iterator": "^5.1.0",
"phpunit/php-text-template": "^4.0.1",
"sebastian/code-unit-reverse-lookup": "^4.0.1",
"sebastian/complexity": "^4.0.1",
"sebastian/environment": "^7.2.0",
"sebastian/lines-of-code": "^3.0.1",
"sebastian/version": "^5.0.2",
"theseer/tokenizer": "^1.2.3"
},
"require-dev": {
"phpunit/phpunit": "^12.0-dev"
"phpunit/phpunit": "^11.5.0"
},
"suggest": {
"ext-pcov": "PHP extension that provides line coverage",
@@ -62,7 +62,7 @@
},
"extra": {
"branch-alias": {
"dev-main": "12.0.x-dev"
"dev-main": "11.0.x-dev"
}
}
}

View File

@@ -14,9 +14,11 @@ 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;
@@ -28,33 +30,37 @@ use SebastianBergmann\CodeCoverage\Node\Directory;
use SebastianBergmann\CodeCoverage\StaticAnalysis\CachingFileAnalyser;
use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser;
use SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingFileAnalyser;
use SebastianBergmann\CodeCoverage\Test\Target\MapBuilder;
use SebastianBergmann\CodeCoverage\Test\Target\Mapper;
use SebastianBergmann\CodeCoverage\Test\Target\TargetCollection;
use SebastianBergmann\CodeCoverage\Test\Target\TargetCollectionValidator;
use SebastianBergmann\CodeCoverage\Test\Target\ValidationResult;
use SebastianBergmann\CodeCoverage\Test\TestSize\TestSize;
use SebastianBergmann\CodeCoverage\Test\TestStatus\TestStatus;
use SebastianBergmann\CodeUnitReverseLookup\Wizard;
/**
* Provides collection functionality for PHP code coverage information.
*
* @phpstan-type TestType array{size: string, status: string}
* @phpstan-type TargetedLines array<non-empty-string, list<positive-int>>
* @phpstan-type TestType = array{
* size: string,
* status: string,
* }
*/
final class CodeCoverage
{
private const string UNCOVERED_FILES = 'UNCOVERED_FILES';
private const UNCOVERED_FILES = 'UNCOVERED_FILES';
private readonly Driver $driver;
private readonly Filter $filter;
private ?Mapper $targetMapper = null;
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;
/**
* @var array<string,list<int>>
*/
private array $linesToBeIgnored = [];
/**
* @var array<string, TestType>
*/
@@ -73,6 +79,7 @@ final class CodeCoverage
$this->driver = $driver;
$this->filter = $filter;
$this->data = new ProcessedCodeCoverageData;
$this->wizard = new Wizard;
}
/**
@@ -121,7 +128,9 @@ final class CodeCoverage
public function getData(bool $raw = false): ProcessedCodeCoverageData
{
if (!$raw) {
$this->addUncoveredFilesFromFilter();
if ($this->includeUncoveredFiles) {
$this->addUncoveredFilesFromFilter();
}
}
return $this->data;
@@ -165,11 +174,19 @@ final class CodeCoverage
$this->cachedReport = null;
}
public function stop(bool $append = true, ?TestStatus $status = null, null|false|TargetCollection $covers = null, ?TargetCollection $uses = null): RawCodeCoverageData
/**
* @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->append($data, null, $append, $status, $covers, $uses);
$this->linesToBeIgnored = array_merge_recursive(
$this->linesToBeIgnored,
$linesToBeIgnored,
);
$this->append($data, null, $append, $status, $linesToBeCovered, $linesToBeUsed, $linesToBeIgnored);
$this->currentId = null;
$this->currentSize = null;
@@ -179,11 +196,13 @@ final class CodeCoverage
}
/**
* @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, null|false|TargetCollection $covers = null, ?TargetCollection $uses = null): void
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;
@@ -193,32 +212,24 @@ final class CodeCoverage
throw new TestIdMissingException;
}
$this->cachedReport = null;
if ($status === null) {
$status = TestStatus::unknown();
}
if ($covers === null) {
$covers = TargetCollection::fromArray([]);
}
if ($uses === null) {
$uses = TargetCollection::fromArray([]);
}
$size = $this->currentSize;
if ($size === null) {
$size = TestSize::unknown();
}
$this->cachedReport = null;
$this->applyFilter($rawData);
$this->applyExecutableLinesFilter($rawData);
if ($this->useAnnotationsForIgnoringCode) {
$this->applyIgnoredLinesFilter($rawData);
$this->applyIgnoredLinesFilter($rawData, $linesToBeIgnored);
}
$this->data->initializeUnseenData($rawData);
@@ -231,17 +242,6 @@ final class CodeCoverage
return;
}
$linesToBeCovered = false;
$linesToBeUsed = [];
if ($covers !== false) {
$linesToBeCovered = $this->targetMapper()->mapTargets($covers);
}
if ($linesToBeCovered !== false) {
$linesToBeUsed = $this->targetMapper()->mapTargets($uses);
}
$this->applyCoversAndUsesFilter(
$rawData,
$linesToBeCovered,
@@ -287,6 +287,22 @@ final class CodeCoverage
$this->checkForUnintentionallyCoveredCode = false;
}
/**
* @deprecated
*/
public function includeUncoveredFiles(): void
{
$this->includeUncoveredFiles = true;
}
/**
* @deprecated
*/
public function excludeUncoveredFiles(): void
{
$this->includeUncoveredFiles = false;
}
public function enableAnnotationsForIgnoringCode(): void
{
$this->useAnnotationsForIgnoringCode = true;
@@ -362,15 +378,12 @@ final class CodeCoverage
return $this->driver->collectsBranchAndPathCoverage();
}
public function validate(TargetCollection $targets): ValidationResult
public function detectsDeadCode(): bool
{
return (new TargetCollectionValidator)->validate($this->targetMapper(), $targets);
return $this->driver->detectsDeadCode();
}
/**
* @param false|TargetedLines $linesToBeCovered
* @param TargetedLines $linesToBeUsed
*
* @throws ReflectionException
* @throws UnintentionallyCoveredCodeException
*/
@@ -397,9 +410,11 @@ final class CodeCoverage
$rawData->removeCoverageDataForFile($fileWithNoCoverage);
}
foreach ($linesToBeCovered as $fileToBeCovered => $includedLines) {
$rawData->keepLineCoverageDataOnlyForLines($fileToBeCovered, $includedLines);
$rawData->keepFunctionCoverageDataOnlyForLines($fileToBeCovered, $includedLines);
if (is_array($linesToBeCovered)) {
foreach ($linesToBeCovered as $fileToBeCovered => $includedLines) {
$rawData->keepLineCoverageDataOnlyForLines($fileToBeCovered, $includedLines);
$rawData->keepFunctionCoverageDataOnlyForLines($fileToBeCovered, $includedLines);
}
}
}
@@ -437,13 +452,23 @@ final class CodeCoverage
}
}
private function applyIgnoredLinesFilter(RawCodeCoverageData $data): void
/**
* @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),
@@ -469,15 +494,13 @@ final class CodeCoverage
$this->analyser(),
),
self::UNCOVERED_FILES,
linesToBeIgnored: $this->linesToBeIgnored,
);
}
}
}
/**
* @param TargetedLines $linesToBeCovered
* @param TargetedLines $linesToBeUsed
*
* @throws ReflectionException
* @throws UnintentionallyCoveredCodeException
*/
@@ -493,7 +516,7 @@ final class CodeCoverage
foreach ($data->lineCoverage() as $file => $_data) {
foreach ($_data as $line => $flag) {
if ($flag === 1 && !isset($allowedLines[$file][$line])) {
$unintentionallyCoveredUnits[] = $this->targetMapper->lookup($file, $line);
$unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line);
}
}
}
@@ -507,12 +530,6 @@ final class CodeCoverage
}
}
/**
* @param TargetedLines $linesToBeCovered
* @param TargetedLines $linesToBeUsed
*
* @return TargetedLines
*/
private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed): array
{
$allowedLines = [];
@@ -595,19 +612,6 @@ final class CodeCoverage
return $processed;
}
private function targetMapper(): Mapper
{
if ($this->targetMapper !== null) {
return $this->targetMapper;
}
$this->targetMapper = new Mapper(
(new MapBuilder)->build($this->filter, $this->analyser()),
);
return $this->targetMapper;
}
private function analyser(): FileAnalyser
{
if ($this->analyser !== null) {

View File

@@ -17,31 +17,13 @@ use function count;
use function is_array;
use function ksort;
use SebastianBergmann\CodeCoverage\Driver\Driver;
use SebastianBergmann\CodeCoverage\Driver\XdebugDriver;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-import-type XdebugFunctionCoverageType from XdebugDriver
* @phpstan-import-type XdebugFunctionCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver
*
* @phpstan-type TestIdType string
* @phpstan-type FunctionCoverageDataType array{
* branches: array<int, array{
* op_start: int,
* op_end: int,
* line_start: int,
* line_end: int,
* hit: list<TestIdType>,
* out: array<int, int>,
* out_hit: array<int, int>,
* }>,
* paths: array<int, array{
* path: array<int, int>,
* hit: list<TestIdType>,
* }>,
* hit: list<TestIdType>
* }
* @phpstan-type FunctionCoverageType array<string, array<string, FunctionCoverageDataType>>
* @phpstan-type TestIdType = string
*/
final class ProcessedCodeCoverageData
{
@@ -58,7 +40,22 @@ final class ProcessedCodeCoverageData
* Maintains base format of raw data (@see https://xdebug.org/docs/code_coverage), but each 'hit' entry is an array
* of testcase ids.
*
* @var FunctionCoverageType
* @var array<string, array<string, array{
* branches: array<int, array{
* op_start: int,
* op_end: int,
* line_start: int,
* line_end: int,
* hit: list<TestIdType>,
* out: array<int, int>,
* out_hit: array<int, int>,
* }>,
* paths: array<int, array{
* path: array<int, int>,
* hit: list<TestIdType>,
* }>,
* hit: list<TestIdType>
* }>>
*/
private array $functionCoverage = [];
@@ -219,8 +216,6 @@ final class ProcessedCodeCoverageData
* 4 = the line has been tested
*
* During a merge, a higher number is better.
*
* @return 1|2|3|4
*/
private function priorityForLine(array $data, int $line): int
{
@@ -242,7 +237,7 @@ final class ProcessedCodeCoverageData
/**
* For a function we have never seen before, copy all data over and simply init the 'hit' array.
*
* @param FunctionCoverageDataType|XdebugFunctionCoverageType $functionData
* @param XdebugFunctionCoverageType $functionData
*/
private function initPreviouslyUnseenFunction(string $file, string $functionName, array $functionData): void
{
@@ -262,7 +257,7 @@ final class ProcessedCodeCoverageData
* Techniques such as mocking and where the contents of a file are different vary during tests (e.g. compiling
* containers) mean that the functions inside a file cannot be relied upon to be static.
*
* @param FunctionCoverageDataType|XdebugFunctionCoverageType $functionData
* @param XdebugFunctionCoverageType $functionData
*/
private function initPreviouslySeenFunction(string $file, string $functionName, array $functionData): void
{

View File

@@ -14,7 +14,6 @@ use function array_diff_key;
use function array_flip;
use function array_intersect;
use function array_intersect_key;
use function array_map;
use function count;
use function explode;
use function file_get_contents;
@@ -26,15 +25,14 @@ use function str_ends_with;
use function str_starts_with;
use function trim;
use SebastianBergmann\CodeCoverage\Driver\Driver;
use SebastianBergmann\CodeCoverage\Driver\XdebugDriver;
use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-import-type XdebugFunctionsCoverageType from XdebugDriver
* @phpstan-import-type XdebugCodeCoverageWithoutPathCoverageType from XdebugDriver
* @phpstan-import-type XdebugCodeCoverageWithPathCoverageType from XdebugDriver
* @phpstan-import-type XdebugFunctionsCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver
* @phpstan-import-type XdebugCodeCoverageWithoutPathCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver
* @phpstan-import-type XdebugCodeCoverageWithPathCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver
*/
final class RawCodeCoverageData
{
@@ -88,10 +86,11 @@ final class RawCodeCoverageData
public static function fromUncoveredFile(string $filename, FileAnalyser $analyser): self
{
$lineCoverage = array_map(
static fn (): int => Driver::LINE_NOT_EXECUTED,
$analyser->executableLinesIn($filename),
);
$lineCoverage = [];
foreach ($analyser->executableLinesIn($filename) as $line => $branch) {
$lineCoverage[$line] = Driver::LINE_NOT_EXECUTED;
}
return new self([$filename => $lineCoverage], []);
}
@@ -261,9 +260,6 @@ final class RawCodeCoverageData
}
}
/**
* @return array<int>
*/
private function getEmptyLinesForFile(string $filename): array
{
if (!isset(self::$emptyLineCache[$filename])) {

View File

@@ -12,6 +12,7 @@ namespace SebastianBergmann\CodeCoverage\Driver;
use function sprintf;
use SebastianBergmann\CodeCoverage\BranchAndPathCoverageNotSupportedException;
use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData;
use SebastianBergmann\CodeCoverage\DeadCodeDetectionNotSupportedException;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
@@ -23,36 +24,37 @@ abstract class Driver
*
* @see http://xdebug.org/docs/code_coverage
*/
public const int LINE_NOT_EXECUTABLE = -2;
public const LINE_NOT_EXECUTABLE = -2;
/**
* @var int
*
* @see http://xdebug.org/docs/code_coverage
*/
public const int LINE_NOT_EXECUTED = -1;
public const LINE_NOT_EXECUTED = -1;
/**
* @var int
*
* @see http://xdebug.org/docs/code_coverage
*/
public const int LINE_EXECUTED = 1;
public const LINE_EXECUTED = 1;
/**
* @var int
*
* @see http://xdebug.org/docs/code_coverage
*/
public const int BRANCH_NOT_HIT = 0;
public const BRANCH_NOT_HIT = 0;
/**
* @var int
*
* @see http://xdebug.org/docs/code_coverage
*/
public const int BRANCH_HIT = 1;
public const BRANCH_HIT = 1;
private bool $collectBranchAndPathCoverage = false;
private bool $detectDeadCode = false;
public function canCollectBranchAndPathCoverage(): bool
{
@@ -86,6 +88,38 @@ abstract class Driver
$this->collectBranchAndPathCoverage = false;
}
public function canDetectDeadCode(): bool
{
return false;
}
public function detectsDeadCode(): bool
{
return $this->detectDeadCode;
}
/**
* @throws DeadCodeDetectionNotSupportedException
*/
public function enableDeadCodeDetection(): void
{
if (!$this->canDetectDeadCode()) {
throw new DeadCodeDetectionNotSupportedException(
sprintf(
'%s does not support dead code detection',
$this->nameAndVersion(),
),
);
}
$this->detectDeadCode = true;
}
public function disableDeadCodeDetection(): void
{
$this->detectDeadCode = false;
}
abstract public function nameAndVersion(): string;
abstract public function start(): void;

View File

@@ -38,9 +38,6 @@ final class PcovDriver extends Driver
$this->filter = $filter;
}
/**
* @codeCoverageIgnore
*/
public function start(): void
{
start();
@@ -50,7 +47,6 @@ final class PcovDriver extends Driver
{
stop();
// @codeCoverageIgnoreStart
$filesToCollectCoverageFor = waiting();
$collected = [];
@@ -65,7 +61,6 @@ final class PcovDriver extends Driver
}
return RawCodeCoverageData::fromXdebugWithoutPathCoverage($collected);
// @codeCoverageIgnoreEnd
}
public function nameAndVersion(): string

View File

@@ -21,7 +21,6 @@ final class Selector
* @throws PcovNotAvailableException
* @throws XdebugNotAvailableException
* @throws XdebugNotEnabledException
* @throws XdebugVersionNotSupportedException
*/
public function forLineCoverage(Filter $filter): Driver
{
@@ -32,7 +31,11 @@ final class Selector
}
if ($runtime->hasXdebug()) {
return new XdebugDriver($filter);
$driver = new XdebugDriver($filter);
$driver->enableDeadCodeDetection();
return $driver;
}
throw new NoCodeCoverageDriverAvailableException;
@@ -42,13 +45,13 @@ final class Selector
* @throws NoCodeCoverageDriverWithPathCoverageSupportAvailableException
* @throws XdebugNotAvailableException
* @throws XdebugNotEnabledException
* @throws XdebugVersionNotSupportedException
*/
public function forLineAndPathCoverage(Filter $filter): Driver
{
if ((new Runtime)->hasXdebug()) {
$driver = new XdebugDriver($filter);
$driver->enableDeadCodeDetection();
$driver->enableBranchAndPathCoverage();
return $driver;

View File

@@ -14,8 +14,11 @@ use const XDEBUG_CC_DEAD_CODE;
use const XDEBUG_CC_UNUSED;
use const XDEBUG_FILTER_CODE_COVERAGE;
use const XDEBUG_PATH_INCLUDE;
use function explode;
use function extension_loaded;
use function getenv;
use function in_array;
use function ini_get;
use function phpversion;
use function version_compare;
use function xdebug_get_code_coverage;
@@ -31,8 +34,8 @@ use SebastianBergmann\CodeCoverage\Filter;
*
* @see https://xdebug.org/docs/code_coverage#xdebug_get_code_coverage
*
* @phpstan-type XdebugLinesCoverageType array<int, int>
* @phpstan-type XdebugBranchCoverageType array{
* @phpstan-type XdebugLinesCoverageType = array<int, int>
* @phpstan-type XdebugBranchCoverageType = array{
* op_start: int,
* op_end: int,
* line_start: int,
@@ -41,32 +44,32 @@ use SebastianBergmann\CodeCoverage\Filter;
* out: array<int, int>,
* out_hit: array<int, int>,
* }
* @phpstan-type XdebugPathCoverageType array{
* @phpstan-type XdebugPathCoverageType = array{
* path: array<int, int>,
* hit: int,
* }
* @phpstan-type XdebugFunctionCoverageType array{
* @phpstan-type XdebugFunctionCoverageType = array{
* branches: array<int, XdebugBranchCoverageType>,
* paths: array<int, XdebugPathCoverageType>,
* }
* @phpstan-type XdebugFunctionsCoverageType array<string, XdebugFunctionCoverageType>
* @phpstan-type XdebugPathAndBranchesCoverageType array{
* @phpstan-type XdebugFunctionsCoverageType = array<string, XdebugFunctionCoverageType>
* @phpstan-type XdebugPathAndBranchesCoverageType = array{
* lines: XdebugLinesCoverageType,
* functions: XdebugFunctionsCoverageType,
* }
* @phpstan-type XdebugCodeCoverageWithoutPathCoverageType array<string, XdebugLinesCoverageType>
* @phpstan-type XdebugCodeCoverageWithPathCoverageType array<string, XdebugPathAndBranchesCoverageType>
* @phpstan-type XdebugCodeCoverageWithoutPathCoverageType = array<string, XdebugLinesCoverageType>
* @phpstan-type XdebugCodeCoverageWithPathCoverageType = array<string, XdebugPathAndBranchesCoverageType>
*/
final class XdebugDriver extends Driver
{
/**
* @throws XdebugNotAvailableException
* @throws XdebugNotEnabledException
* @throws XdebugVersionNotSupportedException
*/
public function __construct(Filter $filter)
{
$this->ensureXdebugIsAvailable();
$this->ensureXdebugCodeCoverageFeatureIsEnabled();
if (!$filter->isEmpty()) {
xdebug_set_filter(
@@ -82,9 +85,18 @@ final class XdebugDriver extends Driver
return true;
}
public function canDetectDeadCode(): bool
{
return true;
}
public function start(): void
{
$flags = XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE;
$flags = XDEBUG_CC_UNUSED;
if ($this->detectsDeadCode() || $this->collectsBranchAndPathCoverage()) {
$flags |= XDEBUG_CC_DEAD_CODE;
}
if ($this->collectsBranchAndPathCoverage()) {
$flags |= XDEBUG_CC_BRANCH_CHECK;
@@ -115,20 +127,35 @@ final class XdebugDriver extends Driver
/**
* @throws XdebugNotAvailableException
* @throws XdebugNotEnabledException
* @throws XdebugVersionNotSupportedException
*/
private function ensureXdebugIsAvailable(): void
{
if (!extension_loaded('xdebug')) {
throw new XdebugNotAvailableException;
}
}
if (!version_compare(phpversion('xdebug'), '3.1', '>=')) {
throw new XdebugVersionNotSupportedException(phpversion('xdebug'));
/**
* @throws XdebugNotEnabledException
*/
private function ensureXdebugCodeCoverageFeatureIsEnabled(): void
{
if (version_compare(phpversion('xdebug'), '3.1', '>=')) {
if (!in_array('coverage', xdebug_info('mode'), true)) {
throw new XdebugNotEnabledException;
}
return;
}
if (!in_array('coverage', xdebug_info('mode'), true)) {
$mode = getenv('XDEBUG_MODE');
if ($mode === false || $mode === '') {
$mode = ini_get('xdebug.mode');
}
if ($mode === false ||
!in_array('coverage', explode(',', $mode), true)) {
throw new XdebugNotEnabledException;
}
}

View File

@@ -0,0 +1,16 @@
<?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 RuntimeException;
final class DeadCodeDetectionNotSupportedException extends RuntimeException implements Exception
{
}

View File

@@ -1,27 +0,0 @@
<?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\Test\Target;
use function sprintf;
use RuntimeException;
use SebastianBergmann\CodeCoverage\Exception;
final class InvalidCodeCoverageTargetException extends RuntimeException implements Exception
{
public function __construct(Target $target)
{
parent::__construct(
sprintf(
'%s is not a valid target for code coverage',
$target->description(),
),
);
}
}

View File

@@ -1,30 +0,0 @@
<?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\Driver;
use function sprintf;
use RuntimeException;
use SebastianBergmann\CodeCoverage\Exception;
final class XdebugVersionNotSupportedException extends RuntimeException implements Exception
{
/**
* @param non-empty-string $version
*/
public function __construct(string $version)
{
parent::__construct(
sprintf(
'Version %s of the Xdebug extension is not supported',
$version,
),
);
}
}

View File

@@ -15,24 +15,20 @@ use function str_ends_with;
use function str_replace;
use function substr;
use Countable;
use SebastianBergmann\CodeCoverage\StaticAnalysis\LinesOfCode;
use SebastianBergmann\CodeCoverage\Util\Percentage;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-import-type ProcessedFunctionType from File
* @phpstan-import-type ProcessedClassType from File
* @phpstan-import-type ProcessedTraitType from File
* @phpstan-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
* @phpstan-import-type ProcessedFunctionType from \SebastianBergmann\CodeCoverage\Node\File
* @phpstan-import-type ProcessedClassType from \SebastianBergmann\CodeCoverage\Node\File
* @phpstan-import-type ProcessedTraitType from \SebastianBergmann\CodeCoverage\Node\File
*/
abstract class AbstractNode implements Countable
{
private readonly string $name;
private string $pathAsString;
/**
* @var non-empty-list<self>
*/
private array $pathAsArray;
private readonly ?AbstractNode $parent;
private string $id;
@@ -65,9 +61,6 @@ abstract class AbstractNode implements Countable
return $this->pathAsString;
}
/**
* @return non-empty-list<self>
*/
public function pathAsArray(): array
{
return $this->pathAsArray;
@@ -193,7 +186,10 @@ abstract class AbstractNode implements Countable
*/
abstract public function functions(): array;
abstract public function linesOfCode(): LinesOfCode;
/**
* @return LinesOfCodeType
*/
abstract public function linesOfCode(): array;
abstract public function numberOfExecutableLines(): int;

View File

@@ -28,11 +28,11 @@ use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-import-type TestType from CodeCoverage
* @phpstan-import-type TestType from \SebastianBergmann\CodeCoverage\CodeCoverage
*/
final readonly class Builder
final class Builder
{
private FileAnalyser $analyser;
private readonly FileAnalyser $analyser;
public function __construct(FileAnalyser $analyser)
{
@@ -223,7 +223,6 @@ final readonly class Builder
$paths[$i] = substr($paths[$i], 7);
$paths[$i] = str_replace('/', DIRECTORY_SEPARATOR, $paths[$i]);
}
$paths[$i] = explode(DIRECTORY_SEPARATOR, $paths[$i]);
if (empty($paths[$i][0])) {

View File

@@ -14,10 +14,10 @@ use function sprintf;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class CrapIndex
final class CrapIndex
{
private int $cyclomaticComplexity;
private float $codeCoverage;
private readonly int $cyclomaticComplexity;
private readonly float $codeCoverage;
public function __construct(int $cyclomaticComplexity, float $codeCoverage)
{

View File

@@ -14,16 +14,11 @@ use function assert;
use function count;
use IteratorAggregate;
use RecursiveIteratorIterator;
use SebastianBergmann\CodeCoverage\StaticAnalysis\LinesOfCode;
/**
* @template-implements IteratorAggregate<int, AbstractNode>
*
* @phpstan-import-type ProcessedFunctionType from File
* @phpstan-import-type ProcessedClassType from File
* @phpstan-import-type ProcessedTraitType from File
*
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
*/
final class Directory extends AbstractNode implements IteratorAggregate
{
@@ -40,23 +35,15 @@ final class Directory extends AbstractNode implements IteratorAggregate
/**
* @var list<File>
*/
private array $files = [];
private array $files = [];
private ?array $classes = null;
private ?array $traits = null;
private ?array $functions = null;
/**
* @var ?array<string, ProcessedClassType>
* @var null|LinesOfCodeType
*/
private ?array $classes = null;
/**
* @var ?array<string, ProcessedTraitType>
*/
private ?array $traits = null;
/**
* @var ?array<string, ProcessedFunctionType>
*/
private ?array $functions = null;
private ?LinesOfCode $linesOfCode = null;
private ?array $linesOfCode = null;
private int $numFiles = -1;
private int $numExecutableLines = -1;
private int $numExecutedLines = -1;
@@ -86,9 +73,6 @@ final class Directory extends AbstractNode implements IteratorAggregate
return $this->numFiles;
}
/**
* @return RecursiveIteratorIterator<Iterator<AbstractNode>>
*/
public function getIterator(): RecursiveIteratorIterator
{
return new RecursiveIteratorIterator(
@@ -118,33 +102,21 @@ final class Directory extends AbstractNode implements IteratorAggregate
$this->numExecutedLines = -1;
}
/**
* @return list<Directory>
*/
public function directories(): array
{
return $this->directories;
}
/**
* @return list<File>
*/
public function files(): array
{
return $this->files;
}
/**
* @return list<Directory|File>
*/
public function children(): array
{
return $this->children;
}
/**
* @return array<string, ProcessedClassType>
*/
public function classes(): array
{
if ($this->classes === null) {
@@ -161,9 +133,6 @@ final class Directory extends AbstractNode implements IteratorAggregate
return $this->classes;
}
/**
* @return array<string, ProcessedTraitType>
*/
public function traits(): array
{
if ($this->traits === null) {
@@ -180,9 +149,6 @@ final class Directory extends AbstractNode implements IteratorAggregate
return $this->traits;
}
/**
* @return array<string, ProcessedFunctionType>
*/
public function functions(): array
{
if ($this->functions === null) {
@@ -199,22 +165,25 @@ final class Directory extends AbstractNode implements IteratorAggregate
return $this->functions;
}
public function linesOfCode(): LinesOfCode
/**
* @return LinesOfCodeType
*/
public function linesOfCode(): array
{
if ($this->linesOfCode === null) {
$linesOfCode = 0;
$commentLinesOfCode = 0;
$nonCommentLinesOfCode = 0;
$this->linesOfCode = [
'linesOfCode' => 0,
'commentLinesOfCode' => 0,
'nonCommentLinesOfCode' => 0,
];
foreach ($this->children as $child) {
$childLinesOfCode = $child->linesOfCode();
$linesOfCode += $childLinesOfCode->linesOfCode();
$commentLinesOfCode += $childLinesOfCode->commentLinesOfCode();
$nonCommentLinesOfCode += $childLinesOfCode->nonCommentLinesOfCode();
$this->linesOfCode['linesOfCode'] += $childLinesOfCode['linesOfCode'];
$this->linesOfCode['commentLinesOfCode'] += $childLinesOfCode['commentLinesOfCode'];
$this->linesOfCode['nonCommentLinesOfCode'] += $childLinesOfCode['nonCommentLinesOfCode'];
}
$this->linesOfCode = new LinesOfCode($linesOfCode, $commentLinesOfCode, $nonCommentLinesOfCode);
}
return $this->linesOfCode;

View File

@@ -12,21 +12,18 @@ namespace SebastianBergmann\CodeCoverage\Node;
use function array_filter;
use function count;
use function range;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\StaticAnalysis\Class_;
use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser;
use SebastianBergmann\CodeCoverage\StaticAnalysis\Function_;
use SebastianBergmann\CodeCoverage\StaticAnalysis\LinesOfCode;
use SebastianBergmann\CodeCoverage\StaticAnalysis\Method;
use SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-import-type TestType from CodeCoverage
* @phpstan-import-type LinesType from FileAnalyser
* @phpstan-import-type CodeUnitFunctionType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitMethodType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitClassType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitTraitType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
* @phpstan-import-type LinesType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
*
* @phpstan-type ProcessedFunctionType array{
* @phpstan-type ProcessedFunctionType = array{
* functionName: string,
* namespace: string,
* signature: string,
@@ -43,7 +40,7 @@ use SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_;
* crap: int|string,
* link: string
* }
* @phpstan-type ProcessedMethodType array{
* @phpstan-type ProcessedMethodType = array{
* methodName: string,
* visibility: string,
* signature: string,
@@ -60,7 +57,7 @@ use SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_;
* crap: int|string,
* link: string
* }
* @phpstan-type ProcessedClassType array{
* @phpstan-type ProcessedClassType = array{
* className: string,
* namespace: string,
* methods: array<string, ProcessedMethodType>,
@@ -76,7 +73,7 @@ use SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_;
* crap: int|string,
* link: string
* }
* @phpstan-type ProcessedTraitType array{
* @phpstan-type ProcessedTraitType = array{
* traitName: string,
* namespace: string,
* methods: array<string, ProcessedMethodType>,
@@ -100,10 +97,6 @@ final class File extends AbstractNode
*/
private array $lineCoverageData;
private array $functionCoverageData;
/**
* @var array<string, TestType>
*/
private readonly array $testData;
private int $numExecutableLines = 0;
private int $numExecutedLines = 0;
@@ -126,7 +119,11 @@ final class File extends AbstractNode
* @var array<string, ProcessedFunctionType>
*/
private array $functions = [];
private readonly LinesOfCode $linesOfCode;
/**
* @var LinesOfCodeType
*/
private readonly array $linesOfCode;
private ?int $numClasses = null;
private int $numTestedClasses = 0;
private ?int $numTraits = null;
@@ -136,18 +133,18 @@ final class File extends AbstractNode
private ?int $numTestedFunctions = null;
/**
* @var array<int, array|array{0: Class_, 1: string}|array{0: Function_}|array{0: Trait_, 1: string}>
* @var array<int, array|array{0: CodeUnitClassType, 1: string}|array{0: CodeUnitFunctionType}|array{0: CodeUnitTraitType, 1: string}>
*/
private array $codeUnitsByLine = [];
/**
* @param array<int, ?list<non-empty-string>> $lineCoverageData
* @param array<string, TestType> $testData
* @param array<string, Class_> $classes
* @param array<string, Trait_> $traits
* @param array<string, Function_> $functions
* @param array<string, CodeUnitClassType> $classes
* @param array<string, CodeUnitTraitType> $traits
* @param array<string, CodeUnitFunctionType> $functions
* @param LinesOfCodeType $linesOfCode
*/
public function __construct(string $name, AbstractNode $parent, array $lineCoverageData, array $functionCoverageData, array $testData, array $classes, array $traits, array $functions, LinesOfCode $linesOfCode)
public function __construct(string $name, AbstractNode $parent, array $lineCoverageData, array $functionCoverageData, array $testData, array $classes, array $traits, array $functions, array $linesOfCode)
{
parent::__construct($name, $parent);
@@ -177,9 +174,6 @@ final class File extends AbstractNode
return $this->functionCoverageData;
}
/**
* @return array<string, TestType>
*/
public function testData(): array
{
return $this->testData;
@@ -209,7 +203,7 @@ final class File extends AbstractNode
return $this->functions;
}
public function linesOfCode(): LinesOfCode
public function linesOfCode(): array
{
return $this->linesOfCode;
}
@@ -366,13 +360,13 @@ final class File extends AbstractNode
}
/**
* @param array<string, Class_> $classes
* @param array<string, Trait_> $traits
* @param array<string, Function_> $functions
* @param array<string, CodeUnitClassType> $classes
* @param array<string, CodeUnitTraitType> $traits
* @param array<string, CodeUnitFunctionType> $functions
*/
private function calculateStatistics(array $classes, array $traits, array $functions): void
{
foreach (range(1, $this->linesOfCode->linesOfCode()) as $lineNumber) {
foreach (range(1, $this->linesOfCode['linesOfCode']) as $lineNumber) {
$this->codeUnitsByLine[$lineNumber] = [];
}
@@ -380,7 +374,7 @@ final class File extends AbstractNode
$this->processTraits($traits);
$this->processFunctions($functions);
foreach (range(1, $this->linesOfCode->linesOfCode()) as $lineNumber) {
foreach (range(1, $this->linesOfCode['linesOfCode']) as $lineNumber) {
if (isset($this->lineCoverageData[$lineNumber])) {
foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) {
$codeUnit['executableLines']++;
@@ -473,7 +467,7 @@ final class File extends AbstractNode
}
/**
* @param array<string, Class_> $classes
* @param array<string, CodeUnitClassType> $classes
*/
private function processClasses(array $classes): void
{
@@ -482,9 +476,9 @@ final class File extends AbstractNode
foreach ($classes as $className => $class) {
$this->classes[$className] = [
'className' => $className,
'namespace' => $class->namespace(),
'namespace' => $class['namespace'],
'methods' => [],
'startLine' => $class->startLine(),
'startLine' => $class['startLine'],
'executableLines' => 0,
'executedLines' => 0,
'executableBranches' => 0,
@@ -494,11 +488,11 @@ final class File extends AbstractNode
'ccn' => 0,
'coverage' => 0,
'crap' => 0,
'link' => $link . $class->startLine(),
'link' => $link . $class['startLine'],
];
foreach ($class->methods() as $methodName => $method) {
$methodData = $this->newMethod($className, $method, $link);
foreach ($class['methods'] as $methodName => $method) {
$methodData = $this->newMethod($className, $methodName, $method, $link);
$this->classes[$className]['methods'][$methodName] = $methodData;
$this->classes[$className]['executableBranches'] += $methodData['executableBranches'];
@@ -511,7 +505,7 @@ final class File extends AbstractNode
$this->numExecutablePaths += $methodData['executablePaths'];
$this->numExecutedPaths += $methodData['executedPaths'];
foreach (range($method->startLine(), $method->endLine()) as $lineNumber) {
foreach (range($method['startLine'], $method['endLine']) as $lineNumber) {
$this->codeUnitsByLine[$lineNumber] = [
&$this->classes[$className],
&$this->classes[$className]['methods'][$methodName],
@@ -522,7 +516,7 @@ final class File extends AbstractNode
}
/**
* @param array<string, Trait_> $traits
* @param array<string, CodeUnitTraitType> $traits
*/
private function processTraits(array $traits): void
{
@@ -531,9 +525,9 @@ final class File extends AbstractNode
foreach ($traits as $traitName => $trait) {
$this->traits[$traitName] = [
'traitName' => $traitName,
'namespace' => $trait->namespace(),
'namespace' => $trait['namespace'],
'methods' => [],
'startLine' => $trait->startLine(),
'startLine' => $trait['startLine'],
'executableLines' => 0,
'executedLines' => 0,
'executableBranches' => 0,
@@ -543,11 +537,11 @@ final class File extends AbstractNode
'ccn' => 0,
'coverage' => 0,
'crap' => 0,
'link' => $link . $trait->startLine(),
'link' => $link . $trait['startLine'],
];
foreach ($trait->methods() as $methodName => $method) {
$methodData = $this->newMethod($traitName, $method, $link);
foreach ($trait['methods'] as $methodName => $method) {
$methodData = $this->newMethod($traitName, $methodName, $method, $link);
$this->traits[$traitName]['methods'][$methodName] = $methodData;
$this->traits[$traitName]['executableBranches'] += $methodData['executableBranches'];
@@ -560,7 +554,7 @@ final class File extends AbstractNode
$this->numExecutablePaths += $methodData['executablePaths'];
$this->numExecutedPaths += $methodData['executedPaths'];
foreach (range($method->startLine(), $method->endLine()) as $lineNumber) {
foreach (range($method['startLine'], $method['endLine']) as $lineNumber) {
$this->codeUnitsByLine[$lineNumber] = [
&$this->traits[$traitName],
&$this->traits[$traitName]['methods'][$methodName],
@@ -571,7 +565,7 @@ final class File extends AbstractNode
}
/**
* @param array<string, Function_> $functions
* @param array<string, CodeUnitFunctionType> $functions
*/
private function processFunctions(array $functions): void
{
@@ -580,23 +574,23 @@ final class File extends AbstractNode
foreach ($functions as $functionName => $function) {
$this->functions[$functionName] = [
'functionName' => $functionName,
'namespace' => $function->namespace(),
'signature' => $function->signature(),
'startLine' => $function->startLine(),
'endLine' => $function->endLine(),
'namespace' => $function['namespace'],
'signature' => $function['signature'],
'startLine' => $function['startLine'],
'endLine' => $function['endLine'],
'executableLines' => 0,
'executedLines' => 0,
'executableBranches' => 0,
'executedBranches' => 0,
'executablePaths' => 0,
'executedPaths' => 0,
'ccn' => $function->cyclomaticComplexity(),
'ccn' => $function['ccn'],
'coverage' => 0,
'crap' => 0,
'link' => $link . $function->startLine(),
'link' => $link . $function['startLine'],
];
foreach (range($function->startLine(), $function->endLine()) as $lineNumber) {
foreach (range($function['startLine'], $function['endLine']) as $lineNumber) {
$this->codeUnitsByLine[$lineNumber] = [&$this->functions[$functionName]];
}
@@ -640,29 +634,31 @@ final class File extends AbstractNode
}
/**
* @param CodeUnitMethodType $method
*
* @return ProcessedMethodType
*/
private function newMethod(string $className, Method $method, string $link): array
private function newMethod(string $className, string $methodName, array $method, string $link): array
{
$methodData = [
'methodName' => $method->name(),
'visibility' => $method->visibility()->value,
'signature' => $method->signature(),
'startLine' => $method->startLine(),
'endLine' => $method->endLine(),
'methodName' => $methodName,
'visibility' => $method['visibility'],
'signature' => $method['signature'],
'startLine' => $method['startLine'],
'endLine' => $method['endLine'],
'executableLines' => 0,
'executedLines' => 0,
'executableBranches' => 0,
'executedBranches' => 0,
'executablePaths' => 0,
'executedPaths' => 0,
'ccn' => $method->cyclomaticComplexity(),
'ccn' => $method['ccn'],
'coverage' => 0,
'crap' => 0,
'link' => $link . $method->startLine(),
'link' => $link . $method['startLine'],
];
$key = $className . '->' . $method->name();
$key = $className . '->' . $methodName;
if (isset($this->functionCoverageData[$key]['branches'])) {
$methodData['executableBranches'] = count(

View File

@@ -9,13 +9,10 @@
*/
namespace SebastianBergmann\CodeCoverage\Node;
use function assert;
use function count;
use RecursiveIterator;
/**
* @template-implements RecursiveIterator<int, AbstractNode>
*
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class Iterator implements RecursiveIterator
@@ -32,38 +29,57 @@ final class Iterator implements RecursiveIterator
$this->nodes = $node->children();
}
/**
* Rewinds the Iterator to the first element.
*/
public function rewind(): void
{
$this->position = 0;
}
/**
* Checks if there is a current element after calls to rewind() or next().
*/
public function valid(): bool
{
return $this->position < count($this->nodes);
}
/**
* Returns the key of the current element.
*/
public function key(): int
{
return $this->position;
}
/**
* Returns the current element.
*/
public function current(): ?AbstractNode
{
return $this->valid() ? $this->nodes[$this->position] : null;
}
/**
* Moves forward to next element.
*/
public function next(): void
{
$this->position++;
}
/**
* Returns the sub iterator for the current element.
*/
public function getChildren(): self
{
assert($this->nodes[$this->position] instanceof Directory);
return new self($this->nodes[$this->position]);
}
/**
* Checks whether the current element has children.
*/
public function hasChildren(): bool
{
return $this->nodes[$this->position] instanceof Directory;

View File

@@ -168,8 +168,8 @@ final class Clover
$linesOfCode = $item->linesOfCode();
$xmlMetrics = $xmlDocument->createElement('metrics');
$xmlMetrics->setAttribute('loc', (string) $linesOfCode->linesOfCode());
$xmlMetrics->setAttribute('ncloc', (string) $linesOfCode->nonCommentLinesOfCode());
$xmlMetrics->setAttribute('loc', (string) $linesOfCode['linesOfCode']);
$xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['nonCommentLinesOfCode']);
$xmlMetrics->setAttribute('classes', (string) $item->numberOfClassesAndTraits());
$xmlMetrics->setAttribute('methods', (string) $item->numberOfMethods());
$xmlMetrics->setAttribute('coveredmethods', (string) $item->numberOfTestedMethods());
@@ -201,8 +201,8 @@ final class Clover
$xmlMetrics = $xmlDocument->createElement('metrics');
$xmlMetrics->setAttribute('files', (string) count($report));
$xmlMetrics->setAttribute('loc', (string) $linesOfCode->linesOfCode());
$xmlMetrics->setAttribute('ncloc', (string) $linesOfCode->nonCommentLinesOfCode());
$xmlMetrics->setAttribute('loc', (string) $linesOfCode['linesOfCode']);
$xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['nonCommentLinesOfCode']);
$xmlMetrics->setAttribute('classes', (string) $report->numberOfClassesAndTraits());
$xmlMetrics->setAttribute('methods', (string) $report->numberOfMethods());
$xmlMetrics->setAttribute('coveredmethods', (string) $report->numberOfTestedMethods());

View File

@@ -153,7 +153,7 @@ final class Cobertura
$linesValid = $method['executableLines'];
$linesCovered = $method['executedLines'];
$lineRate = $linesCovered / $linesValid;
$lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
$branchesValid = $method['executableBranches'];
$branchesCovered = $method['executedBranches'];
@@ -228,7 +228,7 @@ final class Cobertura
$linesValid = $function['executableLines'];
$linesCovered = $function['executedLines'];
$lineRate = $linesCovered / $linesValid;
$lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
$functionsLinesValid += $linesValid;
$functionsLinesCovered += $linesCovered;

View File

@@ -22,9 +22,9 @@ use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
final readonly class Crap4j
final class Crap4j
{
private int $threshold;
private readonly int $threshold;
public function __construct(int $threshold = 30)
{

View File

@@ -12,13 +12,13 @@ namespace SebastianBergmann\CodeCoverage\Report\Html;
/**
* @immutable
*/
final readonly class Colors
final class Colors
{
private string $successLow;
private string $successMedium;
private string $successHigh;
private string $warning;
private string $danger;
private readonly string $successLow;
private readonly string $successMedium;
private readonly string $successHigh;
private readonly string $warning;
private readonly string $danger;
public static function default(): self
{

View File

@@ -15,9 +15,9 @@ use SebastianBergmann\CodeCoverage\InvalidArgumentException;
/**
* @immutable
*/
final readonly class CustomCssFile
final class CustomCssFile
{
private string $path;
private readonly string $path;
public static function default(): self
{

View File

@@ -22,13 +22,13 @@ use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\Template\Exception;
use SebastianBergmann\Template\Template;
final readonly class Facade
final class Facade
{
private string $templatePath;
private string $generator;
private Colors $colors;
private Thresholds $thresholds;
private CustomCssFile $customCssFile;
private readonly string $templatePath;
private readonly string $generator;
private readonly Colors $colors;
private readonly Thresholds $thresholds;
private readonly CustomCssFile $customCssFile;
public function __construct(string $generator = '', ?Colors $colors = null, ?Thresholds $thresholds = null, ?CustomCssFile $customCssFile = null)
{

View File

@@ -44,9 +44,6 @@ abstract class Renderer
$this->hasBranchCoverage = $hasBranchCoverage;
}
/**
* @param array<non-empty-string, float|int|string> $data
*/
protected function renderItemTemplate(Template $template, array $data): string
{
$numSeparator = '&nbsp;/&nbsp;';
@@ -174,8 +171,8 @@ abstract class Renderer
'version' => $this->version,
'runtime' => $this->runtimeString(),
'generator' => $this->generator,
'low_upper_bound' => (string) $this->thresholds->lowUpperBound(),
'high_lower_bound' => (string) $this->thresholds->highLowerBound(),
'low_upper_bound' => $this->thresholds->lowUpperBound(),
'high_lower_bound' => $this->thresholds->highLowerBound(),
],
);
}

View File

@@ -12,7 +12,6 @@ namespace SebastianBergmann\CodeCoverage\Report\Html;
use function array_values;
use function arsort;
use function asort;
use function assert;
use function count;
use function explode;
use function floor;
@@ -22,14 +21,10 @@ use function str_replace;
use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException;
use SebastianBergmann\CodeCoverage\Node\AbstractNode;
use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;
use SebastianBergmann\CodeCoverage\Node\File as FileNode;
use SebastianBergmann\Template\Exception;
use SebastianBergmann\Template\Template;
/**
* @phpstan-import-type ProcessedClassType from FileNode
* @phpstan-import-type ProcessedTraitType from FileNode
*
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class Dashboard extends Renderer
@@ -86,9 +81,7 @@ final class Dashboard extends Renderer
}
/**
* @param array<string, ProcessedClassType|ProcessedTraitType> $classes
*
* @return array{class: non-empty-string, method: non-empty-string}
* Returns the data for the Class/Method Complexity charts.
*/
private function complexity(array $classes, string $baseLink): array
{
@@ -122,21 +115,14 @@ final class Dashboard extends Renderer
];
}
$class = json_encode($result['class']);
assert($class !== false);
$method = json_encode($result['method']);
assert($method !== false);
return ['class' => $class, 'method' => $method];
return [
'class' => json_encode($result['class']),
'method' => json_encode($result['method']),
];
}
/**
* @param array<string, ProcessedClassType|ProcessedTraitType> $classes
*
* @return array{class: non-empty-string, method: non-empty-string}
* Returns the data for the Class / Method Coverage Distribution chart.
*/
private function coverageDistribution(array $classes): array
{
@@ -195,21 +181,14 @@ final class Dashboard extends Renderer
}
}
$class = json_encode(array_values($result['class']));
assert($class !== false);
$method = json_encode(array_values($result['method']));
assert($method !== false);
return ['class' => $class, 'method' => $method];
return [
'class' => json_encode(array_values($result['class'])),
'method' => json_encode(array_values($result['method'])),
];
}
/**
* @param array<string, ProcessedClassType|ProcessedTraitType> $classes
*
* @return array{class: string, method: string}
* Returns the classes / methods with insufficient coverage.
*/
private function insufficientCoverage(array $classes, string $baseLink): array
{
@@ -263,9 +242,7 @@ final class Dashboard extends Renderer
}
/**
* @param array<string, ProcessedClassType|ProcessedTraitType> $classes
*
* @return array{class: string, method: string}
* Returns the project risks according to the CRAP index.
*/
private function projectRisks(array $classes, string $baseLink): array
{

View File

@@ -108,11 +108,6 @@ use SebastianBergmann\Template\Exception;
use SebastianBergmann\Template\Template;
/**
* @phpstan-import-type ProcessedClassType from FileNode
* @phpstan-import-type ProcessedTraitType from FileNode
* @phpstan-import-type ProcessedMethodType from FileNode
* @phpstan-import-type ProcessedFunctionType from FileNode
*
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class File extends Renderer
@@ -120,7 +115,7 @@ final class File extends Renderer
/**
* @var array<int,true>
*/
private const array KEYWORD_TOKENS = [
private const KEYWORD_TOKENS = [
T_ABSTRACT => true,
T_ARRAY => true,
T_AS => true,
@@ -190,13 +185,8 @@ final class File extends Renderer
T_YIELD => true,
T_YIELD_FROM => true,
];
private const int HTML_SPECIAL_CHARS_FLAGS = ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE;
/**
* @var array<non-empty-string, list<string>>
*/
private static array $formattedSourceCache = [];
private int $htmlSpecialCharsFlags = ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE;
public function render(FileNode $node, string $file): void
{
@@ -324,9 +314,6 @@ final class File extends Renderer
return $items;
}
/**
* @param array<string, ProcessedClassType|ProcessedTraitType> $items
*/
private function renderTraitOrClassItems(array $items, Template $template, Template $methodItemTemplate): string
{
$buffer = '';
@@ -431,9 +418,6 @@ final class File extends Renderer
return $buffer;
}
/**
* @param array<string, ProcessedFunctionType> $functions
*/
private function renderFunctionItems(array $functions, Template $template): string
{
if (empty($functions)) {
@@ -452,9 +436,6 @@ final class File extends Renderer
return $buffer;
}
/**
* @param ProcessedFunctionType|ProcessedMethodType $item
*/
private function renderFunctionOrMethodItem(Template $template, array $item, string $indent = ''): string
{
$numMethods = 0;
@@ -495,7 +476,7 @@ final class File extends Renderer
'%s<a href="#%d"><abbr title="%s">%s</abbr></a>',
$indent,
$item['startLine'],
htmlspecialchars($item['signature'], self::HTML_SPECIAL_CHARS_FLAGS),
htmlspecialchars($item['signature'], $this->htmlSpecialCharsFlags),
$item['functionName'] ?? $item['methodName'],
),
'numMethods' => $numMethods,
@@ -573,7 +554,7 @@ final class File extends Renderer
$popover = sprintf(
' data-bs-title="%s" data-bs-content="%s" data-bs-placement="top" data-bs-html="true"',
$popoverTitle,
htmlspecialchars($popoverContent, self::HTML_SPECIAL_CHARS_FLAGS),
htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags),
);
}
@@ -660,7 +641,7 @@ final class File extends Renderer
$popover = sprintf(
' data-bs-title="%s" data-bs-content="%s" data-bs-placement="top" data-bs-html="true"',
$popoverTitle,
htmlspecialchars($popoverContent, self::HTML_SPECIAL_CHARS_FLAGS),
htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags),
);
}
@@ -750,7 +731,7 @@ final class File extends Renderer
$popover = sprintf(
' data-bs-title="%s" data-bs-content="%s" data-bs-placement="top" data-bs-html="true"',
$popoverTitle,
htmlspecialchars($popoverContent, self::HTML_SPECIAL_CHARS_FLAGS),
htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags),
);
}
@@ -787,7 +768,7 @@ final class File extends Renderer
}
if ($branchStructure !== '') { // don't show empty branches
$branches .= '<h5 class="structure-heading"><a name="' . htmlspecialchars($methodName, self::HTML_SPECIAL_CHARS_FLAGS) . '">' . $this->abbreviateMethodName($methodName) . '</a></h5>' . "\n";
$branches .= '<h5 class="structure-heading"><a name="' . htmlspecialchars($methodName, $this->htmlSpecialCharsFlags) . '">' . $this->abbreviateMethodName($methodName) . '</a></h5>' . "\n";
$branches .= $branchStructure;
}
}
@@ -797,9 +778,6 @@ final class File extends Renderer
return $branchesTemplate->render();
}
/**
* @param list<string> $codeLines
*/
private function renderBranchLines(array $branch, array $codeLines, array $testData): string
{
$linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}');
@@ -851,7 +829,7 @@ final class File extends Renderer
$popover = sprintf(
' data-bs-title="%s" data-bs-content="%s" data-bs-placement="top" data-bs-html="true"',
$popoverTitle,
htmlspecialchars($popoverContent, self::HTML_SPECIAL_CHARS_FLAGS),
htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags),
);
}
@@ -896,7 +874,7 @@ final class File extends Renderer
}
if ($pathStructure !== '') {
$paths .= '<h5 class="structure-heading"><a name="' . htmlspecialchars($methodName, self::HTML_SPECIAL_CHARS_FLAGS) . '">' . $this->abbreviateMethodName($methodName) . '</a></h5>' . "\n";
$paths .= '<h5 class="structure-heading"><a name="' . htmlspecialchars($methodName, $this->htmlSpecialCharsFlags) . '">' . $this->abbreviateMethodName($methodName) . '</a></h5>' . "\n";
$paths .= $pathStructure;
}
}
@@ -906,9 +884,6 @@ final class File extends Renderer
return $pathsTemplate->render();
}
/**
* @param list<string> $codeLines
*/
private function renderPathLines(array $path, array $branches, array $codeLines, array $testData): string
{
$linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}');
@@ -969,7 +944,7 @@ final class File extends Renderer
$popover = sprintf(
' data-bs-title="%s" data-bs-content="%s" data-bs-placement="top" data-bs-html="true"',
$popoverTitle,
htmlspecialchars($popoverContent, self::HTML_SPECIAL_CHARS_FLAGS),
htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags),
);
}
@@ -990,7 +965,7 @@ final class File extends Renderer
{
$template->setVar(
[
'lineNumber' => (string) $lineNumber,
'lineNumber' => $lineNumber,
'lineContent' => $lineContent,
'class' => $class,
'popover' => $popover,
@@ -1000,11 +975,6 @@ final class File extends Renderer
return $template->render();
}
/**
* @param non-empty-string $file
*
* @return list<string>
*/
private function loadFile(string $file): array
{
if (isset(self::$formattedSourceCache[$file])) {
@@ -1025,14 +995,14 @@ final class File extends Renderer
if ($token === '"' && $tokens[$j - 1] !== '\\') {
$result[$i] .= sprintf(
'<span class="string">%s</span>',
htmlspecialchars($token, self::HTML_SPECIAL_CHARS_FLAGS),
htmlspecialchars($token, $this->htmlSpecialCharsFlags),
);
$stringFlag = !$stringFlag;
} else {
$result[$i] .= sprintf(
'<span class="keyword">%s</span>',
htmlspecialchars($token, self::HTML_SPECIAL_CHARS_FLAGS),
htmlspecialchars($token, $this->htmlSpecialCharsFlags),
);
}
@@ -1044,7 +1014,7 @@ final class File extends Renderer
$value = str_replace(
["\t", ' '],
['&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;'],
htmlspecialchars($value, self::HTML_SPECIAL_CHARS_FLAGS),
htmlspecialchars($value, $this->htmlSpecialCharsFlags),
);
if ($value === "\n") {
@@ -1143,7 +1113,7 @@ final class File extends Renderer
return sprintf(
'<li%s>%s</li>',
$testCSS,
htmlspecialchars($test, self::HTML_SPECIAL_CHARS_FLAGS),
htmlspecialchars($test, $this->htmlSpecialCharsFlags),
);
}

View File

@@ -26,27 +26,27 @@ final class Text
/**
* @var string
*/
private const string COLOR_GREEN = "\x1b[30;42m";
private const COLOR_GREEN = "\x1b[30;42m";
/**
* @var string
*/
private const string COLOR_YELLOW = "\x1b[30;43m";
private const COLOR_YELLOW = "\x1b[30;43m";
/**
* @var string
*/
private const string COLOR_RED = "\x1b[37;41m";
private const COLOR_RED = "\x1b[37;41m";
/**
* @var string
*/
private const string COLOR_HEADER = "\x1b[1;37;40m";
private const COLOR_HEADER = "\x1b[1;37;40m";
/**
* @var string
*/
private const string COLOR_RESET = "\x1b[0m";
private const COLOR_RESET = "\x1b[0m";
private readonly Thresholds $thresholds;
private readonly bool $showUncoveredFiles;
private readonly bool $showOnlySummary;

View File

@@ -14,10 +14,10 @@ use SebastianBergmann\CodeCoverage\InvalidArgumentException;
/**
* @immutable
*/
final readonly class Thresholds
final class Thresholds
{
private int $lowUpperBound;
private int $highLowerBound;
private readonly int $lowUpperBound;
private readonly int $highLowerBound;
public static function default(): self
{

View File

@@ -18,9 +18,9 @@ use SebastianBergmann\Environment\Runtime;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class BuildInformation
final class BuildInformation
{
private DOMElement $contextNode;
private readonly DOMElement $contextNode;
public function __construct(DOMElement $contextNode)
{

View File

@@ -32,19 +32,12 @@ use SebastianBergmann\CodeCoverage\Driver\PathExistsButIsNotDirectoryException;
use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException;
use SebastianBergmann\CodeCoverage\Node\AbstractNode;
use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\Node\File as FileNode;
use SebastianBergmann\CodeCoverage\Util\Filesystem as DirectoryUtil;
use SebastianBergmann\CodeCoverage\Version;
use SebastianBergmann\CodeCoverage\XmlException;
use SebastianBergmann\Environment\Runtime;
/**
* @phpstan-import-type ProcessedClassType from File
* @phpstan-import-type ProcessedTraitType from File
* @phpstan-import-type ProcessedFunctionType from File
* @phpstan-import-type TestType from CodeCoverage
*/
final class Facade
{
private string $target;
@@ -182,9 +175,6 @@ final class Facade
$this->saveDocument($fileReport->asDom(), $file->id());
}
/**
* @param ProcessedClassType|ProcessedTraitType $unit
*/
private function processUnit(array $unit, Report $report): void
{
if (isset($unit['className'])) {
@@ -215,9 +205,6 @@ final class Facade
}
}
/**
* @param ProcessedFunctionType $function
*/
private function processFunction(array $function, Report $report): void
{
$functionObject = $report->functionObject($function['functionName']);
@@ -228,9 +215,6 @@ final class Facade
$functionObject->setTotals((string) $function['executableLines'], (string) $function['executedLines'], (string) $function['coverage']);
}
/**
* @param array<string, TestType> $tests
*/
private function processTests(array $tests): void
{
$testsObject = $this->project->tests();
@@ -245,9 +229,9 @@ final class Facade
$loc = $node->linesOfCode();
$totals->setNumLines(
$loc->linesOfCode(),
$loc->commentLinesOfCode(),
$loc->nonCommentLinesOfCode(),
$loc['linesOfCode'],
$loc['commentLinesOfCode'],
$loc['nonCommentLinesOfCode'],
$node->numberOfExecutableLines(),
$node->numberOfExecutedLines(),
);

View File

@@ -9,7 +9,6 @@
*/
namespace SebastianBergmann\CodeCoverage\Report\Xml;
use function assert;
use DOMDocument;
use DOMElement;
@@ -40,8 +39,6 @@ class File
);
}
assert($totalsContainer instanceof DOMElement);
return new Totals($totalsContainer);
}
@@ -68,8 +65,6 @@ class File
),
);
assert($lineNode instanceof DOMElement);
return new Coverage($lineNode, $line);
}

View File

@@ -14,9 +14,9 @@ use DOMElement;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Method
final class Method
{
private DOMElement $contextNode;
private readonly DOMElement $contextNode;
public function __construct(DOMElement $context, string $name)
{

View File

@@ -9,7 +9,6 @@
*/
namespace SebastianBergmann\CodeCoverage\Report\Xml;
use function assert;
use DOMDocument;
use DOMElement;
@@ -44,8 +43,6 @@ abstract class Node
);
}
assert($totalsContainer instanceof DOMElement);
return new Totals($totalsContainer);
}

View File

@@ -9,9 +9,7 @@
*/
namespace SebastianBergmann\CodeCoverage\Report\Xml;
use function assert;
use DOMDocument;
use DOMElement;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
@@ -45,8 +43,6 @@ final class Project extends Node
);
}
assert($buildNode instanceof DOMElement);
return new BuildInformation($buildNode);
}
@@ -66,8 +62,6 @@ final class Project extends Node
);
}
assert($testsNode instanceof DOMElement);
return new Tests($testsNode);
}

View File

@@ -9,11 +9,9 @@
*/
namespace SebastianBergmann\CodeCoverage\Report\Xml;
use function assert;
use function basename;
use function dirname;
use DOMDocument;
use DOMElement;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
@@ -40,7 +38,7 @@ final class Report extends File
return $this->dom();
}
public function functionObject(string $name): Method
public function functionObject($name): Method
{
$node = $this->contextNode()->appendChild(
$this->dom()->createElementNS(
@@ -49,17 +47,15 @@ final class Report extends File
),
);
assert($node instanceof DOMElement);
return new Method($node, $name);
}
public function classObject(string $name): Unit
public function classObject($name): Unit
{
return $this->unitObject('class', $name);
}
public function traitObject(string $name): Unit
public function traitObject($name): Unit
{
return $this->unitObject('trait', $name);
}
@@ -80,8 +76,6 @@ final class Report extends File
);
}
assert($source instanceof DOMElement);
return new Source($source);
}
@@ -91,7 +85,7 @@ final class Report extends File
$this->contextNode()->setAttribute('path', dirname($name));
}
private function unitObject(string $tagName, string $name): Unit
private function unitObject(string $tagName, $name): Unit
{
$node = $this->contextNode()->appendChild(
$this->dom()->createElementNS(
@@ -100,8 +94,6 @@ final class Report extends File
),
);
assert($node instanceof DOMElement);
return new Unit($node, $name);
}
}

View File

@@ -17,9 +17,9 @@ use TheSeer\Tokenizer\XMLSerializer;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Source
final class Source
{
private DOMElement $context;
private readonly DOMElement $context;
public function __construct(DOMElement $context)
{

View File

@@ -11,16 +11,15 @@ namespace SebastianBergmann\CodeCoverage\Report\Xml;
use function assert;
use DOMElement;
use SebastianBergmann\CodeCoverage\CodeCoverage;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-import-type TestType from CodeCoverage
* @phpstan-import-type TestType from \SebastianBergmann\CodeCoverage\CodeCoverage
*/
final readonly class Tests
final class Tests
{
private DOMElement $contextNode;
private readonly DOMElement $contextNode;
public function __construct(DOMElement $context)
{

View File

@@ -17,14 +17,14 @@ use SebastianBergmann\CodeCoverage\Util\Percentage;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Totals
final class Totals
{
private DOMNode $container;
private DOMElement $linesNode;
private DOMElement $methodsNode;
private DOMElement $functionsNode;
private DOMElement $classesNode;
private DOMElement $traitsNode;
private readonly DOMNode $container;
private readonly DOMElement $linesNode;
private readonly DOMElement $methodsNode;
private readonly DOMElement $functionsNode;
private readonly DOMElement $classesNode;
private readonly DOMElement $traitsNode;
public function __construct(DOMElement $container)
{

View File

@@ -15,9 +15,9 @@ use DOMElement;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Unit
final class Unit
{
private DOMElement $contextNode;
private readonly DOMElement $contextNode;
public function __construct(DOMElement $context, string $name)
{
@@ -68,8 +68,6 @@ final readonly class Unit
),
);
assert($node instanceof DOMElement);
return new Method($node, $name);
}

View File

@@ -23,17 +23,12 @@ use SebastianBergmann\FileIterator\Facade as FileIteratorFacade;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-type CachedDataForFile array{
* interfacesIn: array<string, Interface_>,
* classesIn: array<string, Class_>,
* traitsIn: array<string, Trait_>,
* functionsIn: array<string, Function_>,
* linesOfCodeFor: LinesOfCode,
* ignoredLinesFor: LinesType,
* executableLinesIn: LinesType
* }
*
* @phpstan-import-type LinesType from FileAnalyser
* @phpstan-import-type CodeUnitFunctionType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitMethodType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitClassType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitTraitType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
* @phpstan-import-type LinesType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
*/
final class CachingFileAnalyser implements FileAnalyser
{
@@ -42,10 +37,6 @@ final class CachingFileAnalyser implements FileAnalyser
private readonly FileAnalyser $analyser;
private readonly bool $useAnnotationsForIgnoringCode;
private readonly bool $ignoreDeprecatedCode;
/**
* @var array<non-empty-string, CachedDataForFile>
*/
private array $cache = [];
public function __construct(string $directory, FileAnalyser $analyser, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode)
@@ -59,19 +50,7 @@ final class CachingFileAnalyser implements FileAnalyser
}
/**
* @return array<string, Interface_>
*/
public function interfacesIn(string $filename): array
{
if (!isset($this->cache[$filename])) {
$this->process($filename);
}
return $this->cache[$filename]['interfacesIn'];
}
/**
* @return array<string, Class_>
* @return array<string, CodeUnitClassType>
*/
public function classesIn(string $filename): array
{
@@ -83,7 +62,7 @@ final class CachingFileAnalyser implements FileAnalyser
}
/**
* @return array<string, Trait_>
* @return array<string, CodeUnitTraitType>
*/
public function traitsIn(string $filename): array
{
@@ -95,7 +74,7 @@ final class CachingFileAnalyser implements FileAnalyser
}
/**
* @return array<string, Function_>
* @return array<string, CodeUnitFunctionType>
*/
public function functionsIn(string $filename): array
{
@@ -106,7 +85,10 @@ final class CachingFileAnalyser implements FileAnalyser
return $this->cache[$filename]['functionsIn'];
}
public function linesOfCodeFor(string $filename): LinesOfCode
/**
* @return LinesOfCodeType
*/
public function linesOfCodeFor(string $filename): array
{
if (!isset($this->cache[$filename])) {
$this->process($filename);
@@ -150,7 +132,6 @@ final class CachingFileAnalyser implements FileAnalyser
}
$this->cache[$filename] = [
'interfacesIn' => $this->analyser->interfacesIn($filename),
'classesIn' => $this->analyser->classesIn($filename),
'traitsIn' => $this->analyser->traitsIn($filename),
'functionsIn' => $this->analyser->functionsIn($filename),
@@ -162,9 +143,6 @@ final class CachingFileAnalyser implements FileAnalyser
$this->write($filename, $this->cache[$filename]);
}
/**
* @return CachedDataForFile|false
*/
private function read(string $filename): array|false
{
$cacheFile = $this->cacheFile($filename);
@@ -175,22 +153,10 @@ final class CachingFileAnalyser implements FileAnalyser
return unserialize(
file_get_contents($cacheFile),
[
'allowed_classes' => [
Class_::class,
Function_::class,
Interface_::class,
LinesOfCode::class,
Method::class,
Trait_::class,
],
],
['allowed_classes' => false],
);
}
/**
* @param CachedDataForFile $data
*/
private function write(string $filename, array $data): void
{
file_put_contents(

View File

@@ -21,6 +21,7 @@ use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
@@ -31,51 +32,63 @@ use SebastianBergmann\Complexity\CyclomaticComplexityCalculatingVisitor;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-type CodeUnitFunctionType = array{
* name: string,
* namespacedName: string,
* namespace: string,
* signature: string,
* startLine: int,
* endLine: int,
* ccn: int
* }
* @phpstan-type CodeUnitMethodType = array{
* methodName: string,
* signature: string,
* visibility: string,
* startLine: int,
* endLine: int,
* ccn: int
* }
* @phpstan-type CodeUnitClassType = array{
* name: string,
* namespacedName: string,
* namespace: string,
* startLine: int,
* endLine: int,
* methods: array<string, CodeUnitMethodType>
* }
* @phpstan-type CodeUnitTraitType = array{
* name: string,
* namespacedName: string,
* namespace: string,
* startLine: int,
* endLine: int,
* methods: array<string, CodeUnitMethodType>
* }
*/
final class CodeUnitFindingVisitor extends NodeVisitorAbstract
{
/**
* @var non-empty-string
*/
private string $file;
/**
* @var array<string, \SebastianBergmann\CodeCoverage\StaticAnalysis\Interface_>
*/
private array $interfaces = [];
/**
* @var array<string, \SebastianBergmann\CodeCoverage\StaticAnalysis\Class_>
* @var array<string, CodeUnitClassType>
*/
private array $classes = [];
/**
* @var array<string, \SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_>
* @var array<string, CodeUnitTraitType>
*/
private array $traits = [];
/**
* @var array<string, \SebastianBergmann\CodeCoverage\StaticAnalysis\Function_>
* @var array<string, CodeUnitFunctionType>
*/
private array $functions = [];
/**
* @param non-empty-string $file
*/
public function __construct(string $file)
public function enterNode(Node $node): void
{
$this->file = $file;
}
public function enterNode(Node $node): null
{
if ($node instanceof Interface_) {
$this->processInterface($node);
}
if ($node instanceof Class_) {
if ($node->isAnonymous()) {
return null;
return;
}
$this->processClass($node);
@@ -85,52 +98,27 @@ final class CodeUnitFindingVisitor extends NodeVisitorAbstract
$this->processTrait($node);
}
if (!$node instanceof Function_) {
return null;
if (!$node instanceof ClassMethod && !$node instanceof Function_) {
return;
}
if ($node instanceof ClassMethod) {
$parentNode = $node->getAttribute('parent');
if ($parentNode instanceof Class_ && $parentNode->isAnonymous()) {
return;
}
$this->processMethod($node);
return;
}
$this->processFunction($node);
return null;
}
public function leaveNode(Node $node): null
{
if ($node instanceof Class_ && $node->isAnonymous()) {
return null;
}
if (!$node instanceof Class_ && !$node instanceof Trait_) {
return null;
}
$traits = [];
foreach ($node->getTraitUses() as $traitUse) {
foreach ($traitUse->traits as $trait) {
$traits[] = $trait->toString();
}
}
if (empty($traits)) {
return null;
}
$this->postProcessClassOrTrait($node, $traits);
return null;
}
/**
* @return array<string, \SebastianBergmann\CodeCoverage\StaticAnalysis\Interface_>
*/
public function interfaces(): array
{
return $this->interfaces;
}
/**
* @return array<string, \SebastianBergmann\CodeCoverage\StaticAnalysis\Class_>
* @return array<string, CodeUnitClassType>
*/
public function classes(): array
{
@@ -138,7 +126,7 @@ final class CodeUnitFindingVisitor extends NodeVisitorAbstract
}
/**
* @return array<string, \SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_>
* @return array<string, CodeUnitTraitType>
*/
public function traits(): array
{
@@ -146,7 +134,7 @@ final class CodeUnitFindingVisitor extends NodeVisitorAbstract
}
/**
* @return array<string, \SebastianBergmann\CodeCoverage\StaticAnalysis\Function_>
* @return array<string, CodeUnitFunctionType>
*/
public function functions(): array
{
@@ -222,66 +210,32 @@ final class CodeUnitFindingVisitor extends NodeVisitorAbstract
return $type->toString();
}
private function visibility(ClassMethod $node): Visibility
private function visibility(ClassMethod $node): string
{
if ($node->isPrivate()) {
return Visibility::Private;
return 'private';
}
if ($node->isProtected()) {
return Visibility::Protected;
return 'protected';
}
return Visibility::Public;
}
private function processInterface(Interface_ $node): void
{
$name = $node->name->toString();
$namespacedName = $node->namespacedName->toString();
$parentInterfaces = [];
foreach ($node->extends as $parentInterface) {
$parentInterfaces[] = $parentInterface->toString();
}
$this->interfaces[$namespacedName] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Interface_(
$name,
$namespacedName,
$this->namespace($namespacedName, $name),
$node->getStartLine(),
$node->getEndLine(),
$parentInterfaces,
);
return 'public';
}
private function processClass(Class_ $node): void
{
$name = $node->name->toString();
$namespacedName = $node->namespacedName->toString();
$parentClass = null;
$interfaces = [];
if ($node->extends instanceof Name) {
$parentClass = $node->extends->toString();
}
foreach ($node->implements as $interface) {
$interfaces[] = $interface->toString();
}
$this->classes[$namespacedName] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Class_(
$name,
$namespacedName,
$this->namespace($namespacedName, $name),
$this->file,
$node->getStartLine(),
$node->getEndLine(),
$parentClass,
$interfaces,
[],
$this->processMethods($node->getMethods()),
);
$this->classes[$namespacedName] = [
'name' => $name,
'namespacedName' => $namespacedName,
'namespace' => $this->namespace($namespacedName, $name),
'startLine' => $node->getStartLine(),
'endLine' => $node->getEndLine(),
'methods' => [],
];
}
private function processTrait(Trait_ $node): void
@@ -289,39 +243,57 @@ final class CodeUnitFindingVisitor extends NodeVisitorAbstract
$name = $node->name->toString();
$namespacedName = $node->namespacedName->toString();
$this->traits[$namespacedName] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_(
$name,
$namespacedName,
$this->namespace($namespacedName, $name),
$this->file,
$node->getStartLine(),
$node->getEndLine(),
[],
$this->processMethods($node->getMethods()),
);
$this->traits[$namespacedName] = [
'name' => $name,
'namespacedName' => $namespacedName,
'namespace' => $this->namespace($namespacedName, $name),
'startLine' => $node->getStartLine(),
'endLine' => $node->getEndLine(),
'methods' => [],
];
}
/**
* @param list<ClassMethod> $nodes
*
* @return array<non-empty-string, Method>
*/
private function processMethods(array $nodes): array
private function processMethod(ClassMethod $node): void
{
$methods = [];
$parentNode = $node->getAttribute('parent');
foreach ($nodes as $node) {
$methods[$node->name->toString()] = new Method(
$node->name->toString(),
$node->getStartLine(),
$node->getEndLine(),
$this->signature($node),
$this->visibility($node),
$this->cyclomaticComplexity($node),
);
if ($parentNode instanceof Interface_) {
return;
}
return $methods;
assert($parentNode instanceof Class_ || $parentNode instanceof Trait_ || $parentNode instanceof Enum_);
assert(isset($parentNode->name));
assert(isset($parentNode->namespacedName));
assert($parentNode->namespacedName instanceof Name);
$parentName = $parentNode->name->toString();
$parentNamespacedName = $parentNode->namespacedName->toString();
if ($parentNode instanceof Class_) {
$storage = &$this->classes;
} else {
$storage = &$this->traits;
}
if (!isset($storage[$parentNamespacedName])) {
$storage[$parentNamespacedName] = [
'name' => $parentName,
'namespacedName' => $parentNamespacedName,
'namespace' => $this->namespace($parentNamespacedName, $parentName),
'startLine' => $parentNode->getStartLine(),
'endLine' => $parentNode->getEndLine(),
'methods' => [],
];
}
$storage[$parentNamespacedName]['methods'][$node->name->toString()] = [
'methodName' => $node->name->toString(),
'signature' => $this->signature($node),
'visibility' => $this->visibility($node),
'startLine' => $node->getStartLine(),
'endLine' => $node->getEndLine(),
'ccn' => $this->cyclomaticComplexity($node),
];
}
private function processFunction(Function_ $node): void
@@ -333,15 +305,15 @@ final class CodeUnitFindingVisitor extends NodeVisitorAbstract
$name = $node->name->toString();
$namespacedName = $node->namespacedName->toString();
$this->functions[$namespacedName] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Function_(
$name,
$namespacedName,
$this->namespace($namespacedName, $name),
$node->getStartLine(),
$node->getEndLine(),
$this->signature($node),
$this->cyclomaticComplexity($node),
);
$this->functions[$namespacedName] = [
'name' => $name,
'namespacedName' => $namespacedName,
'namespace' => $this->namespace($namespacedName, $name),
'signature' => $this->signature($node),
'startLine' => $node->getStartLine(),
'endLine' => $node->getEndLine(),
'ccn' => $this->cyclomaticComplexity($node),
];
}
private function namespace(string $namespacedName, string $name): string
@@ -385,44 +357,4 @@ final class CodeUnitFindingVisitor extends NodeVisitorAbstract
return $node->toString();
}
/**
* @param list<non-empty-string> $traits
*/
private function postProcessClassOrTrait(Class_|Trait_ $node, array $traits): void
{
$name = $node->namespacedName->toString();
if ($node instanceof Class_) {
assert(isset($this->classes[$name]));
$this->classes[$name] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Class_(
$this->classes[$name]->name(),
$this->classes[$name]->namespacedName(),
$this->classes[$name]->namespace(),
$this->classes[$name]->file(),
$this->classes[$name]->startLine(),
$this->classes[$name]->endLine(),
$this->classes[$name]->parentClass(),
$this->classes[$name]->interfaces(),
$traits,
$this->classes[$name]->methods(),
);
return;
}
assert(isset($this->traits[$name]));
$this->traits[$name] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_(
$this->traits[$name]->name(),
$this->traits[$name]->namespacedName(),
$this->traits[$name]->namespace(),
$this->traits[$name]->file(),
$this->traits[$name]->startLine(),
$this->traits[$name]->endLine(),
$traits,
$this->traits[$name]->methods(),
);
}
}

View File

@@ -27,7 +27,7 @@ use PhpParser\NodeVisitorAbstract;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-import-type LinesType from FileAnalyser
* @phpstan-import-type LinesType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
*/
final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
{
@@ -54,7 +54,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
$this->source = $source;
}
public function enterNode(Node $node): null
public function enterNode(Node $node): void
{
foreach ($node->getComments() as $comment) {
$commentLine = $comment->getStartLine();
@@ -80,7 +80,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
}
}
return null;
return;
}
if ($node instanceof Node\Stmt\Interface_) {
@@ -88,7 +88,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
$this->unsets[$line] = true;
}
return null;
return;
}
if ($node instanceof Node\Stmt\Declare_ ||
@@ -113,7 +113,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
$node instanceof Node\Name ||
$node instanceof Node\Param ||
$node instanceof Node\Scalar) {
return null;
return;
}
if ($node instanceof Node\Expr\Match_) {
@@ -125,13 +125,13 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
);
}
return null;
return;
}
if ($node instanceof Node\Stmt\Expression && $node->expr instanceof Node\Expr\Throw_) {
$this->setLineBranch($node->expr->expr->getEndLine(), $node->expr->expr->getEndLine(), ++$this->nextBranch);
return null;
return;
}
if ($node instanceof Node\Stmt\Enum_ ||
@@ -176,7 +176,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
}
if ($isConcreteClassLike) {
return null;
return;
}
$hasEmptyBody = [] === $node->stmts ||
@@ -188,15 +188,15 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
if ($hasEmptyBody) {
if ($node->getEndLine() === $node->getStartLine() && isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) {
return null;
return;
}
$this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch);
return null;
return;
}
return null;
return;
}
if ($node instanceof Node\Expr\ArrowFunction) {
@@ -208,12 +208,12 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
$endLine = $node->expr->getEndLine();
if ($endLine < $startLine) {
return null;
return;
}
$this->setLineBranch($startLine, $endLine, ++$this->nextBranch);
return null;
return;
}
if ($node instanceof Node\Expr\Ternary) {
@@ -226,7 +226,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
$this->setLineBranch($node->else->getStartLine(), $node->else->getEndLine(), ++$this->nextBranch);
}
return null;
return;
}
if ($node instanceof Node\Expr\BinaryOp\Coalesce) {
@@ -234,14 +234,14 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
$this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch);
}
return null;
return;
}
if ($node instanceof Node\Stmt\If_ ||
$node instanceof Node\Stmt\ElseIf_ ||
$node instanceof Node\Stmt\Case_) {
if (null === $node->cond) {
return null;
return;
}
$this->setLineBranch(
@@ -250,7 +250,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
++$this->nextBranch,
);
return null;
return;
}
if ($node instanceof Node\Stmt\For_) {
@@ -292,7 +292,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
}
if (null === $startLine || null === $endLine) {
return null;
return;
}
$this->setLineBranch(
@@ -301,7 +301,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
++$this->nextBranch,
);
return null;
return;
}
if ($node instanceof Node\Stmt\Foreach_) {
@@ -311,7 +311,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
++$this->nextBranch,
);
return null;
return;
}
if ($node instanceof Node\Stmt\While_ ||
@@ -322,7 +322,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
++$this->nextBranch,
);
return null;
return;
}
if ($node instanceof Node\Stmt\Catch_) {
@@ -337,7 +337,7 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
++$this->nextBranch,
);
return null;
return;
}
if ($node instanceof Node\Expr\CallLike) {
@@ -349,19 +349,17 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
$this->setLineBranch($node->getStartLine(), $node->getEndLine(), $branch);
return null;
return;
}
if (isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) {
return null;
return;
}
$this->setLineBranch($node->getStartLine(), $node->getEndLine(), ++$this->nextBranch);
return null;
}
public function afterTraverse(array $nodes): null
public function afterTraverse(array $nodes): void
{
$lines = explode("\n", $this->source);
@@ -381,8 +379,6 @@ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
$this->executableLinesGroupedByBranch,
$this->unsets,
);
return null;
}
/**

View File

@@ -12,31 +12,39 @@ namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-type LinesType array<int, int>
* @phpstan-import-type CodeUnitFunctionType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitMethodType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitClassType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitTraitType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
*
* @phpstan-type LinesOfCodeType = array{
* linesOfCode: int,
* commentLinesOfCode: int,
* nonCommentLinesOfCode: int
* }
* @phpstan-type LinesType = array<int, int>
*/
interface FileAnalyser
{
/**
* @return array<string, Interface_>
*/
public function interfacesIn(string $filename): array;
/**
* @return array<string, Class_>
* @return array<string, CodeUnitClassType>
*/
public function classesIn(string $filename): array;
/**
* @return array<string, Trait_>
* @return array<string, CodeUnitTraitType>
*/
public function traitsIn(string $filename): array;
/**
* @return array<string, Function_>
* @return array<string, CodeUnitFunctionType>
*/
public function functionsIn(string $filename): array;
public function linesOfCodeFor(string $filename): LinesOfCode;
/**
* @return LinesOfCodeType
*/
public function linesOfCodeFor(string $filename): array;
/**
* @return LinesType

View File

@@ -39,7 +39,7 @@ final class IgnoredLinesFindingVisitor extends NodeVisitorAbstract
$this->ignoreDeprecated = $ignoreDeprecated;
}
public function enterNode(Node $node): null
public function enterNode(Node $node): void
{
if (!$node instanceof Class_ &&
!$node instanceof Trait_ &&
@@ -48,11 +48,11 @@ final class IgnoredLinesFindingVisitor extends NodeVisitorAbstract
!$node instanceof ClassMethod &&
!$node instanceof Function_ &&
!$node instanceof Attribute) {
return null;
return;
}
if ($node instanceof Class_ && $node->isAnonymous()) {
return null;
return;
}
if ($node instanceof Class_ ||
@@ -68,11 +68,11 @@ final class IgnoredLinesFindingVisitor extends NodeVisitorAbstract
}
if (!$this->useAnnotationsForIgnoringCode) {
return null;
return;
}
if ($node instanceof Interface_) {
return null;
return;
}
if ($node instanceof Attribute &&
@@ -84,12 +84,10 @@ final class IgnoredLinesFindingVisitor extends NodeVisitorAbstract
$this->ignoredLines[] = $line;
}
return null;
return;
}
$this->processDocComment($node);
return null;
}
/**

View File

@@ -34,32 +34,32 @@ use SebastianBergmann\LinesOfCode\LineCountingVisitor;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @phpstan-import-type LinesType from FileAnalyser
* @phpstan-import-type CodeUnitFunctionType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitMethodType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitClassType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type CodeUnitTraitType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @phpstan-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
* @phpstan-import-type LinesType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
*/
final class ParsingFileAnalyser implements FileAnalyser
{
/**
* @var array<string, array<string, Interface_>>
*/
private array $interfaces = [];
/**
* @var array<string, array<string, Class_>>
* @var array<string, array<string, CodeUnitClassType>>
*/
private array $classes = [];
/**
* @var array<string, array<string, Trait_>>
* @var array<string, array<string, CodeUnitTraitType>>
*/
private array $traits = [];
/**
* @var array<string, array<string, Function_>>
* @var array<string, array<string, CodeUnitFunctionType>>
*/
private array $functions = [];
/**
* @var array<string, LinesOfCode>
* @var array<string, LinesOfCodeType>
*/
private array $linesOfCode = [];
@@ -82,17 +82,7 @@ final class ParsingFileAnalyser implements FileAnalyser
}
/**
* @return array<string, Interface_>
*/
public function interfacesIn(string $filename): array
{
$this->analyse($filename);
return $this->interfaces[$filename];
}
/**
* @return array<string, Class_>
* @return array<string, CodeUnitClassType>
*/
public function classesIn(string $filename): array
{
@@ -102,7 +92,7 @@ final class ParsingFileAnalyser implements FileAnalyser
}
/**
* @return array<string, Trait_>
* @return array<string, CodeUnitTraitType>
*/
public function traitsIn(string $filename): array
{
@@ -112,7 +102,7 @@ final class ParsingFileAnalyser implements FileAnalyser
}
/**
* @return array<string, Function_>
* @return array<string, CodeUnitFunctionType>
*/
public function functionsIn(string $filename): array
{
@@ -121,7 +111,10 @@ final class ParsingFileAnalyser implements FileAnalyser
return $this->functions[$filename];
}
public function linesOfCodeFor(string $filename): LinesOfCode
/**
* @return LinesOfCodeType
*/
public function linesOfCodeFor(string $filename): array
{
$this->analyse($filename);
@@ -153,7 +146,7 @@ final class ParsingFileAnalyser implements FileAnalyser
*/
private function analyse(string $filename): void
{
if (isset($this->interfaces[$filename])) {
if (isset($this->classes[$filename])) {
return;
}
@@ -174,7 +167,7 @@ final class ParsingFileAnalyser implements FileAnalyser
assert($nodes !== null);
$traverser = new NodeTraverser;
$codeUnitFindingVisitor = new CodeUnitFindingVisitor($filename);
$codeUnitFindingVisitor = new CodeUnitFindingVisitor;
$lineCountingVisitor = new LineCountingVisitor($linesOfCode);
$ignoredLinesFindingVisitor = new IgnoredLinesFindingVisitor($this->useAnnotationsForIgnoringCode, $this->ignoreDeprecatedCode);
$executableLinesFindingVisitor = new ExecutableLinesFindingVisitor($source);
@@ -202,7 +195,6 @@ final class ParsingFileAnalyser implements FileAnalyser
}
// @codeCoverageIgnoreEnd
$this->interfaces[$filename] = $codeUnitFindingVisitor->interfaces();
$this->classes[$filename] = $codeUnitFindingVisitor->classes();
$this->traits[$filename] = $codeUnitFindingVisitor->traits();
$this->functions[$filename] = $codeUnitFindingVisitor->functions();
@@ -222,11 +214,11 @@ final class ParsingFileAnalyser implements FileAnalyser
$result = $lineCountingVisitor->result();
$this->linesOfCode[$filename] = new LinesOfCode(
$result->linesOfCode(),
$result->commentLinesOfCode(),
$result->nonCommentLinesOfCode(),
);
$this->linesOfCode[$filename] = [
'linesOfCode' => $result->linesOfCode(),
'commentLinesOfCode' => $result->commentLinesOfCode(),
'nonCommentLinesOfCode' => $result->nonCommentLinesOfCode(),
];
}
private function findLinesIgnoredByLineBasedAnnotations(string $filename, string $source, bool $useAnnotationsForIgnoringCode): void

View File

@@ -1,174 +0,0 @@
<?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\StaticAnalysis;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Class_
{
/**
* @var non-empty-string
*/
private string $name;
/**
* @var non-empty-string
*/
private string $namespacedName;
private string $namespace;
/**
* @var non-empty-string
*/
private string $file;
/**
* @var non-negative-int
*/
private int $startLine;
/**
* @var non-negative-int
*/
private int $endLine;
/**
* @var ?non-empty-string
*/
private ?string $parentClass;
/**
* @var list<non-empty-string>
*/
private array $interfaces;
/**
* @var list<non-empty-string>
*/
private array $traits;
/**
* @var array<non-empty-string, Method>
*/
private array $methods;
/**
* @param non-empty-string $name
* @param non-empty-string $namespacedName
* @param non-empty-string $file
* @param non-negative-int $startLine
* @param non-negative-int $endLine
* @param ?non-empty-string $parentClass
* @param list<non-empty-string> $interfaces
* @param list<non-empty-string> $traits
* @param array<non-empty-string, Method> $methods
*/
public function __construct(string $name, string $namespacedName, string $namespace, string $file, int $startLine, int $endLine, ?string $parentClass, array $interfaces, array $traits, array $methods)
{
$this->name = $name;
$this->namespacedName = $namespacedName;
$this->namespace = $namespace;
$this->file = $file;
$this->startLine = $startLine;
$this->endLine = $endLine;
$this->parentClass = $parentClass;
$this->interfaces = $interfaces;
$this->traits = $traits;
$this->methods = $methods;
}
/**
* @return non-empty-string
*/
public function name(): string
{
return $this->name;
}
/**
* @return non-empty-string
*/
public function namespacedName(): string
{
return $this->namespacedName;
}
public function isNamespaced(): bool
{
return $this->namespace !== '';
}
public function namespace(): string
{
return $this->namespace;
}
/**
* @return non-empty-string
*/
public function file(): string
{
return $this->file;
}
/**
* @return non-negative-int
*/
public function startLine(): int
{
return $this->startLine;
}
/**
* @return non-negative-int
*/
public function endLine(): int
{
return $this->endLine;
}
public function hasParent(): bool
{
return $this->parentClass !== null;
}
/**
* @return ?non-empty-string
*/
public function parentClass(): ?string
{
return $this->parentClass;
}
/**
* @return list<non-empty-string>
*/
public function interfaces(): array
{
return $this->interfaces;
}
/**
* @return list<non-empty-string>
*/
public function traits(): array
{
return $this->traits;
}
/**
* @return array<non-empty-string, Method>
*/
public function methods(): array
{
return $this->methods;
}
}

View File

@@ -1,124 +0,0 @@
<?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\StaticAnalysis;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Function_
{
/**
* @var non-empty-string
*/
private string $name;
/**
* @var non-empty-string
*/
private string $namespacedName;
private string $namespace;
/**
* @var non-negative-int
*/
private int $startLine;
/**
* @var non-negative-int
*/
private int $endLine;
/**
* @var non-empty-string
*/
private string $signature;
/**
* @var positive-int
*/
private int $cyclomaticComplexity;
/**
* @param non-empty-string $name
* @param non-empty-string $namespacedName
* @param non-negative-int $startLine
* @param non-negative-int $endLine
* @param non-empty-string $signature
* @param positive-int $cyclomaticComplexity
*/
public function __construct(string $name, string $namespacedName, string $namespace, int $startLine, int $endLine, string $signature, int $cyclomaticComplexity)
{
$this->name = $name;
$this->namespacedName = $namespacedName;
$this->namespace = $namespace;
$this->startLine = $startLine;
$this->endLine = $endLine;
$this->signature = $signature;
$this->cyclomaticComplexity = $cyclomaticComplexity;
}
/**
* @return non-empty-string
*/
public function name(): string
{
return $this->name;
}
/**
* @return non-empty-string
*/
public function namespacedName(): string
{
return $this->namespacedName;
}
public function isNamespaced(): bool
{
return $this->namespace !== '';
}
public function namespace(): string
{
return $this->namespace;
}
/**
* @return non-negative-int
*/
public function startLine(): int
{
return $this->startLine;
}
/**
* @return non-negative-int
*/
public function endLine(): int
{
return $this->endLine;
}
/**
* @return non-empty-string
*/
public function signature(): string
{
return $this->signature;
}
/**
* @return positive-int
*/
public function cyclomaticComplexity(): int
{
return $this->cyclomaticComplexity;
}
}

View File

@@ -1,109 +0,0 @@
<?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\StaticAnalysis;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Interface_
{
/**
* @var non-empty-string
*/
private string $name;
/**
* @var non-empty-string
*/
private string $namespacedName;
private string $namespace;
/**
* @var non-negative-int
*/
private int $startLine;
/**
* @var non-negative-int
*/
private int $endLine;
/**
* @var list<non-empty-string>
*/
private array $parentInterfaces;
/**
* @param non-empty-string $name
* @param non-empty-string $namespacedName
* @param non-negative-int $startLine
* @param non-negative-int $endLine
* @param list<non-empty-string> $parentInterfaces
*/
public function __construct(string $name, string $namespacedName, string $namespace, int $startLine, int $endLine, array $parentInterfaces)
{
$this->name = $name;
$this->namespacedName = $namespacedName;
$this->namespace = $namespace;
$this->startLine = $startLine;
$this->endLine = $endLine;
$this->parentInterfaces = $parentInterfaces;
}
/**
* @return non-empty-string
*/
public function name(): string
{
return $this->name;
}
/**
* @return non-empty-string
*/
public function namespacedName(): string
{
return $this->namespacedName;
}
public function isNamespaced(): bool
{
return $this->namespace !== '';
}
public function namespace(): string
{
return $this->namespace;
}
/**
* @return non-negative-int
*/
public function startLine(): int
{
return $this->startLine;
}
/**
* @return non-negative-int
*/
public function endLine(): int
{
return $this->endLine;
}
/**
* @return list<non-empty-string>
*/
public function parentInterfaces(): array
{
return $this->parentInterfaces;
}
}

View File

@@ -1,67 +0,0 @@
<?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\StaticAnalysis;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class LinesOfCode
{
/**
* @var non-negative-int
*/
private int $linesOfCode;
/**
* @var non-negative-int
*/
private int $commentLinesOfCode;
/**
* @var non-negative-int
*/
private int $nonCommentLinesOfCode;
/**
* @param non-negative-int $linesOfCode
* @param non-negative-int $commentLinesOfCode
* @param non-negative-int $nonCommentLinesOfCode
*/
public function __construct(int $linesOfCode, int $commentLinesOfCode, int $nonCommentLinesOfCode)
{
$this->linesOfCode = $linesOfCode;
$this->commentLinesOfCode = $commentLinesOfCode;
$this->nonCommentLinesOfCode = $nonCommentLinesOfCode;
}
/**
* @return non-negative-int
*/
public function linesOfCode(): int
{
return $this->linesOfCode;
}
/**
* @return non-negative-int
*/
public function commentLinesOfCode(): int
{
return $this->commentLinesOfCode;
}
/**
* @return non-negative-int
*/
public function nonCommentLinesOfCode(): int
{
return $this->nonCommentLinesOfCode;
}
}

View File

@@ -1,104 +0,0 @@
<?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\StaticAnalysis;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Method
{
/**
* @var non-empty-string
*/
private string $name;
/**
* @var non-negative-int
*/
private int $startLine;
/**
* @var non-negative-int
*/
private int $endLine;
private Visibility $visibility;
/**
* @var non-empty-string
*/
private string $signature;
/**
* @var positive-int
*/
private int $cyclomaticComplexity;
/**
* @param non-empty-string $name
* @param non-negative-int $startLine
* @param non-negative-int $endLine
* @param non-empty-string $signature
* @param positive-int $cyclomaticComplexity
*/
public function __construct(string $name, int $startLine, int $endLine, string $signature, Visibility $visibility, int $cyclomaticComplexity)
{
$this->name = $name;
$this->startLine = $startLine;
$this->endLine = $endLine;
$this->signature = $signature;
$this->visibility = $visibility;
$this->cyclomaticComplexity = $cyclomaticComplexity;
}
/**
* @return non-empty-string
*/
public function name(): string
{
return $this->name;
}
/**
* @return non-negative-int
*/
public function startLine(): int
{
return $this->startLine;
}
/**
* @return non-negative-int
*/
public function endLine(): int
{
return $this->endLine;
}
/**
* @return non-empty-string
*/
public function signature(): string
{
return $this->signature;
}
public function visibility(): Visibility
{
return $this->visibility;
}
/**
* @return positive-int
*/
public function cyclomaticComplexity(): int
{
return $this->cyclomaticComplexity;
}
}

View File

@@ -1,139 +0,0 @@
<?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\StaticAnalysis;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Trait_
{
/**
* @var non-empty-string
*/
private string $name;
/**
* @var non-empty-string
*/
private string $namespacedName;
private string $namespace;
/**
* @var non-empty-string
*/
private string $file;
/**
* @var non-negative-int
*/
private int $startLine;
/**
* @var non-negative-int
*/
private int $endLine;
/**
* @var list<non-empty-string>
*/
private array $traits;
/**
* @var array<non-empty-string, Method>
*/
private array $methods;
/**
* @param non-empty-string $name
* @param non-empty-string $namespacedName
* @param non-empty-string $file
* @param non-negative-int $startLine
* @param non-negative-int $endLine
* @param list<non-empty-string> $traits
* @param array<non-empty-string, Method> $methods
*/
public function __construct(string $name, string $namespacedName, string $namespace, string $file, int $startLine, int $endLine, array $traits, array $methods)
{
$this->name = $name;
$this->namespacedName = $namespacedName;
$this->namespace = $namespace;
$this->file = $file;
$this->startLine = $startLine;
$this->endLine = $endLine;
$this->traits = $traits;
$this->methods = $methods;
}
/**
* @return non-empty-string
*/
public function name(): string
{
return $this->name;
}
/**
* @return non-empty-string
*/
public function namespacedName(): string
{
return $this->namespacedName;
}
public function isNamespaced(): bool
{
return $this->namespace !== '';
}
public function namespace(): string
{
return $this->namespace;
}
/**
* @return non-empty-string
*/
public function file(): string
{
return $this->file;
}
/**
* @return non-negative-int
*/
public function startLine(): int
{
return $this->startLine;
}
/**
* @return non-negative-int
*/
public function endLine(): int
{
return $this->endLine;
}
/**
* @return list<non-empty-string>
*/
public function traits(): array
{
return $this->traits;
}
/**
* @return array<non-empty-string, Method>
*/
public function methods(): array
{
return $this->methods;
}
}

View File

@@ -1,20 +0,0 @@
<?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\StaticAnalysis;
/**
* @internal This enumeration is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
enum Visibility: string
{
case Public = 'public';
case Protected = 'protected';
case Private = 'private';
}

View File

@@ -1,68 +0,0 @@
<?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\Test\Target;
/**
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class Class_ extends Target
{
/**
* @var class-string
*/
private string $className;
/**
* @param class-string $className
*/
protected function __construct(string $className)
{
$this->className = $className;
}
public function isClass(): true
{
return true;
}
/**
* @return class-string
*/
public function className(): string
{
return $this->className;
}
/**
* @return non-empty-string
*/
public function key(): string
{
return 'classes';
}
/**
* @return non-empty-string
*/
public function target(): string
{
return $this->className;
}
/**
* @return non-empty-string
*/
public function description(): string
{
return 'Class ' . $this->target();
}
}

View File

@@ -1,68 +0,0 @@
<?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\Test\Target;
/**
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class ClassesThatExtendClass extends Target
{
/**
* @var class-string
*/
private string $className;
/**
* @param class-string $className
*/
protected function __construct(string $className)
{
$this->className = $className;
}
public function isClassesThatExtendClass(): true
{
return true;
}
/**
* @return class-string
*/
public function className(): string
{
return $this->className;
}
/**
* @return non-empty-string
*/
public function key(): string
{
return 'classesThatExtendClass';
}
/**
* @return non-empty-string
*/
public function target(): string
{
return $this->className;
}
/**
* @return non-empty-string
*/
public function description(): string
{
return 'Classes that extend class ' . $this->target();
}
}

View File

@@ -1,68 +0,0 @@
<?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\Test\Target;
/**
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class ClassesThatImplementInterface extends Target
{
/**
* @var class-string
*/
private string $interfaceName;
/**
* @param class-string $interfaceName
*/
protected function __construct(string $interfaceName)
{
$this->interfaceName = $interfaceName;
}
public function isClassesThatImplementInterface(): true
{
return true;
}
/**
* @return class-string
*/
public function interfaceName(): string
{
return $this->interfaceName;
}
/**
* @return non-empty-string
*/
public function key(): string
{
return 'classesThatImplementInterface';
}
/**
* @return non-empty-string
*/
public function target(): string
{
return $this->interfaceName;
}
/**
* @return non-empty-string
*/
public function description(): string
{
return 'Classes that implement interface ' . $this->target();
}
}

View File

@@ -1,68 +0,0 @@
<?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\Test\Target;
/**
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class Function_ extends Target
{
/**
* @var non-empty-string
*/
private string $functionName;
/**
* @param non-empty-string $functionName
*/
protected function __construct(string $functionName)
{
$this->functionName = $functionName;
}
public function isFunction(): true
{
return true;
}
/**
* @return non-empty-string
*/
public function functionName(): string
{
return $this->functionName;
}
/**
* @return non-empty-string
*/
public function key(): string
{
return 'functions';
}
/**
* @return non-empty-string
*/
public function target(): string
{
return $this->functionName;
}
/**
* @return non-empty-string
*/
public function description(): string
{
return 'Function ' . $this->target();
}
}

View File

@@ -1,249 +0,0 @@
<?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\Test\Target;
use function array_keys;
use function array_merge;
use function array_slice;
use function array_unique;
use function count;
use function explode;
use function implode;
use function range;
use SebastianBergmann\CodeCoverage\Filter;
use SebastianBergmann\CodeCoverage\StaticAnalysis\Class_;
use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser;
use SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_;
/**
* @phpstan-import-type TargetMap from Mapper
* @phpstan-import-type TargetMapPart from Mapper
*
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class MapBuilder
{
/**
* @return TargetMap
*/
public function build(Filter $filter, FileAnalyser $analyser): array
{
$namespaces = [];
$classes = [];
$classDetails = [];
$classesThatExtendClass = [];
$classesThatImplementInterface = [];
$traits = [];
$methods = [];
$functions = [];
$reverseLookup = [];
foreach ($filter->files() as $file) {
foreach ($analyser->traitsIn($file) as $trait) {
if ($trait->isNamespaced()) {
$this->processNamespace($namespaces, $trait->namespace(), $file, $trait->startLine(), $trait->endLine());
}
$this->process($traits, $trait->namespacedName(), $file, $trait->startLine(), $trait->endLine());
$this->processMethods($trait, $file, $methods, $reverseLookup);
}
}
foreach ($filter->files() as $file) {
foreach ($analyser->traitsIn($file) as $trait) {
foreach ($trait->traits() as $traitName) {
if (!isset($traits[$traitName])) {
continue;
}
$file = array_keys($traits[$traitName])[0];
if (!isset($traits[$trait->namespacedName()][$file])) {
$traits[$trait->namespacedName()][$file] = $traits[$traitName][$file];
continue;
}
$traits[$trait->namespacedName()][$file] = array_unique(
array_merge(
$traits[$trait->namespacedName()][$file],
$traits[$traitName][$file],
),
);
}
}
}
foreach ($filter->files() as $file) {
foreach ($analyser->interfacesIn($file) as $interface) {
$classesThatImplementInterface[$interface->namespacedName()] = [];
}
foreach ($analyser->classesIn($file) as $class) {
if ($class->isNamespaced()) {
$this->processNamespace($namespaces, $class->namespace(), $file, $class->startLine(), $class->endLine());
}
$this->process($classes, $class->namespacedName(), $file, $class->startLine(), $class->endLine());
foreach ($class->traits() as $traitName) {
if (!isset($traits[$traitName])) {
continue;
}
foreach ($traits[$traitName] as $file => $lines) {
if (!isset($classes[$class->namespacedName()][$file])) {
$classes[$class->namespacedName()][$file] = $lines;
continue;
}
$classes[$class->namespacedName()][$file] = array_unique(
array_merge(
$classes[$class->namespacedName()][$file],
$lines,
),
);
}
}
$this->processMethods($class, $file, $methods, $reverseLookup);
$classesThatExtendClass[$class->namespacedName()] = [];
$classDetails[] = $class;
}
foreach ($analyser->functionsIn($file) as $function) {
if ($function->isNamespaced()) {
$this->processNamespace($namespaces, $function->namespace(), $file, $function->startLine(), $function->endLine());
}
$this->process($functions, $function->namespacedName(), $file, $function->startLine(), $function->endLine());
foreach (range($function->startLine(), $function->endLine()) as $line) {
$reverseLookup[$file . ':' . $line] = $function->namespacedName();
}
}
}
foreach (array_keys($namespaces) as $namespace) {
foreach (array_keys($namespaces[$namespace]) as $file) {
$namespaces[$namespace][$file] = array_unique($namespaces[$namespace][$file]);
}
}
foreach ($classDetails as $class) {
foreach ($class->interfaces() as $interfaceName) {
if (!isset($classesThatImplementInterface[$interfaceName])) {
continue;
}
$this->process($classesThatImplementInterface, $interfaceName, $class->file(), $class->startLine(), $class->endLine());
}
if (!$class->hasParent()) {
continue;
}
if (!isset($classesThatExtendClass[$class->parentClass()])) {
continue;
}
$this->process($classesThatExtendClass, $class->parentClass(), $class->file(), $class->startLine(), $class->endLine());
}
foreach (array_keys($classesThatImplementInterface) as $className) {
if ($classesThatImplementInterface[$className] !== []) {
continue;
}
unset($classesThatImplementInterface[$className]);
}
foreach (array_keys($classesThatExtendClass) as $className) {
if ($classesThatExtendClass[$className] !== []) {
continue;
}
unset($classesThatExtendClass[$className]);
}
return [
'namespaces' => $namespaces,
'traits' => $traits,
'classes' => $classes,
'classesThatExtendClass' => $classesThatExtendClass,
'classesThatImplementInterface' => $classesThatImplementInterface,
'methods' => $methods,
'functions' => $functions,
'reverseLookup' => $reverseLookup,
];
}
private function processMethods(Class_|Trait_ $classOrTrait, string $file, array &$methods, array &$reverseLookup): void
{
foreach ($classOrTrait->methods() as $method) {
$methodName = $classOrTrait->namespacedName() . '::' . $method->name();
$this->process($methods, $methodName, $file, $method->startLine(), $method->endLine());
foreach (range($method->startLine(), $method->endLine()) as $line) {
$reverseLookup[$file . ':' . $line] = $methodName;
}
}
}
/**
* @param TargetMapPart $data
* @param non-empty-string $namespace
* @param non-empty-string $file
* @param positive-int $startLine
* @param positive-int $endLine
*
* @param-out TargetMapPart $data
*/
private function processNamespace(array &$data, string $namespace, string $file, int $startLine, int $endLine): void
{
$parts = explode('\\', $namespace);
foreach (range(1, count($parts)) as $i) {
$this->process($data, implode('\\', array_slice($parts, 0, $i)), $file, $startLine, $endLine);
}
}
/**
* @param TargetMapPart $data
* @param non-empty-string $unit
* @param non-empty-string $file
* @param positive-int $startLine
* @param positive-int $endLine
*
* @param-out TargetMapPart $data
*/
private function process(array &$data, string $unit, string $file, int $startLine, int $endLine): void
{
if (!isset($data[$unit])) {
$data[$unit] = [];
}
if (!isset($data[$unit][$file])) {
$data[$unit][$file] = [];
}
$data[$unit][$file] = array_merge(
$data[$unit][$file],
range($startLine, $endLine),
);
}
}

View File

@@ -1,93 +0,0 @@
<?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\Test\Target;
use function array_merge;
use function array_unique;
/**
* @phpstan-type TargetMap array{namespaces: TargetMapPart, traits: TargetMapPart, classes: TargetMapPart, classesThatExtendClass: TargetMapPart, classesThatImplementInterface: TargetMapPart, methods: TargetMapPart, functions: TargetMapPart, reverseLookup: ReverseLookup}
* @phpstan-type TargetMapPart array<non-empty-string, array<non-empty-string, list<positive-int>>>
* @phpstan-type ReverseLookup array<non-empty-string, non-empty-string>
*
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Mapper
{
/**
* @var TargetMap
*/
private array $map;
/**
* @param TargetMap $map
*/
public function __construct(array $map)
{
$this->map = $map;
}
/**
* @return array<non-empty-string, list<positive-int>>
*/
public function mapTargets(TargetCollection $targets): array
{
$result = [];
foreach ($targets as $target) {
foreach ($this->mapTarget($target) as $file => $lines) {
if (!isset($result[$file])) {
$result[$file] = $lines;
continue;
}
$result[$file] = array_unique(array_merge($result[$file], $lines));
}
}
return $result;
}
/**
* @throws InvalidCodeCoverageTargetException
*
* @return array<non-empty-string, list<positive-int>>
*/
public function mapTarget(Target $target): array
{
if (!isset($this->map[$target->key()][$target->target()])) {
throw new InvalidCodeCoverageTargetException($target);
}
return $this->map[$target->key()][$target->target()];
}
/**
* @param non-empty-string $file
* @param positive-int $line
*
* @return non-empty-string
*/
public function lookup(string $file, int $line): string
{
$key = $file . ':' . $line;
if (isset($this->map['reverseLookup'][$key])) {
return $this->map['reverseLookup'][$key];
}
return $key;
}
}

View File

@@ -1,83 +0,0 @@
<?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\Test\Target;
/**
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class Method extends Target
{
/**
* @var class-string
*/
private string $className;
/**
* @var non-empty-string
*/
private string $methodName;
/**
* @param class-string $className
* @param non-empty-string $methodName
*/
protected function __construct(string $className, string $methodName)
{
$this->className = $className;
$this->methodName = $methodName;
}
public function isMethod(): true
{
return true;
}
/**
* @return class-string
*/
public function className(): string
{
return $this->className;
}
/**
* @return non-empty-string
*/
public function methodName(): string
{
return $this->methodName;
}
/**
* @return non-empty-string
*/
public function key(): string
{
return 'methods';
}
/**
* @return non-empty-string
*/
public function target(): string
{
return $this->className . '::' . $this->methodName;
}
/**
* @return non-empty-string
*/
public function description(): string
{
return 'Method ' . $this->target();
}
}

View File

@@ -1,68 +0,0 @@
<?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\Test\Target;
/**
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class Namespace_ extends Target
{
/**
* @var non-empty-string
*/
private string $namespace;
/**
* @param non-empty-string $namespace
*/
protected function __construct(string $namespace)
{
$this->namespace = $namespace;
}
public function isNamespace(): true
{
return true;
}
/**
* @return non-empty-string
*/
public function namespace(): string
{
return $this->namespace;
}
/**
* @return non-empty-string
*/
public function key(): string
{
return 'namespaces';
}
/**
* @return non-empty-string
*/
public function target(): string
{
return $this->namespace;
}
/**
* @return non-empty-string
*/
public function description(): string
{
return 'Namespace ' . $this->target();
}
}

View File

@@ -1,125 +0,0 @@
<?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\Test\Target;
/**
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
abstract class Target
{
/**
* @param non-empty-string $namespace
*/
public static function forNamespace(string $namespace): Namespace_
{
return new Namespace_($namespace);
}
/**
* @param class-string $className
*/
public static function forClass(string $className): Class_
{
return new Class_($className);
}
/**
* @param class-string $className
* @param non-empty-string $methodName
*/
public static function forMethod(string $className, string $methodName): Method
{
return new Method($className, $methodName);
}
/**
* @param class-string $interfaceName
*/
public static function forClassesThatImplementInterface(string $interfaceName): ClassesThatImplementInterface
{
return new ClassesThatImplementInterface($interfaceName);
}
/**
* @param class-string $className
*/
public static function forClassesThatExtendClass(string $className): ClassesThatExtendClass
{
return new ClassesThatExtendClass($className);
}
/**
* @param non-empty-string $functionName
*/
public static function forFunction(string $functionName): Function_
{
return new Function_($functionName);
}
/**
* @param trait-string $traitName
*/
public static function forTrait(string $traitName): Trait_
{
return new Trait_($traitName);
}
public function isNamespace(): bool
{
return false;
}
public function isClass(): bool
{
return false;
}
public function isMethod(): bool
{
return false;
}
public function isClassesThatImplementInterface(): bool
{
return false;
}
public function isClassesThatExtendClass(): bool
{
return false;
}
public function isFunction(): bool
{
return false;
}
public function isTrait(): bool
{
return false;
}
/**
* @return non-empty-string
*/
abstract public function key(): string;
/**
* @return non-empty-string
*/
abstract public function target(): string;
/**
* @return non-empty-string
*/
abstract public function description(): string;
}

View File

@@ -1,70 +0,0 @@
<?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\Test\Target;
use function count;
use Countable;
use IteratorAggregate;
/**
* @template-implements IteratorAggregate<int, Target>
*
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class TargetCollection implements Countable, IteratorAggregate
{
/**
* @var list<Target>
*/
private array $targets;
/**
* @param list<Target> $targets
*/
public static function fromArray(array $targets): self
{
return new self(...$targets);
}
private function __construct(Target ...$targets)
{
$this->targets = $targets;
}
/**
* @return list<Target>
*/
public function asArray(): array
{
return $this->targets;
}
public function count(): int
{
return count($this->targets);
}
public function isEmpty(): bool
{
return $this->count() === 0;
}
public function isNotEmpty(): bool
{
return $this->count() > 0;
}
public function getIterator(): TargetCollectionIterator
{
return new TargetCollectionIterator($this);
}
}

View File

@@ -1,57 +0,0 @@
<?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\Test\Target;
use function count;
use Iterator;
/**
* @template-implements Iterator<int, Target>
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class TargetCollectionIterator implements Iterator
{
/**
* @var list<Target>
*/
private readonly array $targets;
private int $position = 0;
public function __construct(TargetCollection $metadata)
{
$this->targets = $metadata->asArray();
}
public function rewind(): void
{
$this->position = 0;
}
public function valid(): bool
{
return $this->position < count($this->targets);
}
public function key(): int
{
return $this->position;
}
public function current(): Target
{
return $this->targets[$this->position];
}
public function next(): void
{
$this->position++;
}
}

View File

@@ -1,39 +0,0 @@
<?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\Test\Target;
use function implode;
/**
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class TargetCollectionValidator
{
public function validate(Mapper $mapper, TargetCollection $targets): ValidationResult
{
$errors = [];
foreach ($targets as $target) {
try {
$mapper->mapTarget($target);
} catch (InvalidCodeCoverageTargetException $e) {
$errors[] = $e->getMessage();
}
}
if ($errors === []) {
return ValidationResult::success();
}
return ValidationResult::failure(implode("\n", $errors));
}
}

View File

@@ -1,68 +0,0 @@
<?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\Test\Target;
/**
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class Trait_ extends Target
{
/**
* @var trait-string
*/
private string $traitName;
/**
* @param trait-string $traitName
*/
protected function __construct(string $traitName)
{
$this->traitName = $traitName;
}
public function isTrait(): true
{
return true;
}
/**
* @return trait-string
*/
public function traitName(): string
{
return $this->traitName;
}
/**
* @return non-empty-string
*/
public function key(): string
{
return 'traits';
}
/**
* @return non-empty-string
*/
public function target(): string
{
return $this->traitName;
}
/**
* @return non-empty-string
*/
public function description(): string
{
return 'Trait ' . $this->target();
}
}

View File

@@ -1,46 +0,0 @@
<?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\Test\Target;
/**
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class ValidationFailure extends ValidationResult
{
/**
* @var non-empty-string
*/
private string $message;
/**
* @param non-empty-string $message
*
* @noinspection PhpMissingParentConstructorInspection
*/
protected function __construct(string $message)
{
$this->message = $message;
}
public function isFailure(): true
{
return true;
}
/**
* @return non-empty-string
*/
public function message(): string
{
return $this->message;
}
}

View File

@@ -1,51 +0,0 @@
<?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\Test\Target;
/**
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
abstract readonly class ValidationResult
{
public static function success(): ValidationSuccess
{
return new ValidationSuccess;
}
/**
* @param non-empty-string $message
*/
public static function failure(string $message): ValidationFailure
{
return new ValidationFailure($message);
}
protected function __construct()
{
}
/**
* @phpstan-assert-if-true ValidationSuccess $this
*/
public function isSuccess(): bool
{
return false;
}
/**
* @phpstan-assert-if-true ValidationFailure $this
*/
public function isFailure(): bool
{
return false;
}
}

View File

@@ -1,23 +0,0 @@
<?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\Test\Target;
/**
* @immutable
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class ValidationSuccess extends ValidationResult
{
public function isSuccess(): true
{
return true;
}
}

View File

@@ -14,10 +14,10 @@ use function sprintf;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Percentage
final class Percentage
{
private float $fraction;
private float $total;
private readonly float $fraction;
private readonly float $total;
public static function fromFractionAndTotal(float $fraction, float $total): self
{

View File

@@ -19,7 +19,7 @@ final class Version
public static function id(): string
{
if (self::$version === '') {
self::$version = (new VersionId('12.0', dirname(__DIR__)))->asString();
self::$version = (new VersionId('11.0.8', dirname(__DIR__)))->asString();
}
return self::$version;