Clover.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of phpunit/php-code-coverage.
  4. *
  5. * (c) Sebastian Bergmann <sebastian@phpunit.de>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace SebastianBergmann\CodeCoverage\Report;
  11. use function count;
  12. use function dirname;
  13. use function file_put_contents;
  14. use function is_string;
  15. use function ksort;
  16. use function max;
  17. use function range;
  18. use function str_contains;
  19. use function time;
  20. use DOMDocument;
  21. use SebastianBergmann\CodeCoverage\CodeCoverage;
  22. use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException;
  23. use SebastianBergmann\CodeCoverage\Node\File;
  24. use SebastianBergmann\CodeCoverage\Util\Filesystem;
  25. final class Clover
  26. {
  27. /**
  28. * @throws WriteOperationFailedException
  29. */
  30. public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string
  31. {
  32. $time = (string) time();
  33. $xmlDocument = new DOMDocument('1.0', 'UTF-8');
  34. $xmlDocument->formatOutput = true;
  35. $xmlCoverage = $xmlDocument->createElement('coverage');
  36. $xmlCoverage->setAttribute('generated', $time);
  37. $xmlDocument->appendChild($xmlCoverage);
  38. $xmlProject = $xmlDocument->createElement('project');
  39. $xmlProject->setAttribute('timestamp', $time);
  40. if (is_string($name)) {
  41. $xmlProject->setAttribute('name', $name);
  42. }
  43. $xmlCoverage->appendChild($xmlProject);
  44. $packages = [];
  45. $report = $coverage->getReport();
  46. foreach ($report as $item) {
  47. if (!$item instanceof File) {
  48. continue;
  49. }
  50. /* @var File $item */
  51. $xmlFile = $xmlDocument->createElement('file');
  52. $xmlFile->setAttribute('name', $item->pathAsString());
  53. $classes = $item->classesAndTraits();
  54. $coverageData = $item->lineCoverageData();
  55. $lines = [];
  56. $namespace = 'global';
  57. foreach ($classes as $className => $class) {
  58. $classStatements = 0;
  59. $coveredClassStatements = 0;
  60. $coveredMethods = 0;
  61. $classMethods = 0;
  62. foreach ($class['methods'] as $methodName => $method) {
  63. if ($method['executableLines'] == 0) {
  64. continue;
  65. }
  66. $classMethods++;
  67. $classStatements += $method['executableLines'];
  68. $coveredClassStatements += $method['executedLines'];
  69. if ($method['coverage'] == 100) {
  70. $coveredMethods++;
  71. }
  72. $methodCount = 0;
  73. foreach (range($method['startLine'], $method['endLine']) as $line) {
  74. if (isset($coverageData[$line])) {
  75. $methodCount = max($methodCount, count($coverageData[$line]));
  76. }
  77. }
  78. $lines[$method['startLine']] = [
  79. 'ccn' => $method['ccn'],
  80. 'count' => $methodCount,
  81. 'crap' => $method['crap'],
  82. 'type' => 'method',
  83. 'visibility' => $method['visibility'],
  84. 'name' => $methodName,
  85. ];
  86. }
  87. if (!empty($class['package']['namespace'])) {
  88. $namespace = $class['package']['namespace'];
  89. }
  90. $xmlClass = $xmlDocument->createElement('class');
  91. $xmlClass->setAttribute('name', $className);
  92. $xmlClass->setAttribute('namespace', $namespace);
  93. if (!empty($class['package']['fullPackage'])) {
  94. $xmlClass->setAttribute(
  95. 'fullPackage',
  96. $class['package']['fullPackage'],
  97. );
  98. }
  99. if (!empty($class['package']['category'])) {
  100. $xmlClass->setAttribute(
  101. 'category',
  102. $class['package']['category'],
  103. );
  104. }
  105. if (!empty($class['package']['package'])) {
  106. $xmlClass->setAttribute(
  107. 'package',
  108. $class['package']['package'],
  109. );
  110. }
  111. if (!empty($class['package']['subpackage'])) {
  112. $xmlClass->setAttribute(
  113. 'subpackage',
  114. $class['package']['subpackage'],
  115. );
  116. }
  117. $xmlFile->appendChild($xmlClass);
  118. $xmlMetrics = $xmlDocument->createElement('metrics');
  119. $xmlMetrics->setAttribute('complexity', (string) $class['ccn']);
  120. $xmlMetrics->setAttribute('methods', (string) $classMethods);
  121. $xmlMetrics->setAttribute('coveredmethods', (string) $coveredMethods);
  122. $xmlMetrics->setAttribute('conditionals', (string) $class['executableBranches']);
  123. $xmlMetrics->setAttribute('coveredconditionals', (string) $class['executedBranches']);
  124. $xmlMetrics->setAttribute('statements', (string) $classStatements);
  125. $xmlMetrics->setAttribute('coveredstatements', (string) $coveredClassStatements);
  126. $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class['executableBranches']));
  127. $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class['executedBranches']));
  128. $xmlClass->appendChild($xmlMetrics);
  129. }
  130. foreach ($coverageData as $line => $data) {
  131. if ($data === null || isset($lines[$line])) {
  132. continue;
  133. }
  134. $lines[$line] = [
  135. 'count' => count($data), 'type' => 'stmt',
  136. ];
  137. }
  138. ksort($lines);
  139. foreach ($lines as $line => $data) {
  140. $xmlLine = $xmlDocument->createElement('line');
  141. $xmlLine->setAttribute('num', (string) $line);
  142. $xmlLine->setAttribute('type', $data['type']);
  143. if (isset($data['name'])) {
  144. $xmlLine->setAttribute('name', $data['name']);
  145. }
  146. if (isset($data['visibility'])) {
  147. $xmlLine->setAttribute('visibility', $data['visibility']);
  148. }
  149. if (isset($data['ccn'])) {
  150. $xmlLine->setAttribute('complexity', (string) $data['ccn']);
  151. }
  152. if (isset($data['crap'])) {
  153. $xmlLine->setAttribute('crap', (string) $data['crap']);
  154. }
  155. $xmlLine->setAttribute('count', (string) $data['count']);
  156. $xmlFile->appendChild($xmlLine);
  157. }
  158. $linesOfCode = $item->linesOfCode();
  159. $xmlMetrics = $xmlDocument->createElement('metrics');
  160. $xmlMetrics->setAttribute('loc', (string) $linesOfCode['linesOfCode']);
  161. $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['nonCommentLinesOfCode']);
  162. $xmlMetrics->setAttribute('classes', (string) $item->numberOfClassesAndTraits());
  163. $xmlMetrics->setAttribute('methods', (string) $item->numberOfMethods());
  164. $xmlMetrics->setAttribute('coveredmethods', (string) $item->numberOfTestedMethods());
  165. $xmlMetrics->setAttribute('conditionals', (string) $item->numberOfExecutableBranches());
  166. $xmlMetrics->setAttribute('coveredconditionals', (string) $item->numberOfExecutedBranches());
  167. $xmlMetrics->setAttribute('statements', (string) $item->numberOfExecutableLines());
  168. $xmlMetrics->setAttribute('coveredstatements', (string) $item->numberOfExecutedLines());
  169. $xmlMetrics->setAttribute('elements', (string) ($item->numberOfMethods() + $item->numberOfExecutableLines() + $item->numberOfExecutableBranches()));
  170. $xmlMetrics->setAttribute('coveredelements', (string) ($item->numberOfTestedMethods() + $item->numberOfExecutedLines() + $item->numberOfExecutedBranches()));
  171. $xmlFile->appendChild($xmlMetrics);
  172. if ($namespace === 'global') {
  173. $xmlProject->appendChild($xmlFile);
  174. } else {
  175. if (!isset($packages[$namespace])) {
  176. $packages[$namespace] = $xmlDocument->createElement(
  177. 'package',
  178. );
  179. $packages[$namespace]->setAttribute('name', $namespace);
  180. $xmlProject->appendChild($packages[$namespace]);
  181. }
  182. $packages[$namespace]->appendChild($xmlFile);
  183. }
  184. }
  185. $linesOfCode = $report->linesOfCode();
  186. $xmlMetrics = $xmlDocument->createElement('metrics');
  187. $xmlMetrics->setAttribute('files', (string) count($report));
  188. $xmlMetrics->setAttribute('loc', (string) $linesOfCode['linesOfCode']);
  189. $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['nonCommentLinesOfCode']);
  190. $xmlMetrics->setAttribute('classes', (string) $report->numberOfClassesAndTraits());
  191. $xmlMetrics->setAttribute('methods', (string) $report->numberOfMethods());
  192. $xmlMetrics->setAttribute('coveredmethods', (string) $report->numberOfTestedMethods());
  193. $xmlMetrics->setAttribute('conditionals', (string) $report->numberOfExecutableBranches());
  194. $xmlMetrics->setAttribute('coveredconditionals', (string) $report->numberOfExecutedBranches());
  195. $xmlMetrics->setAttribute('statements', (string) $report->numberOfExecutableLines());
  196. $xmlMetrics->setAttribute('coveredstatements', (string) $report->numberOfExecutedLines());
  197. $xmlMetrics->setAttribute('elements', (string) ($report->numberOfMethods() + $report->numberOfExecutableLines() + $report->numberOfExecutableBranches()));
  198. $xmlMetrics->setAttribute('coveredelements', (string) ($report->numberOfTestedMethods() + $report->numberOfExecutedLines() + $report->numberOfExecutedBranches()));
  199. $xmlProject->appendChild($xmlMetrics);
  200. $buffer = $xmlDocument->saveXML();
  201. if ($target !== null) {
  202. if (!str_contains($target, '://')) {
  203. Filesystem::createDirectory(dirname($target));
  204. }
  205. if (@file_put_contents($target, $buffer) === false) {
  206. throw new WriteOperationFailedException($target);
  207. }
  208. }
  209. return $buffer;
  210. }
  211. }