* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Html; use function array_pop; use function count; use function sprintf; use function str_repeat; use function substr_count; use SebastianBergmann\CodeCoverage\Node\AbstractNode; use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; use SebastianBergmann\CodeCoverage\Node\File as FileNode; use SebastianBergmann\CodeCoverage\Version; use SebastianBergmann\Environment\Runtime; use SebastianBergmann\Template\Template; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ abstract class Renderer { /** * @var string */ protected $templatePath; /** * @var string */ protected $generator; /** * @var string */ protected $date; /** * @var int */ protected $lowUpperBound; /** * @var int */ protected $highLowerBound; /** * @var bool */ protected $hasBranchCoverage; /** * @var string */ protected $version; public function __construct(string $templatePath, string $generator, string $date, int $lowUpperBound, int $highLowerBound, bool $hasBranchCoverage) { $this->templatePath = $templatePath; $this->generator = $generator; $this->date = $date; $this->lowUpperBound = $lowUpperBound; $this->highLowerBound = $highLowerBound; $this->version = Version::id(); $this->hasBranchCoverage = $hasBranchCoverage; } protected function renderItemTemplate(Template $template, array $data): string { $numSeparator = ' / '; if (isset($data['numClasses']) && $data['numClasses'] > 0) { $classesLevel = $this->colorLevel($data['testedClassesPercent']); $classesNumber = $data['numTestedClasses'] . $numSeparator . $data['numClasses']; $classesBar = $this->coverageBar( $data['testedClassesPercent'] ); } else { $classesLevel = ''; $classesNumber = '0' . $numSeparator . '0'; $classesBar = ''; $data['testedClassesPercentAsString'] = 'n/a'; } if ($data['numMethods'] > 0) { $methodsLevel = $this->colorLevel($data['testedMethodsPercent']); $methodsNumber = $data['numTestedMethods'] . $numSeparator . $data['numMethods']; $methodsBar = $this->coverageBar( $data['testedMethodsPercent'] ); } else { $methodsLevel = ''; $methodsNumber = '0' . $numSeparator . '0'; $methodsBar = ''; $data['testedMethodsPercentAsString'] = 'n/a'; } if ($data['numExecutableLines'] > 0) { $linesLevel = $this->colorLevel($data['linesExecutedPercent']); $linesNumber = $data['numExecutedLines'] . $numSeparator . $data['numExecutableLines']; $linesBar = $this->coverageBar( $data['linesExecutedPercent'] ); } else { $linesLevel = ''; $linesNumber = '0' . $numSeparator . '0'; $linesBar = ''; $data['linesExecutedPercentAsString'] = 'n/a'; } if ($data['numExecutablePaths'] > 0) { $pathsLevel = $this->colorLevel($data['pathsExecutedPercent']); $pathsNumber = $data['numExecutedPaths'] . $numSeparator . $data['numExecutablePaths']; $pathsBar = $this->coverageBar( $data['pathsExecutedPercent'] ); } else { $pathsLevel = ''; $pathsNumber = '0' . $numSeparator . '0'; $pathsBar = ''; $data['pathsExecutedPercentAsString'] = 'n/a'; } if ($data['numExecutableBranches'] > 0) { $branchesLevel = $this->colorLevel($data['branchesExecutedPercent']); $branchesNumber = $data['numExecutedBranches'] . $numSeparator . $data['numExecutableBranches']; $branchesBar = $this->coverageBar( $data['branchesExecutedPercent'] ); } else { $branchesLevel = ''; $branchesNumber = '0' . $numSeparator . '0'; $branchesBar = ''; $data['branchesExecutedPercentAsString'] = 'n/a'; } $template->setVar( [ 'icon' => $data['icon'] ?? '', 'crap' => $data['crap'] ?? '', 'name' => $data['name'], 'lines_bar' => $linesBar, 'lines_executed_percent' => $data['linesExecutedPercentAsString'], 'lines_level' => $linesLevel, 'lines_number' => $linesNumber, 'paths_bar' => $pathsBar, 'paths_executed_percent' => $data['pathsExecutedPercentAsString'], 'paths_level' => $pathsLevel, 'paths_number' => $pathsNumber, 'branches_bar' => $branchesBar, 'branches_executed_percent' => $data['branchesExecutedPercentAsString'], 'branches_level' => $branchesLevel, 'branches_number' => $branchesNumber, 'methods_bar' => $methodsBar, 'methods_tested_percent' => $data['testedMethodsPercentAsString'], 'methods_level' => $methodsLevel, 'methods_number' => $methodsNumber, 'classes_bar' => $classesBar, 'classes_tested_percent' => $data['testedClassesPercentAsString'] ?? '', 'classes_level' => $classesLevel, 'classes_number' => $classesNumber, ] ); return $template->render(); } protected function setCommonTemplateVariables(Template $template, AbstractNode $node): void { $template->setVar( [ 'id' => $node->id(), 'full_path' => $node->pathAsString(), 'path_to_root' => $this->pathToRoot($node), 'breadcrumbs' => $this->breadcrumbs($node), 'date' => $this->date, 'version' => $this->version, 'runtime' => $this->runtimeString(), 'generator' => $this->generator, 'low_upper_bound' => $this->lowUpperBound, 'high_lower_bound' => $this->highLowerBound, ] ); } protected function breadcrumbs(AbstractNode $node): string { $breadcrumbs = ''; $path = $node->pathAsArray(); $pathToRoot = []; $max = count($path); if ($node instanceof FileNode) { $max--; } for ($i = 0; $i < $max; $i++) { $pathToRoot[] = str_repeat('../', $i); } foreach ($path as $step) { if ($step !== $node) { $breadcrumbs .= $this->inactiveBreadcrumb( $step, array_pop($pathToRoot) ); } else { $breadcrumbs .= $this->activeBreadcrumb($step); } } return $breadcrumbs; } protected function activeBreadcrumb(AbstractNode $node): string { $buffer = sprintf( ' ' . "\n", $node->name() ); if ($node instanceof DirectoryNode) { $buffer .= ' ' . "\n"; } return $buffer; } protected function inactiveBreadcrumb(AbstractNode $node, string $pathToRoot): string { return sprintf( ' ' . "\n", $pathToRoot, $node->name() ); } protected function pathToRoot(AbstractNode $node): string { $id = $node->id(); $depth = substr_count($id, '/'); if ($id !== 'index' && $node instanceof DirectoryNode) { $depth++; } return str_repeat('../', $depth); } protected function coverageBar(float $percent): string { $level = $this->colorLevel($percent); $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'coverage_bar_branch.html' : 'coverage_bar.html'); $template = new Template( $templateName, '{{', '}}' ); $template->setVar(['level' => $level, 'percent' => sprintf('%.2F', $percent)]); return $template->render(); } protected function colorLevel(float $percent): string { if ($percent <= $this->lowUpperBound) { return 'danger'; } if ($percent > $this->lowUpperBound && $percent < $this->highLowerBound) { return 'warning'; } return 'success'; } private function runtimeString(): string { $runtime = new Runtime; return sprintf( '%s %s', $runtime->getVendorUrl(), $runtime->getName(), $runtime->getVersion() ); } }