Chris@14: Chris@14: * Chris@14: * For the full copyright and license information, please view the LICENSE Chris@14: * file that was distributed with this source code. Chris@14: */ Chris@14: Chris@14: namespace SebastianBergmann\CodeCoverage\Report\Html; Chris@14: Chris@14: use SebastianBergmann\CodeCoverage\Node\File as FileNode; Chris@14: use SebastianBergmann\CodeCoverage\Util; Chris@14: Chris@14: /** Chris@14: * Renders a file node. Chris@14: */ Chris@14: class File extends Renderer Chris@14: { Chris@14: /** Chris@14: * @var int Chris@14: */ Chris@14: private $htmlspecialcharsFlags; Chris@14: Chris@14: /** Chris@14: * Constructor. Chris@14: * Chris@14: * @param string $templatePath Chris@14: * @param string $generator Chris@14: * @param string $date Chris@14: * @param int $lowUpperBound Chris@14: * @param int $highLowerBound Chris@14: */ Chris@14: public function __construct($templatePath, $generator, $date, $lowUpperBound, $highLowerBound) Chris@14: { Chris@14: parent::__construct( Chris@14: $templatePath, Chris@14: $generator, Chris@14: $date, Chris@14: $lowUpperBound, Chris@14: $highLowerBound Chris@14: ); Chris@14: Chris@14: $this->htmlspecialcharsFlags = ENT_COMPAT; Chris@14: Chris@14: $this->htmlspecialcharsFlags = $this->htmlspecialcharsFlags | ENT_HTML401 | ENT_SUBSTITUTE; Chris@14: } Chris@14: Chris@14: /** Chris@14: * @param FileNode $node Chris@14: * @param string $file Chris@14: */ Chris@14: public function render(FileNode $node, $file) Chris@14: { Chris@14: $template = new \Text_Template($this->templatePath . 'file.html', '{{', '}}'); Chris@14: Chris@14: $template->setVar( Chris@14: [ Chris@14: 'items' => $this->renderItems($node), Chris@14: 'lines' => $this->renderSource($node) Chris@14: ] Chris@14: ); Chris@14: Chris@14: $this->setCommonTemplateVariables($template, $node); Chris@14: Chris@14: $template->renderTo($file); Chris@14: } Chris@14: Chris@14: /** Chris@14: * @param FileNode $node Chris@14: * Chris@14: * @return string Chris@14: */ Chris@14: protected function renderItems(FileNode $node) Chris@14: { Chris@14: $template = new \Text_Template($this->templatePath . 'file_item.html', '{{', '}}'); Chris@14: Chris@14: $methodItemTemplate = new \Text_Template( Chris@14: $this->templatePath . 'method_item.html', Chris@14: '{{', Chris@14: '}}' Chris@14: ); Chris@14: Chris@14: $items = $this->renderItemTemplate( Chris@14: $template, Chris@14: [ Chris@14: 'name' => 'Total', Chris@14: 'numClasses' => $node->getNumClassesAndTraits(), Chris@14: 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), Chris@14: 'numMethods' => $node->getNumFunctionsAndMethods(), Chris@14: 'numTestedMethods' => $node->getNumTestedFunctionsAndMethods(), Chris@14: 'linesExecutedPercent' => $node->getLineExecutedPercent(false), Chris@14: 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), Chris@14: 'numExecutedLines' => $node->getNumExecutedLines(), Chris@14: 'numExecutableLines' => $node->getNumExecutableLines(), Chris@14: 'testedMethodsPercent' => $node->getTestedFunctionsAndMethodsPercent(false), Chris@14: 'testedMethodsPercentAsString' => $node->getTestedFunctionsAndMethodsPercent(), Chris@14: 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), Chris@14: 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), Chris@14: 'crap' => 'CRAP' Chris@14: ] Chris@14: ); Chris@14: Chris@14: $items .= $this->renderFunctionItems( Chris@14: $node->getFunctions(), Chris@14: $methodItemTemplate Chris@14: ); Chris@14: Chris@14: $items .= $this->renderTraitOrClassItems( Chris@14: $node->getTraits(), Chris@14: $template, Chris@14: $methodItemTemplate Chris@14: ); Chris@14: Chris@14: $items .= $this->renderTraitOrClassItems( Chris@14: $node->getClasses(), Chris@14: $template, Chris@14: $methodItemTemplate Chris@14: ); Chris@14: Chris@14: return $items; Chris@14: } Chris@14: Chris@14: /** Chris@14: * @param array $items Chris@14: * @param \Text_Template $template Chris@14: * @param \Text_Template $methodItemTemplate Chris@14: * Chris@14: * @return string Chris@14: */ Chris@14: protected function renderTraitOrClassItems(array $items, \Text_Template $template, \Text_Template $methodItemTemplate) Chris@14: { Chris@14: $buffer = ''; Chris@14: Chris@14: if (empty($items)) { Chris@14: return $buffer; Chris@14: } Chris@14: Chris@14: foreach ($items as $name => $item) { Chris@14: $numMethods = 0; Chris@14: $numTestedMethods = 0; Chris@14: Chris@14: foreach ($item['methods'] as $method) { Chris@14: if ($method['executableLines'] > 0) { Chris@14: $numMethods++; Chris@14: Chris@14: if ($method['executedLines'] === $method['executableLines']) { Chris@14: $numTestedMethods++; Chris@14: } Chris@14: } Chris@14: } Chris@14: Chris@14: if ($item['executableLines'] > 0) { Chris@14: $numClasses = 1; Chris@14: $numTestedClasses = $numTestedMethods == $numMethods ? 1 : 0; Chris@14: $linesExecutedPercentAsString = Util::percent( Chris@14: $item['executedLines'], Chris@14: $item['executableLines'], Chris@14: true Chris@14: ); Chris@14: } else { Chris@14: $numClasses = 'n/a'; Chris@14: $numTestedClasses = 'n/a'; Chris@14: $linesExecutedPercentAsString = 'n/a'; Chris@14: } Chris@14: Chris@14: $buffer .= $this->renderItemTemplate( Chris@14: $template, Chris@14: [ Chris@14: 'name' => $name, Chris@14: 'numClasses' => $numClasses, Chris@14: 'numTestedClasses' => $numTestedClasses, Chris@14: 'numMethods' => $numMethods, Chris@14: 'numTestedMethods' => $numTestedMethods, Chris@14: 'linesExecutedPercent' => Util::percent( Chris@14: $item['executedLines'], Chris@14: $item['executableLines'], Chris@14: false Chris@14: ), Chris@14: 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, Chris@14: 'numExecutedLines' => $item['executedLines'], Chris@14: 'numExecutableLines' => $item['executableLines'], Chris@14: 'testedMethodsPercent' => Util::percent( Chris@14: $numTestedMethods, Chris@14: $numMethods, Chris@14: false Chris@14: ), Chris@14: 'testedMethodsPercentAsString' => Util::percent( Chris@14: $numTestedMethods, Chris@14: $numMethods, Chris@14: true Chris@14: ), Chris@14: 'testedClassesPercent' => Util::percent( Chris@14: $numTestedMethods == $numMethods ? 1 : 0, Chris@14: 1, Chris@14: false Chris@14: ), Chris@14: 'testedClassesPercentAsString' => Util::percent( Chris@14: $numTestedMethods == $numMethods ? 1 : 0, Chris@14: 1, Chris@14: true Chris@14: ), Chris@14: 'crap' => $item['crap'] Chris@14: ] Chris@14: ); Chris@14: Chris@14: foreach ($item['methods'] as $method) { Chris@14: $buffer .= $this->renderFunctionOrMethodItem( Chris@14: $methodItemTemplate, Chris@14: $method, Chris@14: ' ' Chris@14: ); Chris@14: } Chris@14: } Chris@14: Chris@14: return $buffer; Chris@14: } Chris@14: Chris@14: /** Chris@14: * @param array $functions Chris@14: * @param \Text_Template $template Chris@14: * Chris@14: * @return string Chris@14: */ Chris@14: protected function renderFunctionItems(array $functions, \Text_Template $template) Chris@14: { Chris@14: if (empty($functions)) { Chris@14: return ''; Chris@14: } Chris@14: Chris@14: $buffer = ''; Chris@14: Chris@14: foreach ($functions as $function) { Chris@14: $buffer .= $this->renderFunctionOrMethodItem( Chris@14: $template, Chris@14: $function Chris@14: ); Chris@14: } Chris@14: Chris@14: return $buffer; Chris@14: } Chris@14: Chris@14: /** Chris@14: * @param \Text_Template $template Chris@14: * Chris@14: * @return string Chris@14: */ Chris@14: protected function renderFunctionOrMethodItem(\Text_Template $template, array $item, $indent = '') Chris@14: { Chris@14: $numMethods = 0; Chris@14: $numTestedMethods = 0; Chris@14: Chris@14: if ($item['executableLines'] > 0) { Chris@14: $numMethods = 1; Chris@14: Chris@14: if ($item['executedLines'] === $item['executableLines']) { Chris@14: $numTestedMethods = 1; Chris@14: } Chris@14: } Chris@14: Chris@14: return $this->renderItemTemplate( Chris@14: $template, Chris@14: [ Chris@14: 'name' => \sprintf( Chris@14: '%s%s', Chris@14: $indent, Chris@14: $item['startLine'], Chris@14: \htmlspecialchars($item['signature']), Chris@14: isset($item['functionName']) ? $item['functionName'] : $item['methodName'] Chris@14: ), Chris@14: 'numMethods' => $numMethods, Chris@14: 'numTestedMethods' => $numTestedMethods, Chris@14: 'linesExecutedPercent' => Util::percent( Chris@14: $item['executedLines'], Chris@14: $item['executableLines'], Chris@14: false Chris@14: ), Chris@14: 'linesExecutedPercentAsString' => Util::percent( Chris@14: $item['executedLines'], Chris@14: $item['executableLines'], Chris@14: true Chris@14: ), Chris@14: 'numExecutedLines' => $item['executedLines'], Chris@14: 'numExecutableLines' => $item['executableLines'], Chris@14: 'testedMethodsPercent' => Util::percent( Chris@14: $numTestedMethods, Chris@14: 1, Chris@14: false Chris@14: ), Chris@14: 'testedMethodsPercentAsString' => Util::percent( Chris@14: $numTestedMethods, Chris@14: 1, Chris@14: true Chris@14: ), Chris@14: 'crap' => $item['crap'] Chris@14: ] Chris@14: ); Chris@14: } Chris@14: Chris@14: /** Chris@14: * @param FileNode $node Chris@14: * Chris@14: * @return string Chris@14: */ Chris@14: protected function renderSource(FileNode $node) Chris@14: { Chris@14: $coverageData = $node->getCoverageData(); Chris@14: $testData = $node->getTestData(); Chris@14: $codeLines = $this->loadFile($node->getPath()); Chris@14: $lines = ''; Chris@14: $i = 1; Chris@14: Chris@14: foreach ($codeLines as $line) { Chris@14: $trClass = ''; Chris@14: $popoverContent = ''; Chris@14: $popoverTitle = ''; Chris@14: Chris@14: if (\array_key_exists($i, $coverageData)) { Chris@14: $numTests = ($coverageData[$i] ? \count($coverageData[$i]) : 0); Chris@14: Chris@14: if ($coverageData[$i] === null) { Chris@14: $trClass = ' class="warning"'; Chris@14: } elseif ($numTests == 0) { Chris@14: $trClass = ' class="danger"'; Chris@14: } else { Chris@14: $lineCss = 'covered-by-large-tests'; Chris@14: $popoverContent = '