diff --git a/.phive/phars.xml b/.phive/phars.xml
index 1a5c004bd..4a2513ca5 100644
--- a/.phive/phars.xml
+++ b/.phive/phars.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/.psalm/baseline.xml b/.psalm/baseline.xml
index 79cae8b80..fbda6cd5c 100644
--- a/.psalm/baseline.xml
+++ b/.psalm/baseline.xml
@@ -1,5 +1,5 @@
-
+
$unit[0]
@@ -19,9 +19,9 @@
isLarge
isMedium
-
+
is_array($linesToBeCovered)
-
+
include_once $uncoveredFile
@@ -77,9 +77,9 @@
-
- $pointer[$path[$i] . $type]
-
+
+ $pointer = &$pointer[$path[$i] . $type]
+
@@ -87,6 +87,9 @@
$this->functions === null
$this->traits === null
+
+ IteratorAggregate
+
$classes
$functions
@@ -96,6 +99,10 @@
$this->directories
$this->files
+
+ $this->directories[] = &$this->children[count($this->children) - 1]
+ $this->files[] = &$this->children[count($this->children) - 1]
+
@@ -122,6 +129,9 @@
$this->nodes[$this->position]
+
+ RecursiveIterator
+
$position
@@ -130,8 +140,10 @@
$this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit']
-
+
$this->functionCoverage[$file][$functionName]['branches']
+ $this->functionCoverage[$file][$functionName]['branches'][$branchId]
+ $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit']
$this->functionCoverage[$file][$functionName]['branches']
diff --git a/ChangeLog.md b/ChangeLog.md
index c972cf032..5c774128a 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -2,7 +2,13 @@
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
-## [9.2.19] - 2022-MM-DD
+## [9.2.20] - 2022-MM-DD
+
+### Fixed
+
+* [#960](https://github.com/sebastianbergmann/php-code-coverage/issues/960): New body font-size is way too big
+
+## [9.2.19] - 2022-11-18
### Fixed
@@ -433,7 +439,8 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt
* This component is no longer supported on PHP 7.1
-[9.2.19]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.18...9.2
+[9.2.20]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.19...9.2
+[9.2.19]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.18...9.2.19
[9.2.18]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.17...9.2.18
[9.2.17]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.16...9.2.17
[9.2.16]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.15...9.2.16
diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php
index 747f95af3..a7ae0ccb0 100644
--- a/src/CodeCoverage.php
+++ b/src/CodeCoverage.php
@@ -643,7 +643,7 @@ private function processUnintentionallyCoveredUnits(array $unintentionallyCovere
} catch (\ReflectionException $e) {
throw new ReflectionException(
$e->getMessage(),
- (int) $e->getCode(),
+ $e->getCode(),
$e
);
}
diff --git a/src/Report/Cobertura.php b/src/Report/Cobertura.php
index 0d1dde760..67a050b71 100644
--- a/src/Report/Cobertura.php
+++ b/src/Report/Cobertura.php
@@ -9,18 +9,11 @@
*/
namespace SebastianBergmann\CodeCoverage\Report;
-use function basename;
-use function count;
use function dirname;
use function file_put_contents;
-use function preg_match;
-use function range;
-use function str_replace;
-use function time;
-use DOMImplementation;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException;
-use SebastianBergmann\CodeCoverage\Node\File;
+use SebastianBergmann\CodeCoverage\Report\Cobertura\CoberturaCoverage;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
final class Cobertura
@@ -30,268 +23,7 @@ final class Cobertura
*/
public function process(CodeCoverage $coverage, ?string $target = null): string
{
- $time = (string) time();
-
- $report = $coverage->getReport();
-
- $implementation = new DOMImplementation;
-
- $documentType = $implementation->createDocumentType(
- 'coverage',
- '',
- 'http://cobertura.sourceforge.net/xml/coverage-04.dtd'
- );
-
- $document = $implementation->createDocument('', '', $documentType);
- $document->xmlVersion = '1.0';
- $document->encoding = 'UTF-8';
- $document->formatOutput = true;
-
- $coverageElement = $document->createElement('coverage');
-
- $linesValid = $report->numberOfExecutableLines();
- $linesCovered = $report->numberOfExecutedLines();
- $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
- $coverageElement->setAttribute('line-rate', (string) $lineRate);
-
- $branchesValid = $report->numberOfExecutableBranches();
- $branchesCovered = $report->numberOfExecutedBranches();
- $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
- $coverageElement->setAttribute('branch-rate', (string) $branchRate);
-
- $coverageElement->setAttribute('lines-covered', (string) $report->numberOfExecutedLines());
- $coverageElement->setAttribute('lines-valid', (string) $report->numberOfExecutableLines());
- $coverageElement->setAttribute('branches-covered', (string) $report->numberOfExecutedBranches());
- $coverageElement->setAttribute('branches-valid', (string) $report->numberOfExecutableBranches());
- $coverageElement->setAttribute('complexity', '');
- $coverageElement->setAttribute('version', '0.4');
- $coverageElement->setAttribute('timestamp', $time);
-
- $document->appendChild($coverageElement);
-
- $sourcesElement = $document->createElement('sources');
- $coverageElement->appendChild($sourcesElement);
-
- $sourceElement = $document->createElement('source', $report->pathAsString());
- $sourcesElement->appendChild($sourceElement);
-
- $packagesElement = $document->createElement('packages');
- $coverageElement->appendChild($packagesElement);
-
- $complexity = 0;
-
- foreach ($report as $item) {
- if (!$item instanceof File) {
- continue;
- }
-
- $packageElement = $document->createElement('package');
- $packageComplexity = 0;
-
- $packageElement->setAttribute('name', str_replace($report->pathAsString() . DIRECTORY_SEPARATOR, '', $item->pathAsString()));
-
- $linesValid = $item->numberOfExecutableLines();
- $linesCovered = $item->numberOfExecutedLines();
- $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
-
- $packageElement->setAttribute('line-rate', (string) $lineRate);
-
- $branchesValid = $item->numberOfExecutableBranches();
- $branchesCovered = $item->numberOfExecutedBranches();
- $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
-
- $packageElement->setAttribute('branch-rate', (string) $branchRate);
-
- $packageElement->setAttribute('complexity', '');
- $packagesElement->appendChild($packageElement);
-
- $classesElement = $document->createElement('classes');
-
- $packageElement->appendChild($classesElement);
-
- $classes = $item->classesAndTraits();
- $coverageData = $item->lineCoverageData();
-
- foreach ($classes as $className => $class) {
- $complexity += $class['ccn'];
- $packageComplexity += $class['ccn'];
-
- if (!empty($class['package']['namespace'])) {
- $className = $class['package']['namespace'] . '\\' . $className;
- }
-
- $linesValid = $class['executableLines'];
- $linesCovered = $class['executedLines'];
- $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
-
- $branchesValid = $class['executableBranches'];
- $branchesCovered = $class['executedBranches'];
- $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
-
- $classElement = $document->createElement('class');
-
- $classElement->setAttribute('name', $className);
- $classElement->setAttribute('filename', str_replace($report->pathAsString() . DIRECTORY_SEPARATOR, '', $item->pathAsString()));
- $classElement->setAttribute('line-rate', (string) $lineRate);
- $classElement->setAttribute('branch-rate', (string) $branchRate);
- $classElement->setAttribute('complexity', (string) $class['ccn']);
-
- $classesElement->appendChild($classElement);
-
- $methodsElement = $document->createElement('methods');
-
- $classElement->appendChild($methodsElement);
-
- $classLinesElement = $document->createElement('lines');
-
- $classElement->appendChild($classLinesElement);
-
- foreach ($class['methods'] as $methodName => $method) {
- if ($method['executableLines'] === 0) {
- continue;
- }
-
- preg_match("/\((.*?)\)/", $method['signature'], $signature);
-
- $linesValid = $method['executableLines'];
- $linesCovered = $method['executedLines'];
- $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
-
- $branchesValid = $method['executableBranches'];
- $branchesCovered = $method['executedBranches'];
- $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
-
- $methodElement = $document->createElement('method');
-
- $methodElement->setAttribute('name', $methodName);
- $methodElement->setAttribute('signature', $signature[1]);
- $methodElement->setAttribute('line-rate', (string) $lineRate);
- $methodElement->setAttribute('branch-rate', (string) $branchRate);
- $methodElement->setAttribute('complexity', (string) $method['ccn']);
-
- $methodLinesElement = $document->createElement('lines');
-
- $methodElement->appendChild($methodLinesElement);
-
- foreach (range($method['startLine'], $method['endLine']) as $line) {
- if (!isset($coverageData[$line]) || $coverageData[$line] === null) {
- continue;
- }
- $methodLineElement = $document->createElement('line');
-
- $methodLineElement->setAttribute('number', (string) $line);
- $methodLineElement->setAttribute('hits', (string) count($coverageData[$line]));
-
- $methodLinesElement->appendChild($methodLineElement);
-
- $classLineElement = $methodLineElement->cloneNode();
-
- $classLinesElement->appendChild($classLineElement);
- }
-
- $methodsElement->appendChild($methodElement);
- }
- }
-
- if ($report->numberOfFunctions() === 0) {
- $packageElement->setAttribute('complexity', (string) $packageComplexity);
-
- continue;
- }
-
- $functionsComplexity = 0;
- $functionsLinesValid = 0;
- $functionsLinesCovered = 0;
- $functionsBranchesValid = 0;
- $functionsBranchesCovered = 0;
-
- $classElement = $document->createElement('class');
- $classElement->setAttribute('name', basename($item->pathAsString()));
- $classElement->setAttribute('filename', str_replace($report->pathAsString() . DIRECTORY_SEPARATOR, '', $item->pathAsString()));
-
- $methodsElement = $document->createElement('methods');
-
- $classElement->appendChild($methodsElement);
-
- $classLinesElement = $document->createElement('lines');
-
- $classElement->appendChild($classLinesElement);
-
- $functions = $report->functions();
-
- foreach ($functions as $functionName => $function) {
- if ($function['executableLines'] === 0) {
- continue;
- }
-
- $complexity += $function['ccn'];
- $packageComplexity += $function['ccn'];
- $functionsComplexity += $function['ccn'];
-
- $linesValid = $function['executableLines'];
- $linesCovered = $function['executedLines'];
- $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
-
- $functionsLinesValid += $linesValid;
- $functionsLinesCovered += $linesCovered;
-
- $branchesValid = $function['executableBranches'];
- $branchesCovered = $function['executedBranches'];
- $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
-
- $functionsBranchesValid += $branchesValid;
- $functionsBranchesCovered += $branchesValid;
-
- $methodElement = $document->createElement('method');
-
- $methodElement->setAttribute('name', $functionName);
- $methodElement->setAttribute('signature', $function['signature']);
- $methodElement->setAttribute('line-rate', (string) $lineRate);
- $methodElement->setAttribute('branch-rate', (string) $branchRate);
- $methodElement->setAttribute('complexity', (string) $function['ccn']);
-
- $methodLinesElement = $document->createElement('lines');
-
- $methodElement->appendChild($methodLinesElement);
-
- foreach (range($function['startLine'], $function['endLine']) as $line) {
- if (!isset($coverageData[$line]) || $coverageData[$line] === null) {
- continue;
- }
- $methodLineElement = $document->createElement('line');
-
- $methodLineElement->setAttribute('number', (string) $line);
- $methodLineElement->setAttribute('hits', (string) count($coverageData[$line]));
-
- $methodLinesElement->appendChild($methodLineElement);
-
- $classLineElement = $methodLineElement->cloneNode();
-
- $classLinesElement->appendChild($classLineElement);
- }
-
- $methodsElement->appendChild($methodElement);
- }
-
- $packageElement->setAttribute('complexity', (string) $packageComplexity);
-
- if ($functionsLinesValid === 0) {
- continue;
- }
-
- $lineRate = $functionsLinesCovered / $functionsLinesValid;
- $branchRate = $functionsBranchesValid === 0 ? 0 : ($functionsBranchesCovered / $functionsBranchesValid);
-
- $classElement->setAttribute('line-rate', (string) $lineRate);
- $classElement->setAttribute('branch-rate', (string) $branchRate);
- $classElement->setAttribute('complexity', (string) $functionsComplexity);
-
- $classesElement->appendChild($classElement);
- }
-
- $coverageElement->setAttribute('complexity', (string) $complexity);
-
- $buffer = $document->saveXML();
+ $buffer = CoberturaCoverage::create($coverage->getReport())->generateDocument()->saveXML();
if ($target !== null) {
Filesystem::createDirectory(dirname($target));
diff --git a/src/Report/Cobertura/CoberturaClass.php b/src/Report/Cobertura/CoberturaClass.php
new file mode 100644
index 000000000..c08fc8eab
--- /dev/null
+++ b/src/Report/Cobertura/CoberturaClass.php
@@ -0,0 +1,164 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\Report\Cobertura;
+
+use function array_merge;
+use function range;
+use DOMDocument;
+use DOMElement;
+
+class CoberturaClass extends CoberturaElement
+{
+ /** @var CoberturaMethod[] */
+ private $methods = [];
+
+ /** @var CoberturaLine[] */
+ private $lines = [];
+
+ /**
+ * @var string
+ */
+ private $name;
+
+ /**
+ * @var string
+ */
+ private $filename;
+
+ /**
+ * @var float
+ */
+ private $complexity;
+
+ public static function create(string $className, string $relativeFilePath, array $classData, array $lineCoverageData): self
+ {
+ if (!empty($classData['package']['namespace'])) {
+ $className = $classData['package']['namespace'] . '\\' . $className;
+ }
+
+ $class = new self(
+ $className,
+ $relativeFilePath,
+ $classData['executableLines'],
+ $classData['executedLines'],
+ $classData['executableBranches'],
+ $classData['executedBranches'],
+ $classData['ccn']
+ );
+
+ $endLine = $classData['startLine'];
+
+ foreach ($classData['methods'] as $methodName => $methodData) {
+ $method = CoberturaMethod::create($methodName, $methodData, $lineCoverageData);
+
+ if (null !== $method) {
+ $class->methods[] = $method;
+ }
+
+ if ($methodData['endLine'] > $endLine) {
+ $endLine = $methodData['endLine'];
+ }
+ }
+
+ /** @var int $lineNumber */
+ foreach (range($classData['startLine'], $endLine) as $lineNumber) {
+ $line = CoberturaLine::create($lineNumber, $lineCoverageData);
+
+ if (null !== $line) {
+ $class->lines[] = $line;
+ }
+ }
+
+ return $class;
+ }
+
+ public static function createForFunctions(
+ string $className,
+ string $relativeFilePath,
+ int $linesValid,
+ int $linesCovered,
+ int $branchesValid,
+ int $branchesCovered,
+ float $complexity,
+ array $functions
+ ): self {
+ $class = new self(
+ $className,
+ $relativeFilePath,
+ $linesValid,
+ $linesCovered,
+ $branchesValid,
+ $branchesCovered,
+ $complexity
+ );
+
+ $class->methods = $functions;
+
+ foreach ($class->methods as $method) {
+ $class->lines = array_merge($class->lines, $method->getLines());
+ }
+
+ return $class;
+ }
+
+ private function __construct(
+ string $name,
+ string $filename,
+ int $linesValid,
+ int $linesCovered,
+ int $branchesValid,
+ int $branchesCovered,
+ float $complexity
+ ) {
+ $this->name = $name;
+ $this->filename = $filename;
+ $this->complexity = $complexity;
+ parent::__construct($linesValid, $linesCovered, $branchesValid, $branchesCovered);
+ }
+
+ public function wrap(DOMDocument $document): DOMElement
+ {
+ $classElement = $document->createElement('class');
+
+ $classElement->setAttribute('name', $this->name);
+ $classElement->setAttribute('filename', $this->filename);
+ $classElement->setAttribute('line-rate', (string) $this->lineRate());
+ $classElement->setAttribute('branch-rate', (string) $this->branchRate());
+ $classElement->setAttribute('complexity', (string) $this->complexity);
+
+ $methodsElement = $document->createElement('methods');
+
+ foreach ($this->methods as $method) {
+ $methodsElement->appendChild($method->wrap($document));
+ }
+
+ $classElement->appendChild($methodsElement);
+
+ $linesElement = $document->createElement('lines');
+
+ foreach ($this->lines as $line) {
+ $linesElement->appendChild($line->wrap($document));
+ }
+
+ $classElement->appendChild($linesElement);
+
+ return $classElement;
+ }
+
+ public function getComplexity(): float
+ {
+ return $this->complexity;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+}
diff --git a/src/Report/Cobertura/CoberturaCoverage.php b/src/Report/Cobertura/CoberturaCoverage.php
new file mode 100644
index 000000000..ad17061b9
--- /dev/null
+++ b/src/Report/Cobertura/CoberturaCoverage.php
@@ -0,0 +1,246 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\Report\Cobertura;
+
+use function array_reduce;
+use function basename;
+use function count;
+use function date;
+use function getcwd;
+use function in_array;
+use function sprintf;
+use function str_replace;
+use function time;
+use Composer\InstalledVersions;
+use DOMDocument;
+use DOMElement;
+use DOMImplementation;
+use SebastianBergmann\CodeCoverage\Node\AbstractNode;
+use SebastianBergmann\CodeCoverage\Node\Directory;
+use SebastianBergmann\CodeCoverage\Node\File;
+
+class CoberturaCoverage extends CoberturaElement
+{
+ private const FUNCTIONS_PACKAGE = '_functions';
+
+ /** @var string[] */
+ private $sources = [];
+
+ /** @var array */
+ private $packages = [];
+
+ /**
+ * @var int
+ */
+ private $timestamp;
+
+ public static function create(Directory $report): self
+ {
+ $coverage = new self(
+ time(),
+ $report->numberOfExecutableLines(),
+ $report->numberOfExecutedLines(),
+ $report->numberOfExecutableBranches(),
+ $report->numberOfExecutedBranches()
+ );
+
+ foreach ($report as $item) {
+ if (!$item instanceof File) {
+ continue;
+ }
+
+ $coverage->processFile($item);
+ }
+
+ return $coverage;
+ }
+
+ private function __construct(
+ int $timestamp,
+ int $linesValid,
+ int $linesCovered,
+ int $branchesValid,
+ int $branchesCovered
+ ) {
+ $this->timestamp = $timestamp;
+ parent::__construct($linesValid, $linesCovered, $branchesValid, $branchesCovered);
+ }
+
+ public function generateDocument(): DOMDocument
+ {
+ $implementation = new DOMImplementation;
+
+ $documentType = $implementation->createDocumentType(
+ 'coverage',
+ '',
+ 'http://cobertura.sourceforge.net/xml/coverage-04.dtd'
+ );
+
+ $document = $implementation->createDocument('', '', $documentType);
+ $document->xmlVersion = '1.0';
+ $document->encoding = 'UTF-8';
+ $document->formatOutput = true;
+
+ $comment = $document->createComment(sprintf(
+ 'Cobertura coverage report generated by the PHP library "%s" on %s.',
+ InstalledVersions::getRootPackage()['name'],
+ date('c', $this->timestamp),
+ ));
+ $document->appendChild($comment);
+
+ $coverageElement = $document->createElement('coverage');
+ $coverageElement->setAttribute('line-rate', (string) $this->lineRate());
+ $coverageElement->setAttribute('branch-rate', (string) $this->branchRate());
+ $coverageElement->setAttribute('lines-covered', (string) $this->linesCovered);
+ $coverageElement->setAttribute('lines-valid', (string) $this->linesValid);
+ $coverageElement->setAttribute('branches-covered', (string) $this->branchesCovered);
+ $coverageElement->setAttribute('branches-valid', (string) $this->branchesValid);
+ $coverageElement->setAttribute('complexity', (string) $this->complexity());
+ $coverageElement->setAttribute('version', '0.4');
+ $coverageElement->setAttribute('timestamp', (string) $this->timestamp);
+
+ $coverageElement->appendChild($this->wrapSources($document));
+
+ $packagesElement = $document->createElement('packages');
+
+ foreach ($this->packages as $package) {
+ $packagesElement->appendChild($package->wrap($document));
+ }
+
+ $coverageElement->appendChild($packagesElement);
+
+ $document->appendChild($coverageElement);
+
+ return $document;
+ }
+
+ private function processFile(File $file): void
+ {
+ $this->addSource($this->relativePath($this->fileRoot($file)->pathAsString()));
+
+ $lineCoverageData = $file->lineCoverageData();
+
+ foreach ($file->classesAndTraits() as $className => $classData) {
+ $class = CoberturaClass::create(
+ $className,
+ $this->relativePath($file->pathAsString()),
+ $classData,
+ $lineCoverageData
+ );
+
+ $packageName = CoberturaPackage::packageName($class->getName());
+
+ if (!isset($this->packages[$packageName])) {
+ $this->packages[$packageName] = new CoberturaPackage($packageName);
+ }
+
+ $this->packages[$packageName]->addClass($class);
+ }
+
+ $this->processFunctions($file);
+ }
+
+ private function processFunctions(File $file): void
+ {
+ $lineCoverageData = $file->lineCoverageData();
+
+ $functions = [];
+ $classComplexity = 0;
+
+ foreach ($file->functions() as $functionName => $functionData) {
+ $method = CoberturaMethod::create($functionName, $functionData, $lineCoverageData);
+
+ if (null !== $method) {
+ $functions[$functionName] = $method;
+ $classComplexity += $functionData['ccn'];
+ }
+ }
+
+ if (count($functions) > 0) {
+ $classCoverageData = array_reduce($functions, static function (array $data, CoberturaMethod $function)
+ {
+ $data['linesValid'] += $function->getLinesValid();
+ $data['linesCovered'] += $function->getLinesCovered();
+ $data['branchesValid'] += $function->getBranchesValid();
+ $data['branchesCovered'] += $function->getBranchesCovered();
+
+ return $data;
+ }, ['linesValid' => 0, 'linesCovered' => 0, 'branchesValid' => 0, 'branchesCovered' => 0]);
+
+ $relativeFilePath = $this->relativePath($file->pathAsString());
+
+ $class = CoberturaClass::createForFunctions(
+ self::FUNCTIONS_PACKAGE . '\\' . basename($relativeFilePath),
+ $relativeFilePath,
+ $classCoverageData['linesValid'],
+ $classCoverageData['linesCovered'],
+ $classCoverageData['branchesValid'],
+ $classCoverageData['branchesCovered'],
+ $classComplexity,
+ $functions
+ );
+
+ if (!isset($this->packages[self::FUNCTIONS_PACKAGE])) {
+ $this->packages[self::FUNCTIONS_PACKAGE] = new CoberturaPackage(self::FUNCTIONS_PACKAGE);
+ }
+
+ $this->packages[self::FUNCTIONS_PACKAGE]->addClass($class);
+ }
+ }
+
+ private function addSource(string $source): void
+ {
+ if (!in_array($source, $this->sources, true)) {
+ $this->sources[] = $source;
+ }
+ }
+
+ private function fileRoot(File $file): AbstractNode
+ {
+ $root = $file;
+
+ while (true) {
+ if ($root->parent() === null) {
+ return $root;
+ }
+
+ /** @var AbstractNode $root */
+ $root = $root->parent();
+ }
+ }
+
+ private function relativePath(string $path): string
+ {
+ return str_replace(getcwd() . DIRECTORY_SEPARATOR, '', $path);
+ }
+
+ private function wrapSources(DOMDocument $document): DOMElement
+ {
+ $sourcesElement = $document->createElement('sources');
+
+ foreach ($this->sources as $source) {
+ $sourcesElement->appendChild($document->createElement('source', $source));
+ }
+
+ return $sourcesElement;
+ }
+
+ private function complexity(): float
+ {
+ return array_reduce(
+ $this->packages,
+ static function (float $complexity, CoberturaPackage $package)
+ {
+ return $complexity + $package->complexity();
+ },
+ 0
+ );
+ }
+}
diff --git a/src/Report/Cobertura/CoberturaElement.php b/src/Report/Cobertura/CoberturaElement.php
new file mode 100644
index 000000000..72c0f473a
--- /dev/null
+++ b/src/Report/Cobertura/CoberturaElement.php
@@ -0,0 +1,71 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\Report\Cobertura;
+
+abstract class CoberturaElement
+{
+ /**
+ * @var int
+ */
+ protected $linesValid;
+
+ /**
+ * @var int
+ */
+ protected $linesCovered;
+
+ /**
+ * @var int
+ */
+ protected $branchesValid;
+
+ /**
+ * @var int
+ */
+ protected $branchesCovered;
+
+ public function __construct(int $linesValid, int $linesCovered, int $branchesValid, int $branchesCovered)
+ {
+ $this->linesValid = $linesValid;
+ $this->linesCovered = $linesCovered;
+ $this->branchesValid = $branchesValid;
+ $this->branchesCovered = $branchesCovered;
+ }
+
+ public function getLinesValid(): int
+ {
+ return $this->linesValid;
+ }
+
+ public function getLinesCovered(): int
+ {
+ return $this->linesCovered;
+ }
+
+ public function getBranchesValid(): int
+ {
+ return $this->branchesValid;
+ }
+
+ public function getBranchesCovered(): int
+ {
+ return $this->branchesCovered;
+ }
+
+ protected function lineRate(): float
+ {
+ return $this->linesValid === 0 ? 0 : $this->linesCovered / $this->linesValid;
+ }
+
+ protected function branchRate(): float
+ {
+ return $this->branchesValid === 0 ? 0 : $this->branchesCovered / $this->branchesValid;
+ }
+}
diff --git a/src/Report/Cobertura/CoberturaLine.php b/src/Report/Cobertura/CoberturaLine.php
new file mode 100644
index 000000000..bc31c02bf
--- /dev/null
+++ b/src/Report/Cobertura/CoberturaLine.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\Report\Cobertura;
+
+use function count;
+use DOMDocument;
+use DOMElement;
+
+class CoberturaLine
+{
+ /**
+ * @var int
+ */
+ private $number;
+
+ /**
+ * @var int
+ */
+ private $hits;
+
+ /**
+ * @var null|bool
+ */
+ private $branch;
+
+ public static function create(int $lineNumber, array $lineCoverageData): ?self
+ {
+ if (!isset($lineCoverageData[$lineNumber])) {
+ return null;
+ }
+
+ return new self($lineNumber, count($lineCoverageData[$lineNumber]));
+ }
+
+ private function __construct(int $number, int $hits, ?bool $branch = null)
+ {
+ $this->number = $number;
+ $this->hits = $hits;
+ $this->branch = $branch;
+ }
+
+ public function wrap(DOMDocument $document): DOMElement
+ {
+ $element = $document->createElement('line');
+
+ $element->setAttribute('number', (string) $this->number);
+ $element->setAttribute('hits', (string) $this->hits);
+
+ if (null !== $this->branch) {
+ $element->setAttribute('branch', $this->branch ? 'true' : 'false');
+ }
+
+ return $element;
+ }
+}
diff --git a/src/Report/Cobertura/CoberturaMethod.php b/src/Report/Cobertura/CoberturaMethod.php
new file mode 100644
index 000000000..cc0acfaf4
--- /dev/null
+++ b/src/Report/Cobertura/CoberturaMethod.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\Report\Cobertura;
+
+use function range;
+use DOMDocument;
+use DOMElement;
+
+class CoberturaMethod extends CoberturaElement
+{
+ /** @var CoberturaLine[] */
+ private $lines = [];
+
+ /**
+ * @var string
+ */
+ private $name;
+
+ /**
+ * @var string
+ */
+ private $signature;
+
+ /**
+ * @var float
+ */
+ private $complexity;
+
+ public static function create(string $name, array $methodData, array $lineCoverageData): ?self
+ {
+ if ($methodData['executableLines'] === 0) {
+ return null;
+ }
+
+ $method = new self(
+ $name,
+ $methodData['signature'],
+ $methodData['executableLines'],
+ $methodData['executedLines'],
+ $methodData['executableBranches'],
+ $methodData['executedBranches'],
+ $methodData['ccn']
+ );
+
+ /** @var int $lineNumber */
+ foreach (range($methodData['startLine'], $methodData['endLine']) as $lineNumber) {
+ $line = CoberturaLine::create($lineNumber, $lineCoverageData);
+
+ if (null !== $line) {
+ $method->lines[] = $line;
+ }
+ }
+
+ return $method;
+ }
+
+ private function __construct(
+ string $name,
+ string $signature,
+ int $linesValid,
+ int $linesCovered,
+ int $branchesValid,
+ int $branchesCovered,
+ float $complexity
+ ) {
+ $this->name = $name;
+ $this->signature = $signature;
+ $this->complexity = $complexity;
+ parent::__construct($linesValid, $linesCovered, $branchesValid, $branchesCovered);
+ }
+
+ public function wrap(DOMDocument $document): DOMElement
+ {
+ $methodElement = $document->createElement('method');
+
+ $methodElement->setAttribute('name', $this->name);
+ $methodElement->setAttribute('signature', $this->signature);
+ $methodElement->setAttribute('line-rate', (string) $this->lineRate());
+ $methodElement->setAttribute('branch-rate', (string) $this->branchRate());
+ $methodElement->setAttribute('complexity', (string) $this->complexity);
+
+ $linesElement = $document->createElement('lines');
+
+ foreach ($this->lines as $line) {
+ $linesElement->appendChild($line->wrap($document));
+ }
+
+ $methodElement->appendChild($linesElement);
+
+ return $methodElement;
+ }
+
+ public function getLines(): array
+ {
+ return $this->lines;
+ }
+}
diff --git a/src/Report/Cobertura/CoberturaPackage.php b/src/Report/Cobertura/CoberturaPackage.php
new file mode 100644
index 000000000..c204ded8a
--- /dev/null
+++ b/src/Report/Cobertura/CoberturaPackage.php
@@ -0,0 +1,99 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\Report\Cobertura;
+
+use function array_reduce;
+use function explode;
+use DOMDocument;
+use DOMElement;
+
+class CoberturaPackage
+{
+ /** @var CoberturaClass[] */
+ private $classes = [];
+
+ /**
+ * @var string
+ */
+ private $name;
+
+ public static function packageName(string $className): string
+ {
+ return explode('\\', $className)[0];
+ }
+
+ public function __construct(string $name)
+ {
+ $this->name = $name;
+ }
+
+ public function addClass(CoberturaClass $class): void
+ {
+ $this->classes[] = $class;
+ }
+
+ public function wrap(DOMDocument $document): DOMElement
+ {
+ $packageElement = $document->createElement('package');
+
+ $packageElement->setAttribute('name', $this->name);
+ $packageElement->setAttribute('line-rate', (string) $this->lineRate());
+ $packageElement->setAttribute('branch-rate', (string) $this->branchRate());
+ $packageElement->setAttribute('complexity', (string) $this->complexity());
+
+ $classesElement = $document->createElement('classes');
+
+ foreach ($this->classes as $class) {
+ $classesElement->appendChild($class->wrap($document));
+ }
+
+ $packageElement->appendChild($classesElement);
+
+ return $packageElement;
+ }
+
+ public function complexity(): float
+ {
+ return array_reduce(
+ $this->classes,
+ static function (float $complexity, CoberturaClass $class)
+ {
+ return $complexity + $class->getComplexity();
+ },
+ 0
+ );
+ }
+
+ private function lineRate(): float
+ {
+ $linesData = array_reduce($this->classes, static function (array $data, CoberturaClass $class)
+ {
+ $data['valid'] += $class->getLinesValid();
+ $data['covered'] += $class->getLinesCovered();
+
+ return $data;
+ }, ['valid' => 0, 'covered' => 0]);
+
+ return $linesData['valid'] === 0 ? 0 : $linesData['covered'] / $linesData['valid'];
+ }
+
+ private function branchRate(): float
+ {
+ $branchesData = array_reduce($this->classes, static function (array $data, CoberturaClass $class)
+ {
+ $data['valid'] += $class->getBranchesValid();
+ $data['covered'] += $class->getBranchesCovered();
+
+ return $data;
+ }, ['valid' => 0, 'covered' => 0]);
+
+ return $branchesData['valid'] === 0 ? 0 : $branchesData['covered'] / $branchesData['valid'];
+ }
+}
diff --git a/src/Report/Html/Renderer/Template/css/style.css b/src/Report/Html/Renderer/Template/css/style.css
index a3fd9f7a7..5dc62ccf3 100644
--- a/src/Report/Html/Renderer/Template/css/style.css
+++ b/src/Report/Html/Renderer/Template/css/style.css
@@ -1,6 +1,6 @@
body {
font-family: sans-serif;
- font-size: 2em;
+ font-size: 1em;
font-kerning: normal;
font-variant-ligatures: common-ligatures;
text-rendering: optimizeLegibility;
diff --git a/src/StaticAnalysis/ParsingFileAnalyser.php b/src/StaticAnalysis/ParsingFileAnalyser.php
index 8792cf3d9..0e221a069 100644
--- a/src/StaticAnalysis/ParsingFileAnalyser.php
+++ b/src/StaticAnalysis/ParsingFileAnalyser.php
@@ -174,7 +174,7 @@ private function analyse(string $filename): void
$filename,
$error->getMessage()
),
- (int) $error->getCode(),
+ $error->getCode(),
$error
);
}
diff --git a/src/Version.php b/src/Version.php
index 338f6c2fe..1b778649f 100644
--- a/src/Version.php
+++ b/src/Version.php
@@ -22,7 +22,7 @@ final class Version
public static function id(): string
{
if (self::$version === null) {
- self::$version = (new VersionId('9.2.18', dirname(__DIR__)))->getVersion();
+ self::$version = (new VersionId('9.2.19', dirname(__DIR__)))->getVersion();
}
return self::$version;
diff --git a/tests/_files/BankAccount-cobertura-line.xml b/tests/_files/BankAccount-cobertura-line.xml
index 75401a6cb..85dcfaa8c 100644
--- a/tests/_files/BankAccount-cobertura-line.xml
+++ b/tests/_files/BankAccount-cobertura-line.xml
@@ -1,33 +1,34 @@
+
- %s
+ tests%e_files
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/tests/_files/BankAccount-cobertura-path.xml b/tests/_files/BankAccount-cobertura-path.xml
index 9ce9efe6e..fecd9e63b 100644
--- a/tests/_files/BankAccount-cobertura-path.xml
+++ b/tests/_files/BankAccount-cobertura-path.xml
@@ -1,33 +1,34 @@
+
- %s
+ tests%e_files
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/tests/_files/class-with-anonymous-function-cobertura.xml b/tests/_files/class-with-anonymous-function-cobertura.xml
index 1eb28dfc5..f67d528fd 100644
--- a/tests/_files/class-with-anonymous-function-cobertura.xml
+++ b/tests/_files/class-with-anonymous-function-cobertura.xml
@@ -1,15 +1,16 @@
+
- %s
+ tests%e_files
-
+
-
+
-
+
diff --git a/tests/_files/class-with-outside-function-cobertura.xml b/tests/_files/class-with-outside-function-cobertura.xml
index fe6c005fc..fe66f3ae9 100644
--- a/tests/_files/class-with-outside-function-cobertura.xml
+++ b/tests/_files/class-with-outside-function-cobertura.xml
@@ -1,15 +1,16 @@
+
- %s
+ tests%e_files
-
+
-
+
-
+
@@ -19,7 +20,11 @@
-
+
+
+
+
+
diff --git a/tests/_files/ignored-lines-cobertura.xml b/tests/_files/ignored-lines-cobertura.xml
index 2650a3dde..81427d442 100644
--- a/tests/_files/ignored-lines-cobertura.xml
+++ b/tests/_files/ignored-lines-cobertura.xml
@@ -1,17 +1,22 @@
+
- %s
+ tests%e_files
-
+
-
+
-
+
+
+
+
+
diff --git a/tests/tests/Report/CoberturaTest.php b/tests/tests/Report/CoberturaTest.php
index 05163ed2d..131ef5a76 100644
--- a/tests/tests/Report/CoberturaTest.php
+++ b/tests/tests/Report/CoberturaTest.php
@@ -9,60 +9,67 @@
*/
namespace SebastianBergmann\CodeCoverage\Report;
+use DOMDocument;
use SebastianBergmann\CodeCoverage\TestCase;
-/**
- * @covers \SebastianBergmann\CodeCoverage\Report\Cobertura
- */
final class CoberturaTest extends TestCase
{
public function testLineCoverageForBankAccountTest(): void
{
- $cobertura = new Cobertura;
+ $report = (new Cobertura)->process($this->getLineCoverageForBankAccount(), null);
$this->assertStringMatchesFormatFile(
TEST_FILES_PATH . 'BankAccount-cobertura-line.xml',
- $cobertura->process($this->getLineCoverageForBankAccount(), null)
+ $report
);
+
+ $this->validateReport($report);
}
public function testPathCoverageForBankAccountTest(): void
{
- $cobertura = new Cobertura;
+ $report = (new Cobertura)->process($this->getPathCoverageForBankAccount(), null);
$this->assertStringMatchesFormatFile(
TEST_FILES_PATH . 'BankAccount-cobertura-path.xml',
- $cobertura->process($this->getPathCoverageForBankAccount(), null)
+ $report
);
}
public function testCoberturaForFileWithIgnoredLines(): void
{
- $cobertura = new Cobertura;
+ $report = (new Cobertura)->process($this->getCoverageForFileWithIgnoredLines());
$this->assertStringMatchesFormatFile(
TEST_FILES_PATH . 'ignored-lines-cobertura.xml',
- $cobertura->process($this->getCoverageForFileWithIgnoredLines())
+ $report
);
}
public function testCoberturaForClassWithAnonymousFunction(): void
{
- $cobertura = new Cobertura;
+ $report = (new Cobertura)->process($this->getCoverageForClassWithAnonymousFunction());
$this->assertStringMatchesFormatFile(
TEST_FILES_PATH . 'class-with-anonymous-function-cobertura.xml',
- $cobertura->process($this->getCoverageForClassWithAnonymousFunction())
+ $report
);
}
public function testCoberturaForClassAndOutsideFunction(): void
{
- $cobertura = new Cobertura;
+ $report = (new Cobertura)->process($this->getCoverageForClassWithOutsideFunction());
$this->assertStringMatchesFormatFile(
TEST_FILES_PATH . 'class-with-outside-function-cobertura.xml',
- $cobertura->process($this->getCoverageForClassWithOutsideFunction())
+ $report
);
}
+
+ private function validateReport(string $coberturaReport): void
+ {
+ $document = (new DOMDocument);
+ $this->assertTrue($document->loadXML($coberturaReport));
+ $this->assertTrue(@$document->validate());
+ }
}
diff --git a/tools/psalm b/tools/psalm
index 86184ee59..d24fac2cd 100755
Binary files a/tools/psalm and b/tools/psalm differ