3 * This file is part of the PHP_CodeCoverage package.
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
12 * Represents a file in the code coverage information tree.
14 * @since Class available since Release 1.1.0
16 class PHP_CodeCoverage_Report_Node_File extends PHP_CodeCoverage_Report_Node
21 protected $coverageData;
31 protected $numExecutableLines = 0;
36 protected $numExecutedLines = 0;
41 protected $classes = array();
46 protected $traits = array();
51 protected $functions = array();
56 protected $linesOfCode = array();
61 protected $numTestedTraits = 0;
66 protected $numTestedClasses = 0;
71 protected $numMethods = null;
76 protected $numTestedMethods = null;
81 protected $numTestedFunctions = null;
86 protected $startLines = array();
91 protected $endLines = array();
96 protected $cacheTokens;
101 * @param string $name
102 * @param PHP_CodeCoverage_Report_Node $parent
103 * @param array $coverageData
104 * @param array $testData
105 * @param bool $cacheTokens
106 * @throws PHP_CodeCoverage_Exception
108 public function __construct($name, PHP_CodeCoverage_Report_Node $parent, array $coverageData, array $testData, $cacheTokens)
110 if (!is_bool($cacheTokens)) {
111 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
117 parent::__construct($name, $parent);
119 $this->coverageData = $coverageData;
120 $this->testData = $testData;
121 $this->cacheTokens = $cacheTokens;
123 $this->calculateStatistics();
127 * Returns the number of files in/under this node.
131 public function count()
137 * Returns the code coverage data of this node.
141 public function getCoverageData()
143 return $this->coverageData;
147 * Returns the test data of this node.
151 public function getTestData()
153 return $this->testData;
157 * Returns the classes of this node.
161 public function getClasses()
163 return $this->classes;
167 * Returns the traits of this node.
171 public function getTraits()
173 return $this->traits;
177 * Returns the functions of this node.
181 public function getFunctions()
183 return $this->functions;
187 * Returns the LOC/CLOC/NCLOC of this node.
191 public function getLinesOfCode()
193 return $this->linesOfCode;
197 * Returns the number of executable lines.
201 public function getNumExecutableLines()
203 return $this->numExecutableLines;
207 * Returns the number of executed lines.
211 public function getNumExecutedLines()
213 return $this->numExecutedLines;
217 * Returns the number of classes.
221 public function getNumClasses()
223 return count($this->classes);
227 * Returns the number of tested classes.
231 public function getNumTestedClasses()
233 return $this->numTestedClasses;
237 * Returns the number of traits.
241 public function getNumTraits()
243 return count($this->traits);
247 * Returns the number of tested traits.
251 public function getNumTestedTraits()
253 return $this->numTestedTraits;
257 * Returns the number of methods.
261 public function getNumMethods()
263 if ($this->numMethods === null) {
264 $this->numMethods = 0;
266 foreach ($this->classes as $class) {
267 foreach ($class['methods'] as $method) {
268 if ($method['executableLines'] > 0) {
274 foreach ($this->traits as $trait) {
275 foreach ($trait['methods'] as $method) {
276 if ($method['executableLines'] > 0) {
283 return $this->numMethods;
287 * Returns the number of tested methods.
291 public function getNumTestedMethods()
293 if ($this->numTestedMethods === null) {
294 $this->numTestedMethods = 0;
296 foreach ($this->classes as $class) {
297 foreach ($class['methods'] as $method) {
298 if ($method['executableLines'] > 0 &&
299 $method['coverage'] == 100) {
300 $this->numTestedMethods++;
305 foreach ($this->traits as $trait) {
306 foreach ($trait['methods'] as $method) {
307 if ($method['executableLines'] > 0 &&
308 $method['coverage'] == 100) {
309 $this->numTestedMethods++;
315 return $this->numTestedMethods;
319 * Returns the number of functions.
323 public function getNumFunctions()
325 return count($this->functions);
329 * Returns the number of tested functions.
333 public function getNumTestedFunctions()
335 if ($this->numTestedFunctions === null) {
336 $this->numTestedFunctions = 0;
338 foreach ($this->functions as $function) {
339 if ($function['executableLines'] > 0 &&
340 $function['coverage'] == 100) {
341 $this->numTestedFunctions++;
346 return $this->numTestedFunctions;
350 * Calculates coverage statistics for the file.
352 protected function calculateStatistics()
354 $classStack = $functionStack = array();
356 if ($this->cacheTokens) {
357 $tokens = PHP_Token_Stream_CachingFactory::get($this->getPath());
359 $tokens = new PHP_Token_Stream($this->getPath());
362 $this->processClasses($tokens);
363 $this->processTraits($tokens);
364 $this->processFunctions($tokens);
365 $this->linesOfCode = $tokens->getLinesOfCode();
368 for ($lineNumber = 1; $lineNumber <= $this->linesOfCode['loc']; $lineNumber++) {
369 if (isset($this->startLines[$lineNumber])) {
370 // Start line of a class.
371 if (isset($this->startLines[$lineNumber]['className'])) {
372 if (isset($currentClass)) {
373 $classStack[] = &$currentClass;
376 $currentClass = &$this->startLines[$lineNumber];
377 } // Start line of a trait.
378 elseif (isset($this->startLines[$lineNumber]['traitName'])) {
379 $currentTrait = &$this->startLines[$lineNumber];
380 } // Start line of a method.
381 elseif (isset($this->startLines[$lineNumber]['methodName'])) {
382 $currentMethod = &$this->startLines[$lineNumber];
383 } // Start line of a function.
384 elseif (isset($this->startLines[$lineNumber]['functionName'])) {
385 if (isset($currentFunction)) {
386 $functionStack[] = &$currentFunction;
389 $currentFunction = &$this->startLines[$lineNumber];
393 if (isset($this->coverageData[$lineNumber])) {
394 if (isset($currentClass)) {
395 $currentClass['executableLines']++;
398 if (isset($currentTrait)) {
399 $currentTrait['executableLines']++;
402 if (isset($currentMethod)) {
403 $currentMethod['executableLines']++;
406 if (isset($currentFunction)) {
407 $currentFunction['executableLines']++;
410 $this->numExecutableLines++;
412 if (count($this->coverageData[$lineNumber]) > 0) {
413 if (isset($currentClass)) {
414 $currentClass['executedLines']++;
417 if (isset($currentTrait)) {
418 $currentTrait['executedLines']++;
421 if (isset($currentMethod)) {
422 $currentMethod['executedLines']++;
425 if (isset($currentFunction)) {
426 $currentFunction['executedLines']++;
429 $this->numExecutedLines++;
433 if (isset($this->endLines[$lineNumber])) {
434 // End line of a class.
435 if (isset($this->endLines[$lineNumber]['className'])) {
436 unset($currentClass);
440 $key = key($classStack);
441 $currentClass = &$classStack[$key];
442 unset($classStack[$key]);
444 } // End line of a trait.
445 elseif (isset($this->endLines[$lineNumber]['traitName'])) {
446 unset($currentTrait);
447 } // End line of a method.
448 elseif (isset($this->endLines[$lineNumber]['methodName'])) {
449 unset($currentMethod);
450 } // End line of a function.
451 elseif (isset($this->endLines[$lineNumber]['functionName'])) {
452 unset($currentFunction);
454 if ($functionStack) {
456 $key = key($functionStack);
457 $currentFunction = &$functionStack[$key];
458 unset($functionStack[$key]);
464 foreach ($this->traits as &$trait) {
465 foreach ($trait['methods'] as &$method) {
466 if ($method['executableLines'] > 0) {
467 $method['coverage'] = ($method['executedLines'] /
468 $method['executableLines']) * 100;
470 $method['coverage'] = 100;
473 $method['crap'] = $this->crap(
478 $trait['ccn'] += $method['ccn'];
481 if ($trait['executableLines'] > 0) {
482 $trait['coverage'] = ($trait['executedLines'] /
483 $trait['executableLines']) * 100;
485 $trait['coverage'] = 100;
488 if ($trait['coverage'] == 100) {
489 $this->numTestedClasses++;
492 $trait['crap'] = $this->crap(
498 foreach ($this->classes as &$class) {
499 foreach ($class['methods'] as &$method) {
500 if ($method['executableLines'] > 0) {
501 $method['coverage'] = ($method['executedLines'] /
502 $method['executableLines']) * 100;
504 $method['coverage'] = 100;
507 $method['crap'] = $this->crap(
512 $class['ccn'] += $method['ccn'];
515 if ($class['executableLines'] > 0) {
516 $class['coverage'] = ($class['executedLines'] /
517 $class['executableLines']) * 100;
519 $class['coverage'] = 100;
522 if ($class['coverage'] == 100) {
523 $this->numTestedClasses++;
526 $class['crap'] = $this->crap(
534 * @param PHP_Token_Stream $tokens
536 protected function processClasses(PHP_Token_Stream $tokens)
538 $classes = $tokens->getClasses();
541 $link = $this->getId() . '.html#';
543 foreach ($classes as $className => $class) {
544 $this->classes[$className] = array(
545 'className' => $className,
546 'methods' => array(),
547 'startLine' => $class['startLine'],
548 'executableLines' => 0,
549 'executedLines' => 0,
553 'package' => $class['package'],
554 'link' => $link . $class['startLine']
557 $this->startLines[$class['startLine']] = &$this->classes[$className];
558 $this->endLines[$class['endLine']] = &$this->classes[$className];
560 foreach ($class['methods'] as $methodName => $method) {
561 $this->classes[$className]['methods'][$methodName] = array(
562 'methodName' => $methodName,
563 'signature' => $method['signature'],
564 'startLine' => $method['startLine'],
565 'endLine' => $method['endLine'],
566 'executableLines' => 0,
567 'executedLines' => 0,
568 'ccn' => $method['ccn'],
571 'link' => $link . $method['startLine']
574 $this->startLines[$method['startLine']] = &$this->classes[$className]['methods'][$methodName];
575 $this->endLines[$method['endLine']] = &$this->classes[$className]['methods'][$methodName];
581 * @param PHP_Token_Stream $tokens
583 protected function processTraits(PHP_Token_Stream $tokens)
585 $traits = $tokens->getTraits();
588 $link = $this->getId() . '.html#';
590 foreach ($traits as $traitName => $trait) {
591 $this->traits[$traitName] = array(
592 'traitName' => $traitName,
593 'methods' => array(),
594 'startLine' => $trait['startLine'],
595 'executableLines' => 0,
596 'executedLines' => 0,
600 'package' => $trait['package'],
601 'link' => $link . $trait['startLine']
604 $this->startLines[$trait['startLine']] = &$this->traits[$traitName];
605 $this->endLines[$trait['endLine']] = &$this->traits[$traitName];
607 foreach ($trait['methods'] as $methodName => $method) {
608 $this->traits[$traitName]['methods'][$methodName] = array(
609 'methodName' => $methodName,
610 'signature' => $method['signature'],
611 'startLine' => $method['startLine'],
612 'endLine' => $method['endLine'],
613 'executableLines' => 0,
614 'executedLines' => 0,
615 'ccn' => $method['ccn'],
618 'link' => $link . $method['startLine']
621 $this->startLines[$method['startLine']] = &$this->traits[$traitName]['methods'][$methodName];
622 $this->endLines[$method['endLine']] = &$this->traits[$traitName]['methods'][$methodName];
628 * @param PHP_Token_Stream $tokens
630 protected function processFunctions(PHP_Token_Stream $tokens)
632 $functions = $tokens->getFunctions();
635 $link = $this->getId() . '.html#';
637 foreach ($functions as $functionName => $function) {
638 $this->functions[$functionName] = array(
639 'functionName' => $functionName,
640 'signature' => $function['signature'],
641 'startLine' => $function['startLine'],
642 'executableLines' => 0,
643 'executedLines' => 0,
644 'ccn' => $function['ccn'],
647 'link' => $link . $function['startLine']
650 $this->startLines[$function['startLine']] = &$this->functions[$functionName];
651 $this->endLines[$function['endLine']] = &$this->functions[$functionName];
656 * Calculates the Change Risk Anti-Patterns (CRAP) index for a unit of code
657 * based on its cyclomatic complexity and percentage of code coverage.
660 * @param float $coverage
662 * @since Method available since Release 1.2.0
664 protected function crap($ccn, $coverage)
666 if ($coverage == 0) {
667 return (string) (pow($ccn, 2) + $ccn);
670 if ($coverage >= 95) {
671 return (string) $ccn;
676 pow($ccn, 2) * pow(1 - $coverage/100, 3) + $ccn