Crap4j.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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 date;
  12. use function dirname;
  13. use function file_put_contents;
  14. use function htmlspecialchars;
  15. use function is_string;
  16. use function round;
  17. use function strpos;
  18. use DOMDocument;
  19. use SebastianBergmann\CodeCoverage\CodeCoverage;
  20. use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException;
  21. use SebastianBergmann\CodeCoverage\Node\File;
  22. use SebastianBergmann\CodeCoverage\Util\Filesystem;
  23. final class Crap4j
  24. {
  25. /**
  26. * @var int
  27. */
  28. private $threshold;
  29. public function __construct(int $threshold = 30)
  30. {
  31. $this->threshold = $threshold;
  32. }
  33. /**
  34. * @throws WriteOperationFailedException
  35. */
  36. public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string
  37. {
  38. $document = new DOMDocument('1.0', 'UTF-8');
  39. $document->formatOutput = true;
  40. $root = $document->createElement('crap_result');
  41. $document->appendChild($root);
  42. $project = $document->createElement('project', is_string($name) ? $name : '');
  43. $root->appendChild($project);
  44. $root->appendChild($document->createElement('timestamp', date('Y-m-d H:i:s')));
  45. $stats = $document->createElement('stats');
  46. $methodsNode = $document->createElement('methods');
  47. $report = $coverage->getReport();
  48. unset($coverage);
  49. $fullMethodCount = 0;
  50. $fullCrapMethodCount = 0;
  51. $fullCrapLoad = 0;
  52. $fullCrap = 0;
  53. foreach ($report as $item) {
  54. $namespace = 'global';
  55. if (!$item instanceof File) {
  56. continue;
  57. }
  58. $file = $document->createElement('file');
  59. $file->setAttribute('name', $item->pathAsString());
  60. $classes = $item->classesAndTraits();
  61. foreach ($classes as $className => $class) {
  62. foreach ($class['methods'] as $methodName => $method) {
  63. $crapLoad = $this->crapLoad((float) $method['crap'], $method['ccn'], $method['coverage']);
  64. $fullCrap += $method['crap'];
  65. $fullCrapLoad += $crapLoad;
  66. $fullMethodCount++;
  67. if ($method['crap'] >= $this->threshold) {
  68. $fullCrapMethodCount++;
  69. }
  70. $methodNode = $document->createElement('method');
  71. if (!empty($class['namespace'])) {
  72. $namespace = $class['namespace'];
  73. }
  74. $methodNode->appendChild($document->createElement('package', $namespace));
  75. $methodNode->appendChild($document->createElement('className', $className));
  76. $methodNode->appendChild($document->createElement('methodName', $methodName));
  77. $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method['signature'])));
  78. $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method['signature'])));
  79. $methodNode->appendChild($document->createElement('crap', (string) $this->roundValue((float) $method['crap'])));
  80. $methodNode->appendChild($document->createElement('complexity', (string) $method['ccn']));
  81. $methodNode->appendChild($document->createElement('coverage', (string) $this->roundValue($method['coverage'])));
  82. $methodNode->appendChild($document->createElement('crapLoad', (string) round($crapLoad)));
  83. $methodsNode->appendChild($methodNode);
  84. }
  85. }
  86. }
  87. $stats->appendChild($document->createElement('name', 'Method Crap Stats'));
  88. $stats->appendChild($document->createElement('methodCount', (string) $fullMethodCount));
  89. $stats->appendChild($document->createElement('crapMethodCount', (string) $fullCrapMethodCount));
  90. $stats->appendChild($document->createElement('crapLoad', (string) round($fullCrapLoad)));
  91. $stats->appendChild($document->createElement('totalCrap', (string) $fullCrap));
  92. $crapMethodPercent = 0;
  93. if ($fullMethodCount > 0) {
  94. $crapMethodPercent = $this->roundValue((100 * $fullCrapMethodCount) / $fullMethodCount);
  95. }
  96. $stats->appendChild($document->createElement('crapMethodPercent', (string) $crapMethodPercent));
  97. $root->appendChild($stats);
  98. $root->appendChild($methodsNode);
  99. $buffer = $document->saveXML();
  100. if ($target !== null) {
  101. if (!strpos($target, '://') !== false) {
  102. Filesystem::createDirectory(dirname($target));
  103. }
  104. if (@file_put_contents($target, $buffer) === false) {
  105. throw new WriteOperationFailedException($target);
  106. }
  107. }
  108. return $buffer;
  109. }
  110. private function crapLoad(float $crapValue, int $cyclomaticComplexity, float $coveragePercent): float
  111. {
  112. $crapLoad = 0;
  113. if ($crapValue >= $this->threshold) {
  114. $crapLoad += $cyclomaticComplexity * (1.0 - $coveragePercent / 100);
  115. $crapLoad += $cyclomaticComplexity / $this->threshold;
  116. }
  117. return $crapLoad;
  118. }
  119. private function roundValue(float $value): float
  120. {
  121. return round($value, 2);
  122. }
  123. }