<?php
if (!defined('T_TRAIT')) {
define('T_TRAIT', 1001);
}
if (!defined('T_INSTEADOF')) {
define('T_INSTEADOF', 1002);
}
if (!defined('T_CALLABLE')) {
define('T_CALLABLE', 1003);
}
class PHP_CodeCoverage_Report_HTML_Renderer_File extends PHP_CodeCoverage_Report_HTML_Renderer {
protected $highlight;
public function __construct($templatePath, $charset, $generator, $date, $lowUpperBound, $highLowerBound, $highlight) {
parent::__construct($templatePath, $charset, $generator, $date, $lowUpperBound, $highLowerBound);
$this->highlight = $highlight;
}
public function render(PHP_CodeCoverage_Report_Node_File $node, $file) {
$template = new Text_Template($this->templatePath . 'file.html');
$template
->setVar(array(
'items' => $this
->renderItems($node),
'lines' => $this
->renderSource($node),
));
$this
->setCommonTemplateVariables($template, $node);
$template
->renderTo($file);
}
protected function renderItems(PHP_CodeCoverage_Report_Node_File $node) {
$template = new Text_Template($this->templatePath . 'file_item.html');
$methodItemTemplate = new Text_Template($this->templatePath . 'method_item.html');
$items = $this
->renderItemTemplate($template, array(
'name' => 'Total',
'numClasses' => $node
->getNumClassesAndTraits(),
'numTestedClasses' => $node
->getNumTestedClassesAndTraits(),
'numMethods' => $node
->getNumMethods(),
'numTestedMethods' => $node
->getNumTestedMethods(),
'linesExecutedPercent' => $node
->getLineExecutedPercent(FALSE),
'linesExecutedPercentAsString' => $node
->getLineExecutedPercent(),
'numExecutedLines' => $node
->getNumExecutedLines(),
'numExecutableLines' => $node
->getNumExecutableLines(),
'testedMethodsPercent' => $node
->getTestedMethodsPercent(FALSE),
'testedMethodsPercentAsString' => $node
->getTestedMethodsPercent(),
'testedClassesPercent' => $node
->getTestedClassesAndTraitsPercent(FALSE),
'testedClassesPercentAsString' => $node
->getTestedClassesAndTraitsPercent(),
'crap' => '<acronym title="Change Risk Anti-Patterns (CRAP) Index">CRAP</acronym>',
));
$items .= $this
->renderFunctionItems($node
->getFunctions(), $methodItemTemplate);
$items .= $this
->renderTraitOrClassItems($node
->getTraits(), $template, $methodItemTemplate);
$items .= $this
->renderTraitOrClassItems($node
->getClasses(), $template, $methodItemTemplate);
return $items;
}
protected function renderTraitOrClassItems(array $items, Text_Template $template, Text_Template $methodItemTemplate) {
if (empty($items)) {
return '';
}
$buffer = '';
foreach ($items as $name => $item) {
$numMethods = count($item['methods']);
$numTestedMethods = 0;
foreach ($item['methods'] as $method) {
if ($method['executedLines'] == $method['executableLines']) {
$numTestedMethods++;
}
}
$buffer .= $this
->renderItemTemplate($template, array(
'name' => $name,
'numClasses' => 1,
'numTestedClasses' => $numTestedMethods == $numMethods ? 1 : 0,
'numMethods' => $numMethods,
'numTestedMethods' => $numTestedMethods,
'linesExecutedPercent' => PHP_CodeCoverage_Util::percent($item['executedLines'], $item['executableLines'], FALSE),
'linesExecutedPercentAsString' => PHP_CodeCoverage_Util::percent($item['executedLines'], $item['executableLines'], TRUE),
'numExecutedLines' => $item['executedLines'],
'numExecutableLines' => $item['executableLines'],
'testedMethodsPercent' => PHP_CodeCoverage_Util::percent($numTestedMethods, $numMethods, FALSE),
'testedMethodsPercentAsString' => PHP_CodeCoverage_Util::percent($numTestedMethods, $numMethods, TRUE),
'testedClassesPercent' => PHP_CodeCoverage_Util::percent($numTestedMethods == $numMethods ? 1 : 0, 1, FALSE),
'testedClassesPercentAsString' => PHP_CodeCoverage_Util::percent($numTestedMethods == $numMethods ? 1 : 0, 1, TRUE),
'crap' => $item['crap'],
));
foreach ($item['methods'] as $method) {
$buffer .= $this
->renderFunctionOrMethodItem($methodItemTemplate, $method, ' ');
}
}
return $buffer;
}
protected function renderFunctionItems(array $functions, Text_Template $template) {
if (empty($functions)) {
return '';
}
$buffer = '';
foreach ($functions as $function) {
$buffer .= $this
->renderFunctionOrMethodItem($template, $function);
}
return $buffer;
}
protected function renderFunctionOrMethodItem(Text_Template $template, array $item, $indent = '') {
$numTestedItems = $item['executedLines'] == $item['executableLines'] ? 1 : 0;
return $this
->renderItemTemplate($template, array(
'name' => sprintf('%s<a href="#%d">%s</a>', $indent, $item['startLine'], htmlspecialchars($item['signature'])),
'numMethods' => 1,
'numTestedMethods' => $numTestedItems,
'linesExecutedPercent' => PHP_CodeCoverage_Util::percent($item['executedLines'], $item['executableLines'], FALSE),
'linesExecutedPercentAsString' => PHP_CodeCoverage_Util::percent($item['executedLines'], $item['executableLines'], TRUE),
'numExecutedLines' => $item['executedLines'],
'numExecutableLines' => $item['executableLines'],
'testedMethodsPercent' => PHP_CodeCoverage_Util::percent($numTestedItems, 1, FALSE),
'testedMethodsPercentAsString' => PHP_CodeCoverage_Util::percent($numTestedItems, 1, TRUE),
'crap' => $item['crap'],
));
}
protected function renderSource(PHP_CodeCoverage_Report_Node_File $node) {
$coverageData = $node
->getCoverageData();
$ignoredLines = $node
->getIgnoredLines();
$testData = $node
->getTestData();
$codeLines = $this
->loadFile($node
->getPath());
$lines = '';
$i = 1;
foreach ($codeLines as $line) {
$numTests = '';
$trClass = '';
$popoverContent = '';
$popoverTitle = '';
if (!isset($ignoredLines[$i]) && isset($coverageData[$i])) {
$numTests = count($coverageData[$i]);
if ($coverageData[$i] === NULL) {
$trClass = ' class="warning"';
}
else {
if ($numTests == 0) {
$trClass = ' class="danger"';
}
else {
$trClass = ' class="success popin"';
$popoverContent = '<ul>';
if ($numTests > 1) {
$popoverTitle = $numTests . ' tests cover line ' . $i;
}
else {
$popoverTitle = '1 test covers line ' . $i;
}
foreach ($coverageData[$i] as $test) {
switch ($testData[$test]) {
case 0:
$testCSS = ' class="success"';
break;
case 1:
case 2:
$testCSS = ' class="warning"';
break;
case 3:
$testCSS = ' class="danger"';
break;
case 4:
$testCSS = ' class="danger"';
break;
default:
$testCSS = '';
}
$popoverContent .= sprintf('<li%s>%s</li>', $testCSS, htmlspecialchars($test));
}
$popoverContent .= '</ul>';
}
}
}
if (!empty($popoverTitle)) {
$popover = sprintf(' data-title="%s" data-content="%s" data-placement="bottom" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent));
}
else {
$popover = '';
}
$lines .= sprintf(' <tr%s%s><td><div align="right"><a name="%d"></a><a href="#%d">%d</a></div></td><td class="codeLine">%s</td></tr>' . "\n", $trClass, $popover, $i, $i, $i, !$this->highlight ? htmlspecialchars($line) : $line);
$i++;
}
return $lines;
}
protected function loadFile($file) {
$buffer = file_get_contents($file);
$lines = explode("\n", str_replace("\t", ' ', $buffer));
$result = array();
if (count($lines) == 0) {
return $result;
}
$lines = array_map('rtrim', $lines);
if (!$this->highlight) {
unset($lines[count($lines) - 1]);
return $lines;
}
$tokens = token_get_all($buffer);
$stringFlag = FALSE;
$i = 0;
$result[$i] = '';
foreach ($tokens as $j => $token) {
if (is_string($token)) {
if ($token === '"' && $tokens[$j - 1] !== '\\') {
$result[$i] .= sprintf('<span class="string">%s</span>', htmlspecialchars($token));
$stringFlag = !$stringFlag;
}
else {
$result[$i] .= sprintf('<span class="keyword">%s</span>', htmlspecialchars($token));
}
continue;
}
list($token, $value) = $token;
$value = str_replace(array(
"\t",
' ',
), array(
' ',
' ',
), htmlspecialchars($value));
if ($value === "\n") {
$result[++$i] = '';
}
else {
$lines = explode("\n", $value);
foreach ($lines as $jj => $line) {
$line = trim($line);
if ($line !== '') {
if ($stringFlag) {
$colour = 'string';
}
else {
switch ($token) {
case T_INLINE_HTML:
$colour = 'html';
break;
case T_COMMENT:
case T_DOC_COMMENT:
$colour = 'comment';
break;
case T_ABSTRACT:
case T_ARRAY:
case T_AS:
case T_BREAK:
case T_CALLABLE:
case T_CASE:
case T_CATCH:
case T_CLASS:
case T_CLONE:
case T_CONTINUE:
case T_DEFAULT:
case T_ECHO:
case T_ELSE:
case T_ELSEIF:
case T_EMPTY:
case T_ENDDECLARE:
case T_ENDFOR:
case T_ENDFOREACH:
case T_ENDIF:
case T_ENDSWITCH:
case T_ENDWHILE:
case T_EXIT:
case T_EXTENDS:
case T_FINAL:
case T_FOREACH:
case T_FUNCTION:
case T_GLOBAL:
case T_IF:
case T_IMPLEMENTS:
case T_INCLUDE:
case T_INCLUDE_ONCE:
case T_INSTANCEOF:
case T_INSTEADOF:
case T_INTERFACE:
case T_ISSET:
case T_LOGICAL_AND:
case T_LOGICAL_OR:
case T_LOGICAL_XOR:
case T_NAMESPACE:
case T_NEW:
case T_PRIVATE:
case T_PROTECTED:
case T_PUBLIC:
case T_REQUIRE:
case T_REQUIRE_ONCE:
case T_RETURN:
case T_STATIC:
case T_THROW:
case T_TRAIT:
case T_TRY:
case T_UNSET:
case T_USE:
case T_VAR:
case T_WHILE:
$colour = 'keyword';
break;
default:
$colour = 'default';
}
}
$result[$i] .= sprintf('<span class="%s">%s</span>', $colour, $line);
}
if (isset($lines[$jj + 1])) {
$result[++$i] = '';
}
}
}
}
unset($result[count($result) - 1]);
return $result;
}
}