From 034866b6649342cf516ff8db31dd817318246e8e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 10 Nov 2025 07:05:24 +0100 Subject: [PATCH 1/4] Represent line-coverage data in objects --- src/Data/ProcessedFunctionType.php | 32 ++++ src/Data/ProcessedMethodType.php | 32 ++++ src/Node/AbstractNode.php | 4 +- src/Node/Directory.php | 2 +- src/Node/File.php | 243 +++++++++++-------------- src/Report/Clover.php | 18 +- src/Report/Cobertura.php | 38 ++-- src/Report/Crap4j.php | 16 +- src/Report/Html/Renderer/Dashboard.php | 35 ++-- src/Report/Html/Renderer/File.php | 49 +++-- src/Report/OpenClover.php | 18 +- src/Report/Text.php | 16 +- src/Report/Xml/Facade.php | 31 ++-- 13 files changed, 285 insertions(+), 249 deletions(-) create mode 100644 src/Data/ProcessedFunctionType.php create mode 100644 src/Data/ProcessedMethodType.php diff --git a/src/Data/ProcessedFunctionType.php b/src/Data/ProcessedFunctionType.php new file mode 100644 index 000000000..6ab07d714 --- /dev/null +++ b/src/Data/ProcessedFunctionType.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Data; + +final class ProcessedFunctionType +{ + public function __construct( + public string $functionName, + public string $namespace, + public string $signature, + public int $startLine, + public int $endLine, + public int $executableLines, + public int $executedLines, + public int $executableBranches, + public int $executedBranches, + public int $executablePaths, + public int $executedPaths, + public int $ccn, + public float|int $coverage, + public int|string $crap, + public string $link, + ) { + } +} diff --git a/src/Data/ProcessedMethodType.php b/src/Data/ProcessedMethodType.php new file mode 100644 index 000000000..63353aefd --- /dev/null +++ b/src/Data/ProcessedMethodType.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Data; + +final class ProcessedMethodType +{ + public function __construct( + public string $methodName, + public string $visibility, + public string $signature, + public int $startLine, + public int $endLine, + public int $executableLines, + public int $executedLines, + public int $executableBranches, + public int $executedBranches, + public int $executablePaths, + public int $executedPaths, + public int $ccn, + public float|int $coverage, + public int|string $crap, + public string $link, + ) { + } +} diff --git a/src/Node/AbstractNode.php b/src/Node/AbstractNode.php index 7e82a3daf..8cd612bf0 100644 --- a/src/Node/AbstractNode.php +++ b/src/Node/AbstractNode.php @@ -15,13 +15,13 @@ use function str_replace; use function substr; use Countable; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; 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 */ @@ -190,7 +190,7 @@ public function cyclomaticComplexity(): int } foreach ($this->functions() as $function) { - $ccn += $function['ccn']; + $ccn += $function->ccn; } return $ccn; diff --git a/src/Node/Directory.php b/src/Node/Directory.php index 2802f93ab..7ad54ef5c 100644 --- a/src/Node/Directory.php +++ b/src/Node/Directory.php @@ -14,12 +14,12 @@ use function count; use IteratorAggregate; use RecursiveIteratorIterator; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; use SebastianBergmann\CodeCoverage\StaticAnalysis\LinesOfCode; /** * @template-implements IteratorAggregate * - * @phpstan-import-type ProcessedFunctionType from File * @phpstan-import-type ProcessedClassType from File * @phpstan-import-type ProcessedTraitType from File * diff --git a/src/Node/File.php b/src/Node/File.php index 54ee70b4a..8408fee92 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -13,6 +13,8 @@ use function count; use function range; use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; +use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType; use SebastianBergmann\CodeCoverage\StaticAnalysis\AnalysisResult; use SebastianBergmann\CodeCoverage\StaticAnalysis\Class_; use SebastianBergmann\CodeCoverage\StaticAnalysis\Function_; @@ -26,40 +28,6 @@ * @phpstan-import-type TestType from CodeCoverage * @phpstan-import-type LinesType from AnalysisResult * - * @phpstan-type ProcessedFunctionType array{ - * functionName: string, - * namespace: string, - * signature: string, - * startLine: int, - * endLine: int, - * executableLines: int, - * executedLines: int, - * executableBranches: int, - * executedBranches: int, - * executablePaths: int, - * executedPaths: int, - * ccn: int, - * coverage: int|float, - * crap: int|string, - * link: string - * } - * @phpstan-type ProcessedMethodType array{ - * methodName: string, - * visibility: string, - * signature: string, - * startLine: int, - * endLine: int, - * executableLines: int, - * executedLines: int, - * executableBranches: int, - * executedBranches: int, - * executablePaths: int, - * executedPaths: int, - * ccn: int, - * coverage: float|int, - * crap: int|string, - * link: string - * } * @phpstan-type ProcessedClassType array{ * className: string, * namespace: string, @@ -136,7 +104,7 @@ final class File extends AbstractNode private ?int $numTestedFunctions = null; /** - * @var array + * @var array */ private array $codeUnitsByLine = []; @@ -251,7 +219,7 @@ public function numberOfClasses(): int foreach ($this->classes as $class) { foreach ($class['methods'] as $method) { - if ($method['executableLines'] > 0) { + if ($method->executableLines > 0) { $this->numClasses++; continue 2; @@ -275,7 +243,7 @@ public function numberOfTraits(): int foreach ($this->traits as $trait) { foreach ($trait['methods'] as $method) { - if ($method['executableLines'] > 0) { + if ($method->executableLines > 0) { $this->numTraits++; continue 2; @@ -299,7 +267,7 @@ public function numberOfMethods(): int foreach ($this->classes as $class) { foreach ($class['methods'] as $method) { - if ($method['executableLines'] > 0) { + if ($method->executableLines > 0) { $this->numMethods++; } } @@ -307,7 +275,7 @@ public function numberOfMethods(): int foreach ($this->traits as $trait) { foreach ($trait['methods'] as $method) { - if ($method['executableLines'] > 0) { + if ($method->executableLines > 0) { $this->numMethods++; } } @@ -324,8 +292,8 @@ public function numberOfTestedMethods(): int foreach ($this->classes as $class) { foreach ($class['methods'] as $method) { - if ($method['executableLines'] > 0 && - $method['coverage'] === 100) { + if ($method->executableLines > 0 && + $method->coverage === 100) { $this->numTestedMethods++; } } @@ -333,8 +301,8 @@ public function numberOfTestedMethods(): int foreach ($this->traits as $trait) { foreach ($trait['methods'] as $method) { - if ($method['executableLines'] > 0 && - $method['coverage'] === 100) { + if ($method->executableLines > 0 && + $method->coverage === 100) { $this->numTestedMethods++; } } @@ -355,8 +323,8 @@ public function numberOfTestedFunctions(): int $this->numTestedFunctions = 0; foreach ($this->functions as $function) { - if ($function['executableLines'] > 0 && - $function['coverage'] === 100) { + if ($function->executableLines > 0 && + $function->coverage === 100) { $this->numTestedFunctions++; } } @@ -383,7 +351,11 @@ private function calculateStatistics(array $classes, array $traits, array $funct foreach (range(1, $this->linesOfCode->linesOfCode()) as $lineNumber) { if (isset($this->lineCoverageData[$lineNumber])) { foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { - $codeUnit['executableLines']++; + if ($codeUnit instanceof ProcessedMethodType || $codeUnit instanceof ProcessedFunctionType) { + $codeUnit->executableLines++; + } else { + $codeUnit['executableLines']++; + } } unset($codeUnit); @@ -392,7 +364,11 @@ private function calculateStatistics(array $classes, array $traits, array $funct if (count($this->lineCoverageData[$lineNumber]) > 0) { foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { - $codeUnit['executedLines']++; + if ($codeUnit instanceof ProcessedMethodType || $codeUnit instanceof ProcessedFunctionType) { + $codeUnit->executedLines++; + } else { + $codeUnit['executedLines']++; + } } unset($codeUnit); @@ -404,14 +380,14 @@ private function calculateStatistics(array $classes, array $traits, array $funct foreach ($this->traits as &$trait) { foreach ($trait['methods'] as &$method) { - $methodLineCoverage = $method['executableLines'] > 0 ? ($method['executedLines'] / $method['executableLines']) * 100 : 100; - $methodBranchCoverage = $method['executableBranches'] > 0 ? ($method['executedBranches'] / $method['executableBranches']) * 100 : 0; - $methodPathCoverage = $method['executablePaths'] > 0 ? ($method['executedPaths'] / $method['executablePaths']) * 100 : 0; + $methodLineCoverage = $method->executableLines > 0 ? ($method->executedLines / $method->executableLines) * 100 : 100; + $methodBranchCoverage = $method->executableBranches > 0 ? ($method->executedBranches / $method->executableBranches) * 100 : 0; + $methodPathCoverage = $method->executablePaths > 0 ? ($method->executedPaths / $method->executablePaths) * 100 : 0; - $method['coverage'] = $methodBranchCoverage > 0 ? $methodBranchCoverage : $methodLineCoverage; - $method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage > 0 ? $methodPathCoverage : $methodLineCoverage))->asString(); + $method->coverage = $methodBranchCoverage > 0 ? $methodBranchCoverage : $methodLineCoverage; + $method->crap = (new CrapIndex($method->ccn, $methodPathCoverage > 0 ? $methodPathCoverage : $methodLineCoverage))->asString(); - $trait['ccn'] += $method['ccn']; + $trait['ccn'] += $method->ccn; } unset($method); @@ -432,14 +408,14 @@ private function calculateStatistics(array $classes, array $traits, array $funct foreach ($this->classes as &$class) { foreach ($class['methods'] as &$method) { - $methodLineCoverage = $method['executableLines'] > 0 ? ($method['executedLines'] / $method['executableLines']) * 100 : 100; - $methodBranchCoverage = $method['executableBranches'] > 0 ? ($method['executedBranches'] / $method['executableBranches']) * 100 : 0; - $methodPathCoverage = $method['executablePaths'] > 0 ? ($method['executedPaths'] / $method['executablePaths']) * 100 : 0; + $methodLineCoverage = $method->executableLines > 0 ? ($method->executedLines / $method->executableLines) * 100 : 100; + $methodBranchCoverage = $method->executableBranches > 0 ? ($method->executedBranches / $method->executableBranches) * 100 : 0; + $methodPathCoverage = $method->executablePaths > 0 ? ($method->executedPaths / $method->executablePaths) * 100 : 0; - $method['coverage'] = $methodBranchCoverage > 0 ? $methodBranchCoverage : $methodLineCoverage; - $method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage > 0 ? $methodPathCoverage : $methodLineCoverage))->asString(); + $method->coverage = $methodBranchCoverage > 0 ? $methodBranchCoverage : $methodLineCoverage; + $method->crap = (new CrapIndex($method->ccn, $methodPathCoverage > 0 ? $methodPathCoverage : $methodLineCoverage))->asString(); - $class['ccn'] += $method['ccn']; + $class['ccn'] += $method->ccn; } unset($method); @@ -459,14 +435,14 @@ private function calculateStatistics(array $classes, array $traits, array $funct unset($class); foreach ($this->functions as &$function) { - $functionLineCoverage = $function['executableLines'] > 0 ? ($function['executedLines'] / $function['executableLines']) * 100 : 100; - $functionBranchCoverage = $function['executableBranches'] > 0 ? ($function['executedBranches'] / $function['executableBranches']) * 100 : 0; - $functionPathCoverage = $function['executablePaths'] > 0 ? ($function['executedPaths'] / $function['executablePaths']) * 100 : 0; + $functionLineCoverage = $function->executableLines > 0 ? ($function->executedLines / $function->executableLines) * 100 : 100; + $functionBranchCoverage = $function->executableBranches > 0 ? ($function->executedBranches / $function->executableBranches) * 100 : 0; + $functionPathCoverage = $function->executablePaths > 0 ? ($function->executedPaths / $function->executablePaths) * 100 : 0; - $function['coverage'] = $functionBranchCoverage > 0 ? $functionBranchCoverage : $functionLineCoverage; - $function['crap'] = (new CrapIndex($function['ccn'], $functionPathCoverage > 0 ? $functionPathCoverage : $functionLineCoverage))->asString(); + $function->coverage = $functionBranchCoverage > 0 ? $functionBranchCoverage : $functionLineCoverage; + $function->crap = (new CrapIndex($function->ccn, $functionPathCoverage > 0 ? $functionPathCoverage : $functionLineCoverage))->asString(); - if ($function['coverage'] === 100) { + if ($function->coverage === 100) { $this->numTestedFunctions++; } } @@ -501,15 +477,15 @@ private function processClasses(array $classes): void $methodData = $this->newMethod($className, $method, $link); $this->classes[$className]['methods'][$methodName] = $methodData; - $this->classes[$className]['executableBranches'] += $methodData['executableBranches']; - $this->classes[$className]['executedBranches'] += $methodData['executedBranches']; - $this->classes[$className]['executablePaths'] += $methodData['executablePaths']; - $this->classes[$className]['executedPaths'] += $methodData['executedPaths']; + $this->classes[$className]['executableBranches'] += $methodData->executableBranches; + $this->classes[$className]['executedBranches'] += $methodData->executedBranches; + $this->classes[$className]['executablePaths'] += $methodData->executablePaths; + $this->classes[$className]['executedPaths'] += $methodData->executedPaths; - $this->numExecutableBranches += $methodData['executableBranches']; - $this->numExecutedBranches += $methodData['executedBranches']; - $this->numExecutablePaths += $methodData['executablePaths']; - $this->numExecutedPaths += $methodData['executedPaths']; + $this->numExecutableBranches += $methodData->executableBranches; + $this->numExecutedBranches += $methodData->executedBranches; + $this->numExecutablePaths += $methodData->executablePaths; + $this->numExecutedPaths += $methodData->executedPaths; foreach (range($method->startLine(), $method->endLine()) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = [ @@ -550,15 +526,15 @@ private function processTraits(array $traits): void $methodData = $this->newMethod($traitName, $method, $link); $this->traits[$traitName]['methods'][$methodName] = $methodData; - $this->traits[$traitName]['executableBranches'] += $methodData['executableBranches']; - $this->traits[$traitName]['executedBranches'] += $methodData['executedBranches']; - $this->traits[$traitName]['executablePaths'] += $methodData['executablePaths']; - $this->traits[$traitName]['executedPaths'] += $methodData['executedPaths']; + $this->traits[$traitName]['executableBranches'] += $methodData->executableBranches; + $this->traits[$traitName]['executedBranches'] += $methodData->executedBranches; + $this->traits[$traitName]['executablePaths'] += $methodData->executablePaths; + $this->traits[$traitName]['executedPaths'] += $methodData->executedPaths; - $this->numExecutableBranches += $methodData['executableBranches']; - $this->numExecutedBranches += $methodData['executedBranches']; - $this->numExecutablePaths += $methodData['executablePaths']; - $this->numExecutedPaths += $methodData['executedPaths']; + $this->numExecutableBranches += $methodData->executableBranches; + $this->numExecutedBranches += $methodData->executedBranches; + $this->numExecutablePaths += $methodData->executablePaths; + $this->numExecutedPaths += $methodData->executedPaths; foreach (range($method->startLine(), $method->endLine()) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = [ @@ -578,34 +554,34 @@ private function processFunctions(array $functions): void $link = $this->id() . '.html#'; foreach ($functions as $functionName => $function) { - $this->functions[$functionName] = [ - 'functionName' => $functionName, - '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(), - 'coverage' => 0, - 'crap' => 0, - 'link' => $link . $function->startLine(), - ]; + $this->functions[$functionName] = new ProcessedFunctionType( + $functionName, + $function->namespace(), + $function->signature(), + $function->startLine(), + $function->endLine(), + 0, + 0, + 0, + 0, + 0, + 0, + $function->cyclomaticComplexity(), + 0, + 0, + $link . $function->startLine(), + ); foreach (range($function->startLine(), $function->endLine()) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = [&$this->functions[$functionName]]; } if (isset($this->functionCoverageData[$functionName]['branches'])) { - $this->functions[$functionName]['executableBranches'] = count( + $this->functions[$functionName]->executableBranches = count( $this->functionCoverageData[$functionName]['branches'], ); - $this->functions[$functionName]['executedBranches'] = count( + $this->functions[$functionName]->executedBranches = count( array_filter( $this->functionCoverageData[$functionName]['branches'], static function (array $branch) @@ -617,11 +593,11 @@ static function (array $branch) } if (isset($this->functionCoverageData[$functionName]['paths'])) { - $this->functions[$functionName]['executablePaths'] = count( + $this->functions[$functionName]->executablePaths = count( $this->functionCoverageData[$functionName]['paths'], ); - $this->functions[$functionName]['executedPaths'] = count( + $this->functions[$functionName]->executedPaths = count( array_filter( $this->functionCoverageData[$functionName]['paths'], static function (array $path) @@ -632,44 +608,26 @@ static function (array $path) ); } - $this->numExecutableBranches += $this->functions[$functionName]['executableBranches']; - $this->numExecutedBranches += $this->functions[$functionName]['executedBranches']; - $this->numExecutablePaths += $this->functions[$functionName]['executablePaths']; - $this->numExecutedPaths += $this->functions[$functionName]['executedPaths']; + $this->numExecutableBranches += $this->functions[$functionName]->executableBranches; + $this->numExecutedBranches += $this->functions[$functionName]->executedBranches; + $this->numExecutablePaths += $this->functions[$functionName]->executablePaths; + $this->numExecutedPaths += $this->functions[$functionName]->executedPaths; } } - /** - * @return ProcessedMethodType - */ - private function newMethod(string $className, Method $method, string $link): array + private function newMethod(string $className, Method $method, string $link): ProcessedMethodType { - $methodData = [ - 'methodName' => $method->name(), - 'visibility' => $method->visibility()->value, - 'signature' => $method->signature(), - 'startLine' => $method->startLine(), - 'endLine' => $method->endLine(), - 'executableLines' => 0, - 'executedLines' => 0, - 'executableBranches' => 0, - 'executedBranches' => 0, - 'executablePaths' => 0, - 'executedPaths' => 0, - 'ccn' => $method->cyclomaticComplexity(), - 'coverage' => 0, - 'crap' => 0, - 'link' => $link . $method->startLine(), - ]; - $key = $className . '->' . $method->name(); + $executableBranches = 0; + $executedBranches = 0; + if (isset($this->functionCoverageData[$key]['branches'])) { - $methodData['executableBranches'] = count( + $executableBranches = count( $this->functionCoverageData[$key]['branches'], ); - $methodData['executedBranches'] = count( + $executedBranches = count( array_filter( $this->functionCoverageData[$key]['branches'], static function (array $branch) @@ -680,12 +638,15 @@ static function (array $branch) ); } + $executablePaths = 0; + $executedPaths = 0; + if (isset($this->functionCoverageData[$key]['paths'])) { - $methodData['executablePaths'] = count( + $executablePaths = count( $this->functionCoverageData[$key]['paths'], ); - $methodData['executedPaths'] = count( + $executedPaths = count( array_filter( $this->functionCoverageData[$key]['paths'], static function (array $path) @@ -696,6 +657,22 @@ static function (array $path) ); } - return $methodData; + return new ProcessedMethodType( + $method->name(), + $method->visibility()->value, + $method->signature(), + $method->startLine(), + $method->endLine(), + 0, + 0, + $executableBranches, + $executedBranches, + $executablePaths, + $executedPaths, + $method->cyclomaticComplexity(), + 0, + 0, + $link . $method->startLine(), + ); } } diff --git a/src/Report/Clover.php b/src/Report/Clover.php index 641cd0bbb..b4d696fe1 100644 --- a/src/Report/Clover.php +++ b/src/Report/Clover.php @@ -80,33 +80,33 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string foreach ($class['methods'] as $methodName => $method) { /** @phpstan-ignore equal.notAllowed */ - if ($method['executableLines'] == 0) { + if ($method->executableLines == 0) { continue; } $classMethods++; - $classStatements += $method['executableLines']; - $coveredClassStatements += $method['executedLines']; + $classStatements += $method->executableLines; + $coveredClassStatements += $method->executedLines; /** @phpstan-ignore equal.notAllowed */ - if ($method['coverage'] == 100) { + if ($method->coverage == 100) { $coveredMethods++; } $methodCount = 0; - foreach (range($method['startLine'], $method['endLine']) as $line) { + foreach (range($method->startLine, $method->endLine) as $line) { if (isset($coverageData[$line])) { $methodCount = max($methodCount, count($coverageData[$line])); } } - $lines[$method['startLine']] = [ - 'ccn' => $method['ccn'], + $lines[$method->startLine] = [ + 'ccn' => $method->ccn, 'count' => $methodCount, - 'crap' => $method['crap'], + 'crap' => $method->crap, 'type' => 'method', - 'visibility' => $method['visibility'], + 'visibility' => $method->visibility, 'name' => $methodName, ]; } diff --git a/src/Report/Cobertura.php b/src/Report/Cobertura.php index 38653f754..ad37a70eb 100644 --- a/src/Report/Cobertura.php +++ b/src/Report/Cobertura.php @@ -144,18 +144,18 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $classElement->appendChild($classLinesElement); foreach ($class['methods'] as $methodName => $method) { - if ($method['executableLines'] === 0) { + if ($method->executableLines === 0) { continue; } - preg_match("/\((.*?)\)/", $method['signature'], $signature); + preg_match("/\((.*?)\)/", $method->signature, $signature); - $linesValid = $method['executableLines']; - $linesCovered = $method['executedLines']; + $linesValid = $method->executableLines; + $linesCovered = $method->executedLines; $lineRate = $linesCovered / $linesValid; - $branchesValid = $method['executableBranches']; - $branchesCovered = $method['executedBranches']; + $branchesValid = $method->executableBranches; + $branchesCovered = $method->executedBranches; $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); $methodElement = $document->createElement('method'); @@ -164,13 +164,13 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $methodElement->setAttribute('signature', $signature[1]); $methodElement->setAttribute('line-rate', (string) $lineRate); $methodElement->setAttribute('branch-rate', (string) $branchRate); - $methodElement->setAttribute('complexity', (string) $method['ccn']); + $methodElement->setAttribute('complexity', (string) $method->ccn); $methodLinesElement = $document->createElement('lines'); $methodElement->appendChild($methodLinesElement); - foreach (range($method['startLine'], $method['endLine']) as $line) { + foreach (range($method->startLine, $method->endLine) as $line) { if (!isset($coverageData[$line])) { continue; } @@ -217,23 +217,23 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $functions = $item->functions(); foreach ($functions as $functionName => $function) { - if ($function['executableLines'] === 0) { + if ($function->executableLines === 0) { continue; } - $complexity += $function['ccn']; - $packageComplexity += $function['ccn']; - $functionsComplexity += $function['ccn']; + $complexity += $function->ccn; + $packageComplexity += $function->ccn; + $functionsComplexity += $function->ccn; - $linesValid = $function['executableLines']; - $linesCovered = $function['executedLines']; + $linesValid = $function->executableLines; + $linesCovered = $function->executedLines; $lineRate = $linesCovered / $linesValid; $functionsLinesValid += $linesValid; $functionsLinesCovered += $linesCovered; - $branchesValid = $function['executableBranches']; - $branchesCovered = $function['executedBranches']; + $branchesValid = $function->executableBranches; + $branchesCovered = $function->executedBranches; $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); $functionsBranchesValid += $branchesValid; @@ -242,16 +242,16 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $methodElement = $document->createElement('method'); $methodElement->setAttribute('name', $functionName); - $methodElement->setAttribute('signature', $function['signature']); + $methodElement->setAttribute('signature', $function->signature); $methodElement->setAttribute('line-rate', (string) $lineRate); $methodElement->setAttribute('branch-rate', (string) $branchRate); - $methodElement->setAttribute('complexity', (string) $function['ccn']); + $methodElement->setAttribute('complexity', (string) $function->ccn); $methodLinesElement = $document->createElement('lines'); $methodElement->appendChild($methodLinesElement); - foreach (range($function['startLine'], $function['endLine']) as $line) { + foreach (range($function->startLine, $function->endLine) as $line) { if (!isset($coverageData[$line])) { continue; } diff --git a/src/Report/Crap4j.php b/src/Report/Crap4j.php index a79d0a68e..8d8e5d122 100644 --- a/src/Report/Crap4j.php +++ b/src/Report/Crap4j.php @@ -71,13 +71,13 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { - $crapLoad = $this->crapLoad((float) $method['crap'], $method['ccn'], $method['coverage']); + $crapLoad = $this->crapLoad((float) $method->crap, $method->ccn, $method->coverage); - $fullCrap += $method['crap']; + $fullCrap += $method->crap; $fullCrapLoad += $crapLoad; $fullMethodCount++; - if ($method['crap'] >= $this->threshold) { + if ($method->crap >= $this->threshold) { $fullCrapMethodCount++; } @@ -90,11 +90,11 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $methodNode->appendChild($document->createElement('package', $namespace)); $methodNode->appendChild($document->createElement('className', $className)); $methodNode->appendChild($document->createElement('methodName', $methodName)); - $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method['signature']))); - $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method['signature']))); - $methodNode->appendChild($document->createElement('crap', (string) $this->roundValue((float) $method['crap']))); - $methodNode->appendChild($document->createElement('complexity', (string) $method['ccn'])); - $methodNode->appendChild($document->createElement('coverage', (string) $this->roundValue($method['coverage']))); + $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method->signature))); + $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method->signature))); + $methodNode->appendChild($document->createElement('crap', (string) $this->roundValue((float) $method->crap))); + $methodNode->appendChild($document->createElement('complexity', (string) $method->ccn)); + $methodNode->appendChild($document->createElement('coverage', (string) $this->roundValue($method->coverage))); $methodNode->appendChild($document->createElement('crapLoad', (string) round($crapLoad))); $methodsNode->appendChild($methodNode); diff --git a/src/Report/Html/Renderer/Dashboard.php b/src/Report/Html/Renderer/Dashboard.php index 305c7fa10..4f0fd7cfc 100644 --- a/src/Report/Html/Renderer/Dashboard.php +++ b/src/Report/Html/Renderer/Dashboard.php @@ -20,6 +20,7 @@ use function str_replace; use function uasort; use function usort; +use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType; use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException; use SebastianBergmann\CodeCoverage\Node\AbstractNode; use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; @@ -102,11 +103,11 @@ private function complexity(array $classes, string $baseLink): array } $result['method'][] = [ - $method['coverage'], - $method['ccn'], - str_replace($baseLink, '', $method['link']), + $method->coverage, + $method->ccn, + str_replace($baseLink, '', $method->link), $methodName, - $method['crap'], + $method->crap, ]; } @@ -173,12 +174,12 @@ private function coverageDistribution(array $classes): array foreach ($classes as $class) { foreach ($class['methods'] as $methodName => $method) { - if ($method['coverage'] === 0) { + if ($method->coverage === 0) { $result['method']['0%']++; - } elseif ($method['coverage'] === 100) { + } elseif ($method->coverage === 100) { $result['method']['100%']++; } else { - $key = floor($method['coverage'] / 10) * 10; + $key = floor($method->coverage / 10) * 10; $key = $key . '-' . ($key + 10) . '%'; $result['method'][$key]++; } @@ -219,14 +220,14 @@ private function insufficientCoverage(array $classes, string $baseLink): array foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { - if ($method['coverage'] < $this->thresholds->highLowerBound()) { + if ($method->coverage < $this->thresholds->highLowerBound()) { $key = $methodName; if ($className !== '*') { $key = $className . '::' . $methodName; } - $leastTestedMethods[$key] = $method['coverage']; + $leastTestedMethods[$key] = $method->coverage; } } @@ -252,7 +253,7 @@ private function insufficientCoverage(array $classes, string $baseLink): array $result['method'] .= sprintf( ' %s%d%%' . "\n", - str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), + str_replace($baseLink, '', $classes[$class]['methods'][$method]->link), $methodName, $method, $coverage, @@ -275,7 +276,7 @@ private function projectRisks(array $classes, string $baseLink): array foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { - if ($method['coverage'] < $this->thresholds->highLowerBound() && $method['ccn'] > 1) { + if ($method->coverage < $this->thresholds->highLowerBound() && $method->ccn > 1) { $key = $methodName; if ($className !== '*') { @@ -296,9 +297,9 @@ private function projectRisks(array $classes, string $baseLink): array { return ((int) ($a['crap']) <=> (int) ($b['crap'])) * -1; }); - uasort($methodRisks, static function (array $a, array $b) + uasort($methodRisks, static function (ProcessedMethodType $a, ProcessedMethodType $b) { - return ((int) ($a['crap']) <=> (int) ($b['crap'])) * -1; + return ((int) ($a->crap) <=> (int) ($b->crap)) * -1; }); foreach ($classRisks as $className => $class) { @@ -317,12 +318,12 @@ private function projectRisks(array $classes, string $baseLink): array $result['method'] .= sprintf( ' %s%.1f%%%d%d' . "\n", - str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), + str_replace($baseLink, '', $classes[$class]['methods'][$method]->link), $methodName, $method, - $methodVals['coverage'], - $methodVals['ccn'], - $methodVals['crap'], + $methodVals->coverage, + $methodVals->ccn, + $methodVals->crap, ); } diff --git a/src/Report/Html/Renderer/File.php b/src/Report/Html/Renderer/File.php index 09dbe31fe..81b54b0ae 100644 --- a/src/Report/Html/Renderer/File.php +++ b/src/Report/Html/Renderer/File.php @@ -102,6 +102,8 @@ use function str_replace; use function token_get_all; use function trim; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; +use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType; use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException; use SebastianBergmann\CodeCoverage\Node\File as FileNode; use SebastianBergmann\CodeCoverage\Util\Percentage; @@ -111,8 +113,6 @@ /** * @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 */ @@ -341,10 +341,10 @@ private function renderTraitOrClassItems(array $items, Template $template, Templ $numTestedMethods = 0; foreach ($item['methods'] as $method) { - if ($method['executableLines'] > 0) { + if ($method->executableLines > 0) { $numMethods++; - if ($method['executedLines'] === $method['executableLines']) { + if ($method->executedLines === $method->executableLines) { $numTestedMethods++; } } @@ -453,35 +453,32 @@ private function renderFunctionItems(array $functions, Template $template): stri return $buffer; } - /** - * @param ProcessedFunctionType|ProcessedMethodType $item - */ - private function renderFunctionOrMethodItem(Template $template, array $item, string $indent = ''): string + private function renderFunctionOrMethodItem(Template $template, ProcessedFunctionType|ProcessedMethodType $item, string $indent = ''): string { $numMethods = 0; $numTestedMethods = 0; - if ($item['executableLines'] > 0) { + if ($item->executableLines > 0) { $numMethods = 1; - if ($item['executedLines'] === $item['executableLines']) { + if ($item->executedLines === $item->executableLines) { $numTestedMethods = 1; } } $executedLinesPercentage = Percentage::fromFractionAndTotal( - $item['executedLines'], - $item['executableLines'], + $item->executedLines, + $item->executableLines, ); $executedBranchesPercentage = Percentage::fromFractionAndTotal( - $item['executedBranches'], - $item['executableBranches'], + $item->executedBranches, + $item->executableBranches, ); $executedPathsPercentage = Percentage::fromFractionAndTotal( - $item['executedPaths'], - $item['executablePaths'], + $item->executedPaths, + $item->executablePaths, ); $testedMethodsPercentage = Percentage::fromFractionAndTotal( @@ -495,27 +492,27 @@ private function renderFunctionOrMethodItem(Template $template, array $item, str 'name' => sprintf( '%s%s', $indent, - $item['startLine'], - htmlspecialchars($item['signature'], self::HTML_SPECIAL_CHARS_FLAGS), - $item['functionName'] ?? $item['methodName'], + $item->startLine, + htmlspecialchars($item->signature, self::HTML_SPECIAL_CHARS_FLAGS), + $item instanceof ProcessedFunctionType ? $item->functionName : $item->methodName, ), 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => $executedLinesPercentage->asFloat(), 'linesExecutedPercentAsString' => $executedLinesPercentage->asString(), - 'numExecutedLines' => $item['executedLines'], - 'numExecutableLines' => $item['executableLines'], + 'numExecutedLines' => $item->executedLines, + 'numExecutableLines' => $item->executableLines, 'branchesExecutedPercent' => $executedBranchesPercentage->asFloat(), 'branchesExecutedPercentAsString' => $executedBranchesPercentage->asString(), - 'numExecutedBranches' => $item['executedBranches'], - 'numExecutableBranches' => $item['executableBranches'], + 'numExecutedBranches' => $item->executedBranches, + 'numExecutableBranches' => $item->executableBranches, 'pathsExecutedPercent' => $executedPathsPercentage->asFloat(), 'pathsExecutedPercentAsString' => $executedPathsPercentage->asString(), - 'numExecutedPaths' => $item['executedPaths'], - 'numExecutablePaths' => $item['executablePaths'], + 'numExecutedPaths' => $item->executedPaths, + 'numExecutablePaths' => $item->executablePaths, 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), - 'crap' => $item['crap'], + 'crap' => $item->crap, ], ); } diff --git a/src/Report/OpenClover.php b/src/Report/OpenClover.php index 65d409b1c..90897a650 100644 --- a/src/Report/OpenClover.php +++ b/src/Report/OpenClover.php @@ -84,33 +84,33 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string foreach ($class['methods'] as $methodName => $method) { /** @phpstan-ignore equal.notAllowed */ - if ($method['executableLines'] == 0) { + if ($method->executableLines == 0) { continue; } $classMethods++; - $classStatements += $method['executableLines']; - $coveredClassStatements += $method['executedLines']; + $classStatements += $method->executableLines; + $coveredClassStatements += $method->executedLines; /** @phpstan-ignore equal.notAllowed */ - if ($method['coverage'] == 100) { + if ($method->coverage == 100) { $coveredMethods++; } $methodCount = 0; - foreach (range($method['startLine'], $method['endLine']) as $line) { + foreach (range($method->startLine, $method->endLine) as $line) { if (isset($coverageData[$line])) { $methodCount = max($methodCount, count($coverageData[$line])); } } - $lines[$method['startLine']] = [ - 'ccn' => $method['ccn'], + $lines[$method->startLine] = [ + 'ccn' => $method->ccn, 'count' => $methodCount, 'type' => 'method', - 'signature' => $method['signature'], - 'visibility' => $method['visibility'], + 'signature' => $method->signature, + 'visibility' => $method->visibility, ]; } diff --git a/src/Report/Text.php b/src/Report/Text.php index f18820b70..b2ea332d6 100644 --- a/src/Report/Text.php +++ b/src/Report/Text.php @@ -192,20 +192,20 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin foreach ($class['methods'] as $method) { /** @phpstan-ignore equal.notAllowed */ - if ($method['executableLines'] == 0) { + if ($method->executableLines == 0) { continue; } $classMethods++; - $classExecutableLines += $method['executableLines']; - $classExecutedLines += $method['executedLines']; - $classExecutableBranches += $method['executableBranches']; - $classExecutedBranches += $method['executedBranches']; - $classExecutablePaths += $method['executablePaths']; - $classExecutedPaths += $method['executedPaths']; + $classExecutableLines += $method->executableLines; + $classExecutedLines += $method->executedLines; + $classExecutableBranches += $method->executableBranches; + $classExecutedBranches += $method->executedBranches; + $classExecutablePaths += $method->executablePaths; + $classExecutedPaths += $method->executedPaths; /** @phpstan-ignore equal.notAllowed */ - if ($method['coverage'] == 100) { + if ($method->coverage == 100) { $coveredMethods++; } } diff --git a/src/Report/Xml/Facade.php b/src/Report/Xml/Facade.php index 30c35e269..608ac95c0 100644 --- a/src/Report/Xml/Facade.php +++ b/src/Report/Xml/Facade.php @@ -23,6 +23,7 @@ use DateTimeImmutable; use DOMDocument; use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; use SebastianBergmann\CodeCoverage\Node\AbstractNode; use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; use SebastianBergmann\CodeCoverage\Node\File; @@ -38,7 +39,6 @@ /** * @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 @@ -196,29 +196,26 @@ private function processUnit(array $unit, Report $report): void $unitObject->setNamespace($unit['namespace']); foreach ($unit['methods'] as $method) { - $methodObject = $unitObject->addMethod($method['methodName']); - $methodObject->setSignature($method['signature']); - $methodObject->setLines((string) $method['startLine'], (string) $method['endLine']); - $methodObject->setCrap($method['crap']); + $methodObject = $unitObject->addMethod($method->methodName); + $methodObject->setSignature($method->signature); + $methodObject->setLines((string) $method->startLine, (string) $method->endLine); + $methodObject->setCrap($method->crap); $methodObject->setTotals( - (string) $method['executableLines'], - (string) $method['executedLines'], - (string) $method['coverage'], + (string) $method->executableLines, + (string) $method->executedLines, + (string) $method->coverage, ); } } - /** - * @param ProcessedFunctionType $function - */ - private function processFunction(array $function, Report $report): void + private function processFunction(ProcessedFunctionType $function, Report $report): void { - $functionObject = $report->functionObject($function['functionName']); + $functionObject = $report->functionObject($function->functionName); - $functionObject->setSignature($function['signature']); - $functionObject->setLines((string) $function['startLine']); - $functionObject->setCrap($function['crap']); - $functionObject->setTotals((string) $function['executableLines'], (string) $function['executedLines'], (string) $function['coverage']); + $functionObject->setSignature($function->signature); + $functionObject->setLines((string) $function->startLine); + $functionObject->setCrap($function->crap); + $functionObject->setTotals((string) $function->executableLines, (string) $function->executedLines, (string) $function->coverage); } /** From fe2729730f066335541cfd4004092ec4305e6d44 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 10 Nov 2025 08:00:31 +0100 Subject: [PATCH 2/4] Reworked ProcessedClassType, ProcessedTraitType --- src/Data/ProcessedClassType.php | 34 +++++ src/Data/ProcessedTraitType.php | 34 +++++ src/Node/AbstractNode.php | 7 +- src/Node/Directory.php | 5 +- src/Node/File.php | 183 ++++++++++--------------- src/Report/Clover.php | 16 +-- src/Report/Cobertura.php | 16 +-- src/Report/Crap4j.php | 6 +- src/Report/Html/Renderer/Dashboard.php | 54 ++++---- src/Report/Html/Renderer/File.php | 49 ++++--- src/Report/OpenClover.php | 18 +-- src/Report/Text.php | 4 +- src/Report/Xml/Facade.php | 28 ++-- 13 files changed, 237 insertions(+), 217 deletions(-) create mode 100644 src/Data/ProcessedClassType.php create mode 100644 src/Data/ProcessedTraitType.php diff --git a/src/Data/ProcessedClassType.php b/src/Data/ProcessedClassType.php new file mode 100644 index 000000000..879c7f0b2 --- /dev/null +++ b/src/Data/ProcessedClassType.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Data; + +final class ProcessedClassType +{ + public function __construct( + public string $className, + public string $namespace, + /** + * @var array + */ + public array $methods, + public int $startLine, + public int $executableLines, + public int $executedLines, + public int $executableBranches, + public int $executedBranches, + public int $executablePaths, + public int $executedPaths, + public int $ccn, + public float|int $coverage, + public int|string $crap, + public string $link, + ) { + } +} diff --git a/src/Data/ProcessedTraitType.php b/src/Data/ProcessedTraitType.php new file mode 100644 index 000000000..8d133c85c --- /dev/null +++ b/src/Data/ProcessedTraitType.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Data; + +final class ProcessedTraitType +{ + public function __construct( + public string $traitName, + public string $namespace, + /** + * @var array + */ + public array $methods, + public int $startLine, + public int $executableLines, + public int $executedLines, + public int $executableBranches, + public int $executedBranches, + public int $executablePaths, + public int $executedPaths, + public int $ccn, + public float|int $coverage, + public int|string $crap, + public string $link, + ) { + } +} diff --git a/src/Node/AbstractNode.php b/src/Node/AbstractNode.php index 8cd612bf0..3b54d82b1 100644 --- a/src/Node/AbstractNode.php +++ b/src/Node/AbstractNode.php @@ -15,15 +15,14 @@ use function str_replace; use function substr; use Countable; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; 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 ProcessedClassType from File - * @phpstan-import-type ProcessedTraitType from File */ abstract class AbstractNode implements Countable { @@ -186,7 +185,7 @@ public function cyclomaticComplexity(): int $ccn = 0; foreach ($this->classesAndTraits() as $classLike) { - $ccn += $classLike['ccn']; + $ccn += $classLike->ccn; } foreach ($this->functions() as $function) { diff --git a/src/Node/Directory.php b/src/Node/Directory.php index 7ad54ef5c..818d665dd 100644 --- a/src/Node/Directory.php +++ b/src/Node/Directory.php @@ -14,15 +14,14 @@ use function count; use IteratorAggregate; use RecursiveIteratorIterator; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; use SebastianBergmann\CodeCoverage\StaticAnalysis\LinesOfCode; /** * @template-implements IteratorAggregate * - * @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 */ final class Directory extends AbstractNode implements IteratorAggregate diff --git a/src/Node/File.php b/src/Node/File.php index 8408fee92..9c57dbd03 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -13,8 +13,10 @@ use function count; use function range; use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; use SebastianBergmann\CodeCoverage\StaticAnalysis\AnalysisResult; use SebastianBergmann\CodeCoverage\StaticAnalysis\Class_; use SebastianBergmann\CodeCoverage\StaticAnalysis\Function_; @@ -27,39 +29,6 @@ * * @phpstan-import-type TestType from CodeCoverage * @phpstan-import-type LinesType from AnalysisResult - * - * @phpstan-type ProcessedClassType array{ - * className: string, - * namespace: string, - * methods: array, - * startLine: int, - * executableLines: int, - * executedLines: int, - * executableBranches: int, - * executedBranches: int, - * executablePaths: int, - * executedPaths: int, - * ccn: int, - * coverage: int|float, - * crap: int|string, - * link: string - * } - * @phpstan-type ProcessedTraitType array{ - * traitName: string, - * namespace: string, - * methods: array, - * startLine: int, - * executableLines: int, - * executedLines: int, - * executableBranches: int, - * executedBranches: int, - * executablePaths: int, - * executedPaths: int, - * ccn: int, - * coverage: float|int, - * crap: int|string, - * link: string - * } */ final class File extends AbstractNode { @@ -218,7 +187,7 @@ public function numberOfClasses(): int $this->numClasses = 0; foreach ($this->classes as $class) { - foreach ($class['methods'] as $method) { + foreach ($class->methods as $method) { if ($method->executableLines > 0) { $this->numClasses++; @@ -242,7 +211,7 @@ public function numberOfTraits(): int $this->numTraits = 0; foreach ($this->traits as $trait) { - foreach ($trait['methods'] as $method) { + foreach ($trait->methods as $method) { if ($method->executableLines > 0) { $this->numTraits++; @@ -266,7 +235,7 @@ public function numberOfMethods(): int $this->numMethods = 0; foreach ($this->classes as $class) { - foreach ($class['methods'] as $method) { + foreach ($class->methods as $method) { if ($method->executableLines > 0) { $this->numMethods++; } @@ -274,7 +243,7 @@ public function numberOfMethods(): int } foreach ($this->traits as $trait) { - foreach ($trait['methods'] as $method) { + foreach ($trait->methods as $method) { if ($method->executableLines > 0) { $this->numMethods++; } @@ -291,7 +260,7 @@ public function numberOfTestedMethods(): int $this->numTestedMethods = 0; foreach ($this->classes as $class) { - foreach ($class['methods'] as $method) { + foreach ($class->methods as $method) { if ($method->executableLines > 0 && $method->coverage === 100) { $this->numTestedMethods++; @@ -300,7 +269,7 @@ public function numberOfTestedMethods(): int } foreach ($this->traits as $trait) { - foreach ($trait['methods'] as $method) { + foreach ($trait->methods as $method) { if ($method->executableLines > 0 && $method->coverage === 100) { $this->numTestedMethods++; @@ -351,11 +320,7 @@ private function calculateStatistics(array $classes, array $traits, array $funct foreach (range(1, $this->linesOfCode->linesOfCode()) as $lineNumber) { if (isset($this->lineCoverageData[$lineNumber])) { foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { - if ($codeUnit instanceof ProcessedMethodType || $codeUnit instanceof ProcessedFunctionType) { - $codeUnit->executableLines++; - } else { - $codeUnit['executableLines']++; - } + $codeUnit->executableLines++; } unset($codeUnit); @@ -364,11 +329,7 @@ private function calculateStatistics(array $classes, array $traits, array $funct if (count($this->lineCoverageData[$lineNumber]) > 0) { foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { - if ($codeUnit instanceof ProcessedMethodType || $codeUnit instanceof ProcessedFunctionType) { - $codeUnit->executedLines++; - } else { - $codeUnit['executedLines']++; - } + $codeUnit->executedLines++; } unset($codeUnit); @@ -379,7 +340,7 @@ private function calculateStatistics(array $classes, array $traits, array $funct } foreach ($this->traits as &$trait) { - foreach ($trait['methods'] as &$method) { + foreach ($trait->methods as &$method) { $methodLineCoverage = $method->executableLines > 0 ? ($method->executedLines / $method->executableLines) * 100 : 100; $methodBranchCoverage = $method->executableBranches > 0 ? ($method->executedBranches / $method->executableBranches) * 100 : 0; $methodPathCoverage = $method->executablePaths > 0 ? ($method->executedPaths / $method->executablePaths) * 100 : 0; @@ -387,19 +348,19 @@ private function calculateStatistics(array $classes, array $traits, array $funct $method->coverage = $methodBranchCoverage > 0 ? $methodBranchCoverage : $methodLineCoverage; $method->crap = (new CrapIndex($method->ccn, $methodPathCoverage > 0 ? $methodPathCoverage : $methodLineCoverage))->asString(); - $trait['ccn'] += $method->ccn; + $trait->ccn += $method->ccn; } unset($method); - $traitLineCoverage = $trait['executableLines'] > 0 ? ($trait['executedLines'] / $trait['executableLines']) * 100 : 100; - $traitBranchCoverage = $trait['executableBranches'] > 0 ? ($trait['executedBranches'] / $trait['executableBranches']) * 100 : 0; - $traitPathCoverage = $trait['executablePaths'] > 0 ? ($trait['executedPaths'] / $trait['executablePaths']) * 100 : 0; + $traitBranchCoverage = $trait->executableBranches > 0 ? ($trait->executedBranches / $trait->executableBranches) * 100 : 0; + $traitLineCoverage = $trait->executableLines > 0 ? ($trait->executedLines / $trait->executableLines) * 100 : 100; + $traitPathCoverage = $trait->executablePaths > 0 ? ($trait->executedPaths / $trait->executablePaths) * 100 : 0; - $trait['coverage'] = $traitBranchCoverage > 0 ? $traitBranchCoverage : $traitLineCoverage; - $trait['crap'] = (new CrapIndex($trait['ccn'], $traitPathCoverage > 0 ? $traitPathCoverage : $traitLineCoverage))->asString(); + $trait->coverage = $traitBranchCoverage > 0 ? $traitBranchCoverage : $traitLineCoverage; + $trait->crap = (new CrapIndex($trait->ccn, $traitPathCoverage > 0 ? $traitPathCoverage : $traitLineCoverage))->asString(); - if ($trait['executableLines'] > 0 && $trait['coverage'] === 100) { + if ($trait->executableLines > 0 && $trait->coverage === 100) { $this->numTestedClasses++; } } @@ -407,7 +368,7 @@ private function calculateStatistics(array $classes, array $traits, array $funct unset($trait); foreach ($this->classes as &$class) { - foreach ($class['methods'] as &$method) { + foreach ($class->methods as &$method) { $methodLineCoverage = $method->executableLines > 0 ? ($method->executedLines / $method->executableLines) * 100 : 100; $methodBranchCoverage = $method->executableBranches > 0 ? ($method->executedBranches / $method->executableBranches) * 100 : 0; $methodPathCoverage = $method->executablePaths > 0 ? ($method->executedPaths / $method->executablePaths) * 100 : 0; @@ -415,19 +376,19 @@ private function calculateStatistics(array $classes, array $traits, array $funct $method->coverage = $methodBranchCoverage > 0 ? $methodBranchCoverage : $methodLineCoverage; $method->crap = (new CrapIndex($method->ccn, $methodPathCoverage > 0 ? $methodPathCoverage : $methodLineCoverage))->asString(); - $class['ccn'] += $method->ccn; + $class->ccn += $method->ccn; } unset($method); - $classLineCoverage = $class['executableLines'] > 0 ? ($class['executedLines'] / $class['executableLines']) * 100 : 100; - $classBranchCoverage = $class['executableBranches'] > 0 ? ($class['executedBranches'] / $class['executableBranches']) * 100 : 0; - $classPathCoverage = $class['executablePaths'] > 0 ? ($class['executedPaths'] / $class['executablePaths']) * 100 : 0; + $classLineCoverage = $class->executableLines > 0 ? ($class->executedLines / $class->executableLines) * 100 : 100; + $classBranchCoverage = $class->executableBranches > 0 ? ($class->executedBranches / $class->executableBranches) * 100 : 0; + $classPathCoverage = $class->executablePaths > 0 ? ($class->executedPaths / $class->executablePaths) * 100 : 0; - $class['coverage'] = $classBranchCoverage > 0 ? $classBranchCoverage : $classLineCoverage; - $class['crap'] = (new CrapIndex($class['ccn'], $classPathCoverage > 0 ? $classPathCoverage : $classLineCoverage))->asString(); + $class->coverage = $classBranchCoverage > 0 ? $classBranchCoverage : $classLineCoverage; + $class->crap = (new CrapIndex($class->ccn, $classPathCoverage > 0 ? $classPathCoverage : $classLineCoverage))->asString(); - if ($class['executableLines'] > 0 && $class['coverage'] === 100) { + if ($class->executableLines > 0 && $class->coverage === 100) { $this->numTestedClasses++; } } @@ -456,31 +417,31 @@ private function processClasses(array $classes): void $link = $this->id() . '.html#'; foreach ($classes as $className => $class) { - $this->classes[$className] = [ - 'className' => $className, - 'namespace' => $class->namespace(), - 'methods' => [], - 'startLine' => $class->startLine(), - 'executableLines' => 0, - 'executedLines' => 0, - 'executableBranches' => 0, - 'executedBranches' => 0, - 'executablePaths' => 0, - 'executedPaths' => 0, - 'ccn' => 0, - 'coverage' => 0, - 'crap' => 0, - 'link' => $link . $class->startLine(), - ]; + $this->classes[$className] = new ProcessedClassType( + $className, + $class->namespace(), + [], + $class->startLine(), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + $link . $class->startLine(), + ); foreach ($class->methods() as $methodName => $method) { - $methodData = $this->newMethod($className, $method, $link); - $this->classes[$className]['methods'][$methodName] = $methodData; + $methodData = $this->newMethod($className, $method, $link); + $this->classes[$className]->methods[$methodName] = $methodData; - $this->classes[$className]['executableBranches'] += $methodData->executableBranches; - $this->classes[$className]['executedBranches'] += $methodData->executedBranches; - $this->classes[$className]['executablePaths'] += $methodData->executablePaths; - $this->classes[$className]['executedPaths'] += $methodData->executedPaths; + $this->classes[$className]->executableBranches += $methodData->executableBranches; + $this->classes[$className]->executedBranches += $methodData->executedBranches; + $this->classes[$className]->executablePaths += $methodData->executablePaths; + $this->classes[$className]->executedPaths += $methodData->executedPaths; $this->numExecutableBranches += $methodData->executableBranches; $this->numExecutedBranches += $methodData->executedBranches; @@ -490,7 +451,7 @@ private function processClasses(array $classes): void foreach (range($method->startLine(), $method->endLine()) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = [ &$this->classes[$className], - &$this->classes[$className]['methods'][$methodName], + &$this->classes[$className]->methods[$methodName], ]; } } @@ -505,31 +466,31 @@ private function processTraits(array $traits): void $link = $this->id() . '.html#'; foreach ($traits as $traitName => $trait) { - $this->traits[$traitName] = [ - 'traitName' => $traitName, - 'namespace' => $trait->namespace(), - 'methods' => [], - 'startLine' => $trait->startLine(), - 'executableLines' => 0, - 'executedLines' => 0, - 'executableBranches' => 0, - 'executedBranches' => 0, - 'executablePaths' => 0, - 'executedPaths' => 0, - 'ccn' => 0, - 'coverage' => 0, - 'crap' => 0, - 'link' => $link . $trait->startLine(), - ]; + $this->traits[$traitName] = new ProcessedTraitType( + $traitName, + $trait->namespace(), + [], + $trait->startLine(), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + $link . $trait->startLine(), + ); foreach ($trait->methods() as $methodName => $method) { - $methodData = $this->newMethod($traitName, $method, $link); - $this->traits[$traitName]['methods'][$methodName] = $methodData; + $methodData = $this->newMethod($traitName, $method, $link); + $this->traits[$traitName]->methods[$methodName] = $methodData; - $this->traits[$traitName]['executableBranches'] += $methodData->executableBranches; - $this->traits[$traitName]['executedBranches'] += $methodData->executedBranches; - $this->traits[$traitName]['executablePaths'] += $methodData->executablePaths; - $this->traits[$traitName]['executedPaths'] += $methodData->executedPaths; + $this->traits[$traitName]->executableBranches += $methodData->executableBranches; + $this->traits[$traitName]->executedBranches += $methodData->executedBranches; + $this->traits[$traitName]->executablePaths += $methodData->executablePaths; + $this->traits[$traitName]->executedPaths += $methodData->executedPaths; $this->numExecutableBranches += $methodData->executableBranches; $this->numExecutedBranches += $methodData->executedBranches; @@ -539,7 +500,7 @@ private function processTraits(array $traits): void foreach (range($method->startLine(), $method->endLine()) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = [ &$this->traits[$traitName], - &$this->traits[$traitName]['methods'][$methodName], + &$this->traits[$traitName]->methods[$methodName], ]; } } diff --git a/src/Report/Clover.php b/src/Report/Clover.php index b4d696fe1..8e59d4c65 100644 --- a/src/Report/Clover.php +++ b/src/Report/Clover.php @@ -74,11 +74,11 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $classMethods = 0; // Assumption: one namespace per file - if ($class['namespace'] !== '') { - $namespace = $class['namespace']; + if ($class->namespace !== '') { + $namespace = $class->namespace; } - foreach ($class['methods'] as $methodName => $method) { + foreach ($class->methods as $methodName => $method) { /** @phpstan-ignore equal.notAllowed */ if ($method->executableLines == 0) { continue; @@ -118,15 +118,15 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $xmlFile->appendChild($xmlClass); $xmlMetrics = $xmlDocument->createElement('metrics'); - $xmlMetrics->setAttribute('complexity', (string) $class['ccn']); + $xmlMetrics->setAttribute('complexity', (string) $class->ccn); $xmlMetrics->setAttribute('methods', (string) $classMethods); $xmlMetrics->setAttribute('coveredmethods', (string) $coveredMethods); - $xmlMetrics->setAttribute('conditionals', (string) $class['executableBranches']); - $xmlMetrics->setAttribute('coveredconditionals', (string) $class['executedBranches']); + $xmlMetrics->setAttribute('conditionals', (string) $class->executableBranches); + $xmlMetrics->setAttribute('coveredconditionals', (string) $class->executedBranches); $xmlMetrics->setAttribute('statements', (string) $classStatements); $xmlMetrics->setAttribute('coveredstatements', (string) $coveredClassStatements); - $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class['executableBranches'])); - $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class['executedBranches'])); + $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class->executableBranches)); + $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class->executedBranches)); $xmlClass->appendChild($xmlMetrics); } diff --git a/src/Report/Cobertura.php b/src/Report/Cobertura.php index ad37a70eb..38f0e79ee 100644 --- a/src/Report/Cobertura.php +++ b/src/Report/Cobertura.php @@ -114,15 +114,15 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $coverageData = $item->lineCoverageData(); foreach ($classes as $className => $class) { - $complexity += $class['ccn']; - $packageComplexity += $class['ccn']; + $complexity += $class->ccn; + $packageComplexity += $class->ccn; - $linesValid = $class['executableLines']; - $linesCovered = $class['executedLines']; + $linesValid = $class->executableLines; + $linesCovered = $class->executedLines; $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid); - $branchesValid = $class['executableBranches']; - $branchesCovered = $class['executedBranches']; + $branchesValid = $class->executableBranches; + $branchesCovered = $class->executedBranches; $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); $classElement = $document->createElement('class'); @@ -131,7 +131,7 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $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']); + $classElement->setAttribute('complexity', (string) $class->ccn); $classesElement->appendChild($classElement); @@ -143,7 +143,7 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $classElement->appendChild($classLinesElement); - foreach ($class['methods'] as $methodName => $method) { + foreach ($class->methods as $methodName => $method) { if ($method->executableLines === 0) { continue; } diff --git a/src/Report/Crap4j.php b/src/Report/Crap4j.php index 8d8e5d122..b015908b4 100644 --- a/src/Report/Crap4j.php +++ b/src/Report/Crap4j.php @@ -70,7 +70,7 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $classes = $item->classesAndTraits(); foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { + foreach ($class->methods as $methodName => $method) { $crapLoad = $this->crapLoad((float) $method->crap, $method->ccn, $method->coverage); $fullCrap += $method->crap; @@ -83,8 +83,8 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $methodNode = $document->createElement('method'); - if ($class['namespace'] !== '') { - $namespace = $class['namespace']; + if ($class->namespace !== '') { + $namespace = $class->namespace; } $methodNode->appendChild($document->createElement('package', $namespace)); diff --git a/src/Report/Html/Renderer/Dashboard.php b/src/Report/Html/Renderer/Dashboard.php index 4f0fd7cfc..9df28c3f4 100644 --- a/src/Report/Html/Renderer/Dashboard.php +++ b/src/Report/Html/Renderer/Dashboard.php @@ -20,18 +20,16 @@ use function str_replace; use function uasort; use function usort; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; 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 @@ -97,7 +95,7 @@ private function complexity(array $classes, string $baseLink): array $result = ['class' => [], 'method' => []]; foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { + foreach ($class->methods as $methodName => $method) { if ($className !== '*') { $methodName = $className . '::' . $methodName; } @@ -112,11 +110,11 @@ private function complexity(array $classes, string $baseLink): array } $result['class'][] = [ - $class['coverage'], - $class['ccn'], - str_replace($baseLink, '', $class['link']), + $class->coverage, + $class->ccn, + str_replace($baseLink, '', $class->link), $className, - $class['crap'], + $class->crap, ]; } @@ -173,7 +171,7 @@ private function coverageDistribution(array $classes): array ]; foreach ($classes as $class) { - foreach ($class['methods'] as $methodName => $method) { + foreach ($class->methods as $method) { if ($method->coverage === 0) { $result['method']['0%']++; } elseif ($method->coverage === 100) { @@ -185,12 +183,12 @@ private function coverageDistribution(array $classes): array } } - if ($class['coverage'] === 0) { + if ($class->coverage === 0) { $result['class']['0%']++; - } elseif ($class['coverage'] === 100) { + } elseif ($class->coverage === 100) { $result['class']['100%']++; } else { - $key = floor($class['coverage'] / 10) * 10; + $key = floor($class->coverage / 10) * 10; $key = $key . '-' . ($key + 10) . '%'; $result['class'][$key]++; } @@ -219,7 +217,7 @@ private function insufficientCoverage(array $classes, string $baseLink): array $result = ['class' => '', 'method' => '']; foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { + foreach ($class->methods as $methodName => $method) { if ($method->coverage < $this->thresholds->highLowerBound()) { $key = $methodName; @@ -231,8 +229,8 @@ private function insufficientCoverage(array $classes, string $baseLink): array } } - if ($class['coverage'] < $this->thresholds->highLowerBound()) { - $leastTestedClasses[$className] = $class['coverage']; + if ($class->coverage < $this->thresholds->highLowerBound()) { + $leastTestedClasses[$className] = $class->coverage; } } @@ -242,7 +240,7 @@ private function insufficientCoverage(array $classes, string $baseLink): array foreach ($leastTestedClasses as $className => $coverage) { $result['class'] .= sprintf( ' %s%d%%' . "\n", - str_replace($baseLink, '', $classes[$className]['link']), + str_replace($baseLink, '', $classes[$className]->link), $className, $coverage, ); @@ -253,7 +251,7 @@ private function insufficientCoverage(array $classes, string $baseLink): array $result['method'] .= sprintf( ' %s%d%%' . "\n", - str_replace($baseLink, '', $classes[$class]['methods'][$method]->link), + str_replace($baseLink, '', $classes[$class]->methods[$method]->link), $methodName, $method, $coverage, @@ -275,7 +273,7 @@ private function projectRisks(array $classes, string $baseLink): array $result = ['class' => '', 'method' => '']; foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { + foreach ($class->methods as $methodName => $method) { if ($method->coverage < $this->thresholds->highLowerBound() && $method->ccn > 1) { $key = $methodName; @@ -287,15 +285,15 @@ private function projectRisks(array $classes, string $baseLink): array } } - if ($class['coverage'] < $this->thresholds->highLowerBound() && - $class['ccn'] > count($class['methods'])) { + if ($class->coverage < $this->thresholds->highLowerBound() && + $class->ccn > count($class->methods)) { $classRisks[$className] = $class; } } - uasort($classRisks, static function (array $a, array $b) + uasort($classRisks, static function (ProcessedClassType|ProcessedTraitType $a, ProcessedClassType|ProcessedTraitType $b) { - return ((int) ($a['crap']) <=> (int) ($b['crap'])) * -1; + return ((int) ($a->crap) <=> (int) ($b->crap)) * -1; }); uasort($methodRisks, static function (ProcessedMethodType $a, ProcessedMethodType $b) { @@ -305,11 +303,11 @@ private function projectRisks(array $classes, string $baseLink): array foreach ($classRisks as $className => $class) { $result['class'] .= sprintf( ' %s%.1f%%%d%d' . "\n", - str_replace($baseLink, '', $classes[$className]['link']), + str_replace($baseLink, '', $classes[$className]->link), $className, - $class['coverage'], - $class['ccn'], - $class['crap'], + $class->coverage, + $class->ccn, + $class->crap, ); } @@ -318,7 +316,7 @@ private function projectRisks(array $classes, string $baseLink): array $result['method'] .= sprintf( ' %s%.1f%%%d%d' . "\n", - str_replace($baseLink, '', $classes[$class]['methods'][$method]->link), + str_replace($baseLink, '', $classes[$class]->methods[$method]->link), $methodName, $method, $methodVals->coverage, diff --git a/src/Report/Html/Renderer/File.php b/src/Report/Html/Renderer/File.php index 81b54b0ae..9564e99e9 100644 --- a/src/Report/Html/Renderer/File.php +++ b/src/Report/Html/Renderer/File.php @@ -102,8 +102,10 @@ use function str_replace; use function token_get_all; use function trim; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException; use SebastianBergmann\CodeCoverage\Node\File as FileNode; use SebastianBergmann\CodeCoverage\Util\Percentage; @@ -111,9 +113,6 @@ 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 File extends Renderer @@ -340,7 +339,7 @@ private function renderTraitOrClassItems(array $items, Template $template, Templ $numMethods = 0; $numTestedMethods = 0; - foreach ($item['methods'] as $method) { + foreach ($item->methods as $method) { if ($method->executableLines > 0) { $numMethods++; @@ -350,20 +349,20 @@ private function renderTraitOrClassItems(array $items, Template $template, Templ } } - if ($item['executableLines'] > 0) { + if ($item->executableLines > 0) { $numClasses = 1; $numTestedClasses = $numTestedMethods === $numMethods ? 1 : 0; $linesExecutedPercentAsString = Percentage::fromFractionAndTotal( - $item['executedLines'], - $item['executableLines'], + $item->executedLines, + $item->executableLines, )->asString(); $branchesExecutedPercentAsString = Percentage::fromFractionAndTotal( - $item['executedBranches'], - $item['executableBranches'], + $item->executedBranches, + $item->executableBranches, )->asString(); $pathsExecutedPercentAsString = Percentage::fromFractionAndTotal( - $item['executedPaths'], - $item['executablePaths'], + $item->executedPaths, + $item->executablePaths, )->asString(); } else { $numClasses = 0; @@ -392,35 +391,35 @@ private function renderTraitOrClassItems(array $items, Template $template, Templ 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => Percentage::fromFractionAndTotal( - $item['executedLines'], - $item['executableLines'], + $item->executedLines, + $item->executableLines, )->asFloat(), 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, - 'numExecutedLines' => $item['executedLines'], - 'numExecutableLines' => $item['executableLines'], + 'numExecutedLines' => $item->executedLines, + 'numExecutableLines' => $item->executableLines, 'branchesExecutedPercent' => Percentage::fromFractionAndTotal( - $item['executedBranches'], - $item['executableBranches'], + $item->executedBranches, + $item->executableBranches, )->asFloat(), 'branchesExecutedPercentAsString' => $branchesExecutedPercentAsString, - 'numExecutedBranches' => $item['executedBranches'], - 'numExecutableBranches' => $item['executableBranches'], + 'numExecutedBranches' => $item->executedBranches, + 'numExecutableBranches' => $item->executableBranches, 'pathsExecutedPercent' => Percentage::fromFractionAndTotal( - $item['executedPaths'], - $item['executablePaths'], + $item->executedPaths, + $item->executablePaths, )->asFloat(), 'pathsExecutedPercentAsString' => $pathsExecutedPercentAsString, - 'numExecutedPaths' => $item['executedPaths'], - 'numExecutablePaths' => $item['executablePaths'], + 'numExecutedPaths' => $item->executedPaths, + 'numExecutablePaths' => $item->executablePaths, 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), 'testedClassesPercent' => $testedClassesPercentage->asFloat(), 'testedClassesPercentAsString' => $testedClassesPercentage->asString(), - 'crap' => $item['crap'], + 'crap' => $item->crap, ], ); - foreach ($item['methods'] as $method) { + foreach ($item->methods as $method) { $buffer .= $this->renderFunctionOrMethodItem( $methodItemTemplate, $method, diff --git a/src/Report/OpenClover.php b/src/Report/OpenClover.php index 90897a650..3c9a7c3d1 100644 --- a/src/Report/OpenClover.php +++ b/src/Report/OpenClover.php @@ -78,11 +78,11 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $classMethods = 0; // Assumption: one namespace per file - if ($class['namespace'] !== '') { - $namespace = $class['namespace']; + if ($class->namespace !== '') { + $namespace = $class->namespace; } - foreach ($class['methods'] as $methodName => $method) { + foreach ($class->methods as $methodName => $method) { /** @phpstan-ignore equal.notAllowed */ if ($method->executableLines == 0) { continue; @@ -115,16 +115,16 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string } $xmlClass = $xmlDocument->createElement('class'); - $xmlClass->setAttribute('name', str_replace($class['namespace'] . '\\', '', $className)); + $xmlClass->setAttribute('name', str_replace($class->namespace . '\\', '', $className)); $xmlFile->appendChild($xmlClass); $xmlMetrics = $xmlDocument->createElement('metrics'); - $xmlMetrics->setAttribute('complexity', (string) $class['ccn']); - $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class['executableBranches'])); - $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class['executedBranches'])); - $xmlMetrics->setAttribute('conditionals', (string) $class['executableBranches']); - $xmlMetrics->setAttribute('coveredconditionals', (string) $class['executedBranches']); + $xmlMetrics->setAttribute('complexity', (string) $class->ccn); + $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class->executableBranches)); + $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class->executedBranches)); + $xmlMetrics->setAttribute('conditionals', (string) $class->executableBranches); + $xmlMetrics->setAttribute('coveredconditionals', (string) $class->executedBranches); $xmlMetrics->setAttribute('statements', (string) $classStatements); $xmlMetrics->setAttribute('coveredstatements', (string) $coveredClassStatements); $xmlMetrics->setAttribute('methods', (string) $classMethods); diff --git a/src/Report/Text.php b/src/Report/Text.php index b2ea332d6..4c8d70986 100644 --- a/src/Report/Text.php +++ b/src/Report/Text.php @@ -190,7 +190,7 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $coveredMethods = 0; $classMethods = 0; - foreach ($class['methods'] as $method) { + foreach ($class->methods as $method) { /** @phpstan-ignore equal.notAllowed */ if ($method->executableLines == 0) { continue; @@ -211,7 +211,7 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin } $classCoverage[$className] = [ - 'namespace' => $class['namespace'], + 'namespace' => $class->namespace, 'className' => $className, 'methodsCovered' => $coveredMethods, 'methodCount' => $classMethods, diff --git a/src/Report/Xml/Facade.php b/src/Report/Xml/Facade.php index 608ac95c0..85346edd4 100644 --- a/src/Report/Xml/Facade.php +++ b/src/Report/Xml/Facade.php @@ -23,10 +23,11 @@ use DateTimeImmutable; use DOMDocument; use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; 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\PathExistsButIsNotDirectoryException; use SebastianBergmann\CodeCoverage\Util\Filesystem; @@ -37,8 +38,6 @@ use SebastianBergmann\Environment\Runtime; /** - * @phpstan-import-type ProcessedClassType from File - * @phpstan-import-type ProcessedTraitType from File * @phpstan-import-type TestType from CodeCoverage */ final class Facade @@ -175,27 +174,24 @@ private function processFile(FileNode $file, Directory $context): void $this->saveDocument($fileReport->asDom(), $file->id()); } - /** - * @param ProcessedClassType|ProcessedTraitType $unit - */ - private function processUnit(array $unit, Report $report): void + private function processUnit(ProcessedClassType|ProcessedTraitType $unit, Report $report): void { - if (isset($unit['className'])) { - $unitObject = $report->classObject($unit['className']); + if ($unit instanceof ProcessedClassType) { + $unitObject = $report->classObject($unit->className); } else { - $unitObject = $report->traitObject($unit['traitName']); + $unitObject = $report->traitObject($unit->traitName); } $unitObject->setLines( - $unit['startLine'], - $unit['executableLines'], - $unit['executedLines'], + $unit->startLine, + $unit->executableLines, + $unit->executedLines, ); - $unitObject->setCrap((float) $unit['crap']); - $unitObject->setNamespace($unit['namespace']); + $unitObject->setCrap((float) $unit->crap); + $unitObject->setNamespace($unit->namespace); - foreach ($unit['methods'] as $method) { + foreach ($unit->methods as $method) { $methodObject = $unitObject->addMethod($method->methodName); $methodObject->setSignature($method->signature); $methodObject->setLines((string) $method->startLine, (string) $method->endLine); From b00d89ab258d4b11665b572005ac14b8ab3fd3bb Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 10 Nov 2025 08:22:27 +0100 Subject: [PATCH 3/4] turn properties readonly --- src/Data/ProcessedClassType.php | 8 ++++---- src/Data/ProcessedFunctionType.php | 12 ++++++------ src/Data/ProcessedMethodType.php | 12 ++++++------ src/Data/ProcessedTraitType.php | 8 ++++---- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Data/ProcessedClassType.php b/src/Data/ProcessedClassType.php index 879c7f0b2..a8a02e8e0 100644 --- a/src/Data/ProcessedClassType.php +++ b/src/Data/ProcessedClassType.php @@ -12,13 +12,13 @@ final class ProcessedClassType { public function __construct( - public string $className, - public string $namespace, + public readonly string $className, + public readonly string $namespace, /** * @var array */ public array $methods, - public int $startLine, + public readonly int $startLine, public int $executableLines, public int $executedLines, public int $executableBranches, @@ -28,7 +28,7 @@ public function __construct( public int $ccn, public float|int $coverage, public int|string $crap, - public string $link, + public readonly string $link, ) { } } diff --git a/src/Data/ProcessedFunctionType.php b/src/Data/ProcessedFunctionType.php index 6ab07d714..46fe75673 100644 --- a/src/Data/ProcessedFunctionType.php +++ b/src/Data/ProcessedFunctionType.php @@ -12,11 +12,11 @@ final class ProcessedFunctionType { public function __construct( - public string $functionName, - public string $namespace, - public string $signature, - public int $startLine, - public int $endLine, + public readonly string $functionName, + public readonly string $namespace, + public readonly string $signature, + public readonly int $startLine, + public readonly int $endLine, public int $executableLines, public int $executedLines, public int $executableBranches, @@ -26,7 +26,7 @@ public function __construct( public int $ccn, public float|int $coverage, public int|string $crap, - public string $link, + public readonly string $link, ) { } } diff --git a/src/Data/ProcessedMethodType.php b/src/Data/ProcessedMethodType.php index 63353aefd..f43b734cb 100644 --- a/src/Data/ProcessedMethodType.php +++ b/src/Data/ProcessedMethodType.php @@ -12,11 +12,11 @@ final class ProcessedMethodType { public function __construct( - public string $methodName, - public string $visibility, - public string $signature, - public int $startLine, - public int $endLine, + public readonly string $methodName, + public readonly string $visibility, + public readonly string $signature, + public readonly int $startLine, + public readonly int $endLine, public int $executableLines, public int $executedLines, public int $executableBranches, @@ -26,7 +26,7 @@ public function __construct( public int $ccn, public float|int $coverage, public int|string $crap, - public string $link, + public readonly string $link, ) { } } diff --git a/src/Data/ProcessedTraitType.php b/src/Data/ProcessedTraitType.php index 8d133c85c..79a105e00 100644 --- a/src/Data/ProcessedTraitType.php +++ b/src/Data/ProcessedTraitType.php @@ -12,13 +12,13 @@ final class ProcessedTraitType { public function __construct( - public string $traitName, - public string $namespace, + public readonly string $traitName, + public readonly string $namespace, /** * @var array */ public array $methods, - public int $startLine, + public readonly int $startLine, public int $executableLines, public int $executedLines, public int $executableBranches, @@ -28,7 +28,7 @@ public function __construct( public int $ccn, public float|int $coverage, public int|string $crap, - public string $link, + public readonly string $link, ) { } } From 131990286826b8a21c464681d1fca1eb71f907f7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 10 Nov 2025 08:26:48 +0100 Subject: [PATCH 4/4] new classes are internal --- src/Data/ProcessedClassType.php | 3 +++ src/Data/ProcessedFunctionType.php | 3 +++ src/Data/ProcessedMethodType.php | 3 +++ src/Data/ProcessedTraitType.php | 3 +++ 4 files changed, 12 insertions(+) diff --git a/src/Data/ProcessedClassType.php b/src/Data/ProcessedClassType.php index a8a02e8e0..fc49007c6 100644 --- a/src/Data/ProcessedClassType.php +++ b/src/Data/ProcessedClassType.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\CodeCoverage\Data; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ final class ProcessedClassType { public function __construct( diff --git a/src/Data/ProcessedFunctionType.php b/src/Data/ProcessedFunctionType.php index 46fe75673..8a3c0725e 100644 --- a/src/Data/ProcessedFunctionType.php +++ b/src/Data/ProcessedFunctionType.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\CodeCoverage\Data; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ final class ProcessedFunctionType { public function __construct( diff --git a/src/Data/ProcessedMethodType.php b/src/Data/ProcessedMethodType.php index f43b734cb..cd7e39313 100644 --- a/src/Data/ProcessedMethodType.php +++ b/src/Data/ProcessedMethodType.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\CodeCoverage\Data; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ final class ProcessedMethodType { public function __construct( diff --git a/src/Data/ProcessedTraitType.php b/src/Data/ProcessedTraitType.php index 79a105e00..b92385ed3 100644 --- a/src/Data/ProcessedTraitType.php +++ b/src/Data/ProcessedTraitType.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\CodeCoverage\Data; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ final class ProcessedTraitType { public function __construct(