Crap4j.php 5.6 KB

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