comparison vendor/phpunit/php-code-coverage/src/Node/File.php @ 14:1fec387a4317

Update Drupal core to 8.5.2 via Composer
author Chris Cannam
date Mon, 23 Apr 2018 09:46:53 +0100
parents
children
comparison
equal deleted inserted replaced
13:5fb285c0d0e3 14:1fec387a4317
1 <?php
2 /*
3 * This file is part of the php-code-coverage package.
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
11 namespace SebastianBergmann\CodeCoverage\Node;
12
13 use SebastianBergmann\CodeCoverage\InvalidArgumentException;
14
15 /**
16 * Represents a file in the code coverage information tree.
17 */
18 class File extends AbstractNode
19 {
20 /**
21 * @var array
22 */
23 private $coverageData;
24
25 /**
26 * @var array
27 */
28 private $testData;
29
30 /**
31 * @var int
32 */
33 private $numExecutableLines = 0;
34
35 /**
36 * @var int
37 */
38 private $numExecutedLines = 0;
39
40 /**
41 * @var array
42 */
43 private $classes = [];
44
45 /**
46 * @var array
47 */
48 private $traits = [];
49
50 /**
51 * @var array
52 */
53 private $functions = [];
54
55 /**
56 * @var array
57 */
58 private $linesOfCode = [];
59
60 /**
61 * @var int
62 */
63 private $numClasses = null;
64
65 /**
66 * @var int
67 */
68 private $numTestedClasses = 0;
69
70 /**
71 * @var int
72 */
73 private $numTraits = null;
74
75 /**
76 * @var int
77 */
78 private $numTestedTraits = 0;
79
80 /**
81 * @var int
82 */
83 private $numMethods = null;
84
85 /**
86 * @var int
87 */
88 private $numTestedMethods = null;
89
90 /**
91 * @var int
92 */
93 private $numTestedFunctions = null;
94
95 /**
96 * @var array
97 */
98 private $startLines = [];
99
100 /**
101 * @var array
102 */
103 private $endLines = [];
104
105 /**
106 * @var bool
107 */
108 private $cacheTokens;
109
110 /**
111 * Constructor.
112 *
113 * @param string $name
114 * @param AbstractNode $parent
115 * @param array $coverageData
116 * @param array $testData
117 * @param bool $cacheTokens
118 *
119 * @throws InvalidArgumentException
120 */
121 public function __construct($name, AbstractNode $parent, array $coverageData, array $testData, $cacheTokens)
122 {
123 if (!\is_bool($cacheTokens)) {
124 throw InvalidArgumentException::create(
125 1,
126 'boolean'
127 );
128 }
129
130 parent::__construct($name, $parent);
131
132 $this->coverageData = $coverageData;
133 $this->testData = $testData;
134 $this->cacheTokens = $cacheTokens;
135
136 $this->calculateStatistics();
137 }
138
139 /**
140 * Returns the number of files in/under this node.
141 *
142 * @return int
143 */
144 public function count()
145 {
146 return 1;
147 }
148
149 /**
150 * Returns the code coverage data of this node.
151 *
152 * @return array
153 */
154 public function getCoverageData()
155 {
156 return $this->coverageData;
157 }
158
159 /**
160 * Returns the test data of this node.
161 *
162 * @return array
163 */
164 public function getTestData()
165 {
166 return $this->testData;
167 }
168
169 /**
170 * Returns the classes of this node.
171 *
172 * @return array
173 */
174 public function getClasses()
175 {
176 return $this->classes;
177 }
178
179 /**
180 * Returns the traits of this node.
181 *
182 * @return array
183 */
184 public function getTraits()
185 {
186 return $this->traits;
187 }
188
189 /**
190 * Returns the functions of this node.
191 *
192 * @return array
193 */
194 public function getFunctions()
195 {
196 return $this->functions;
197 }
198
199 /**
200 * Returns the LOC/CLOC/NCLOC of this node.
201 *
202 * @return array
203 */
204 public function getLinesOfCode()
205 {
206 return $this->linesOfCode;
207 }
208
209 /**
210 * Returns the number of executable lines.
211 *
212 * @return int
213 */
214 public function getNumExecutableLines()
215 {
216 return $this->numExecutableLines;
217 }
218
219 /**
220 * Returns the number of executed lines.
221 *
222 * @return int
223 */
224 public function getNumExecutedLines()
225 {
226 return $this->numExecutedLines;
227 }
228
229 /**
230 * Returns the number of classes.
231 *
232 * @return int
233 */
234 public function getNumClasses()
235 {
236 if ($this->numClasses === null) {
237 $this->numClasses = 0;
238
239 foreach ($this->classes as $class) {
240 foreach ($class['methods'] as $method) {
241 if ($method['executableLines'] > 0) {
242 $this->numClasses++;
243
244 continue 2;
245 }
246 }
247 }
248 }
249
250 return $this->numClasses;
251 }
252
253 /**
254 * Returns the number of tested classes.
255 *
256 * @return int
257 */
258 public function getNumTestedClasses()
259 {
260 return $this->numTestedClasses;
261 }
262
263 /**
264 * Returns the number of traits.
265 *
266 * @return int
267 */
268 public function getNumTraits()
269 {
270 if ($this->numTraits === null) {
271 $this->numTraits = 0;
272
273 foreach ($this->traits as $trait) {
274 foreach ($trait['methods'] as $method) {
275 if ($method['executableLines'] > 0) {
276 $this->numTraits++;
277
278 continue 2;
279 }
280 }
281 }
282 }
283
284 return $this->numTraits;
285 }
286
287 /**
288 * Returns the number of tested traits.
289 *
290 * @return int
291 */
292 public function getNumTestedTraits()
293 {
294 return $this->numTestedTraits;
295 }
296
297 /**
298 * Returns the number of methods.
299 *
300 * @return int
301 */
302 public function getNumMethods()
303 {
304 if ($this->numMethods === null) {
305 $this->numMethods = 0;
306
307 foreach ($this->classes as $class) {
308 foreach ($class['methods'] as $method) {
309 if ($method['executableLines'] > 0) {
310 $this->numMethods++;
311 }
312 }
313 }
314
315 foreach ($this->traits as $trait) {
316 foreach ($trait['methods'] as $method) {
317 if ($method['executableLines'] > 0) {
318 $this->numMethods++;
319 }
320 }
321 }
322 }
323
324 return $this->numMethods;
325 }
326
327 /**
328 * Returns the number of tested methods.
329 *
330 * @return int
331 */
332 public function getNumTestedMethods()
333 {
334 if ($this->numTestedMethods === null) {
335 $this->numTestedMethods = 0;
336
337 foreach ($this->classes as $class) {
338 foreach ($class['methods'] as $method) {
339 if ($method['executableLines'] > 0 &&
340 $method['coverage'] == 100) {
341 $this->numTestedMethods++;
342 }
343 }
344 }
345
346 foreach ($this->traits as $trait) {
347 foreach ($trait['methods'] as $method) {
348 if ($method['executableLines'] > 0 &&
349 $method['coverage'] == 100) {
350 $this->numTestedMethods++;
351 }
352 }
353 }
354 }
355
356 return $this->numTestedMethods;
357 }
358
359 /**
360 * Returns the number of functions.
361 *
362 * @return int
363 */
364 public function getNumFunctions()
365 {
366 return \count($this->functions);
367 }
368
369 /**
370 * Returns the number of tested functions.
371 *
372 * @return int
373 */
374 public function getNumTestedFunctions()
375 {
376 if ($this->numTestedFunctions === null) {
377 $this->numTestedFunctions = 0;
378
379 foreach ($this->functions as $function) {
380 if ($function['executableLines'] > 0 &&
381 $function['coverage'] == 100) {
382 $this->numTestedFunctions++;
383 }
384 }
385 }
386
387 return $this->numTestedFunctions;
388 }
389
390 /**
391 * Calculates coverage statistics for the file.
392 */
393 protected function calculateStatistics()
394 {
395 $classStack = $functionStack = [];
396
397 if ($this->cacheTokens) {
398 $tokens = \PHP_Token_Stream_CachingFactory::get($this->getPath());
399 } else {
400 $tokens = new \PHP_Token_Stream($this->getPath());
401 }
402
403 $this->processClasses($tokens);
404 $this->processTraits($tokens);
405 $this->processFunctions($tokens);
406 $this->linesOfCode = $tokens->getLinesOfCode();
407 unset($tokens);
408
409 for ($lineNumber = 1; $lineNumber <= $this->linesOfCode['loc']; $lineNumber++) {
410 if (isset($this->startLines[$lineNumber])) {
411 // Start line of a class.
412 if (isset($this->startLines[$lineNumber]['className'])) {
413 if (isset($currentClass)) {
414 $classStack[] = &$currentClass;
415 }
416
417 $currentClass = &$this->startLines[$lineNumber];
418 } // Start line of a trait.
419 elseif (isset($this->startLines[$lineNumber]['traitName'])) {
420 $currentTrait = &$this->startLines[$lineNumber];
421 } // Start line of a method.
422 elseif (isset($this->startLines[$lineNumber]['methodName'])) {
423 $currentMethod = &$this->startLines[$lineNumber];
424 } // Start line of a function.
425 elseif (isset($this->startLines[$lineNumber]['functionName'])) {
426 if (isset($currentFunction)) {
427 $functionStack[] = &$currentFunction;
428 }
429
430 $currentFunction = &$this->startLines[$lineNumber];
431 }
432 }
433
434 if (isset($this->coverageData[$lineNumber])) {
435 if (isset($currentClass)) {
436 $currentClass['executableLines']++;
437 }
438
439 if (isset($currentTrait)) {
440 $currentTrait['executableLines']++;
441 }
442
443 if (isset($currentMethod)) {
444 $currentMethod['executableLines']++;
445 }
446
447 if (isset($currentFunction)) {
448 $currentFunction['executableLines']++;
449 }
450
451 $this->numExecutableLines++;
452
453 if (\count($this->coverageData[$lineNumber]) > 0) {
454 if (isset($currentClass)) {
455 $currentClass['executedLines']++;
456 }
457
458 if (isset($currentTrait)) {
459 $currentTrait['executedLines']++;
460 }
461
462 if (isset($currentMethod)) {
463 $currentMethod['executedLines']++;
464 }
465
466 if (isset($currentFunction)) {
467 $currentFunction['executedLines']++;
468 }
469
470 $this->numExecutedLines++;
471 }
472 }
473
474 if (isset($this->endLines[$lineNumber])) {
475 // End line of a class.
476 if (isset($this->endLines[$lineNumber]['className'])) {
477 unset($currentClass);
478
479 if ($classStack) {
480 \end($classStack);
481 $key = \key($classStack);
482 $currentClass = &$classStack[$key];
483 unset($classStack[$key]);
484 }
485 } // End line of a trait.
486 elseif (isset($this->endLines[$lineNumber]['traitName'])) {
487 unset($currentTrait);
488 } // End line of a method.
489 elseif (isset($this->endLines[$lineNumber]['methodName'])) {
490 unset($currentMethod);
491 } // End line of a function.
492 elseif (isset($this->endLines[$lineNumber]['functionName'])) {
493 unset($currentFunction);
494
495 if ($functionStack) {
496 \end($functionStack);
497 $key = \key($functionStack);
498 $currentFunction = &$functionStack[$key];
499 unset($functionStack[$key]);
500 }
501 }
502 }
503 }
504
505 foreach ($this->traits as &$trait) {
506 foreach ($trait['methods'] as &$method) {
507 if ($method['executableLines'] > 0) {
508 $method['coverage'] = ($method['executedLines'] /
509 $method['executableLines']) * 100;
510 } else {
511 $method['coverage'] = 100;
512 }
513
514 $method['crap'] = $this->crap(
515 $method['ccn'],
516 $method['coverage']
517 );
518
519 $trait['ccn'] += $method['ccn'];
520 }
521
522 if ($trait['executableLines'] > 0) {
523 $trait['coverage'] = ($trait['executedLines'] /
524 $trait['executableLines']) * 100;
525
526 if ($trait['coverage'] == 100) {
527 $this->numTestedClasses++;
528 }
529 } else {
530 $trait['coverage'] = 100;
531 }
532
533 $trait['crap'] = $this->crap(
534 $trait['ccn'],
535 $trait['coverage']
536 );
537 }
538
539 foreach ($this->classes as &$class) {
540 foreach ($class['methods'] as &$method) {
541 if ($method['executableLines'] > 0) {
542 $method['coverage'] = ($method['executedLines'] /
543 $method['executableLines']) * 100;
544 } else {
545 $method['coverage'] = 100;
546 }
547
548 $method['crap'] = $this->crap(
549 $method['ccn'],
550 $method['coverage']
551 );
552
553 $class['ccn'] += $method['ccn'];
554 }
555
556 if ($class['executableLines'] > 0) {
557 $class['coverage'] = ($class['executedLines'] /
558 $class['executableLines']) * 100;
559
560 if ($class['coverage'] == 100) {
561 $this->numTestedClasses++;
562 }
563 } else {
564 $class['coverage'] = 100;
565 }
566
567 $class['crap'] = $this->crap(
568 $class['ccn'],
569 $class['coverage']
570 );
571 }
572
573 foreach ($this->functions as &$function) {
574 if ($function['executableLines'] > 0) {
575 $function['coverage'] = ($function['executedLines'] /
576 $function['executableLines']) * 100;
577 } else {
578 $function['coverage'] = 100;
579 }
580
581 if ($function['coverage'] == 100) {
582 $this->numTestedFunctions++;
583 }
584
585 $function['crap'] = $this->crap(
586 $function['ccn'],
587 $function['coverage']
588 );
589 }
590 }
591
592 /**
593 * @param \PHP_Token_Stream $tokens
594 */
595 protected function processClasses(\PHP_Token_Stream $tokens)
596 {
597 $classes = $tokens->getClasses();
598 unset($tokens);
599
600 $link = $this->getId() . '.html#';
601
602 foreach ($classes as $className => $class) {
603 if (!empty($class['package']['namespace'])) {
604 $className = $class['package']['namespace'] . '\\' . $className;
605 }
606
607 $this->classes[$className] = [
608 'className' => $className,
609 'methods' => [],
610 'startLine' => $class['startLine'],
611 'executableLines' => 0,
612 'executedLines' => 0,
613 'ccn' => 0,
614 'coverage' => 0,
615 'crap' => 0,
616 'package' => $class['package'],
617 'link' => $link . $class['startLine']
618 ];
619
620 $this->startLines[$class['startLine']] = &$this->classes[$className];
621 $this->endLines[$class['endLine']] = &$this->classes[$className];
622
623 foreach ($class['methods'] as $methodName => $method) {
624 $this->classes[$className]['methods'][$methodName] = $this->newMethod($methodName, $method, $link);
625
626 $this->startLines[$method['startLine']] = &$this->classes[$className]['methods'][$methodName];
627 $this->endLines[$method['endLine']] = &$this->classes[$className]['methods'][$methodName];
628 }
629 }
630 }
631
632 /**
633 * @param \PHP_Token_Stream $tokens
634 */
635 protected function processTraits(\PHP_Token_Stream $tokens)
636 {
637 $traits = $tokens->getTraits();
638 unset($tokens);
639
640 $link = $this->getId() . '.html#';
641
642 foreach ($traits as $traitName => $trait) {
643 $this->traits[$traitName] = [
644 'traitName' => $traitName,
645 'methods' => [],
646 'startLine' => $trait['startLine'],
647 'executableLines' => 0,
648 'executedLines' => 0,
649 'ccn' => 0,
650 'coverage' => 0,
651 'crap' => 0,
652 'package' => $trait['package'],
653 'link' => $link . $trait['startLine']
654 ];
655
656 $this->startLines[$trait['startLine']] = &$this->traits[$traitName];
657 $this->endLines[$trait['endLine']] = &$this->traits[$traitName];
658
659 foreach ($trait['methods'] as $methodName => $method) {
660 $this->traits[$traitName]['methods'][$methodName] = $this->newMethod($methodName, $method, $link);
661
662 $this->startLines[$method['startLine']] = &$this->traits[$traitName]['methods'][$methodName];
663 $this->endLines[$method['endLine']] = &$this->traits[$traitName]['methods'][$methodName];
664 }
665 }
666 }
667
668 /**
669 * @param \PHP_Token_Stream $tokens
670 */
671 protected function processFunctions(\PHP_Token_Stream $tokens)
672 {
673 $functions = $tokens->getFunctions();
674 unset($tokens);
675
676 $link = $this->getId() . '.html#';
677
678 foreach ($functions as $functionName => $function) {
679 $this->functions[$functionName] = [
680 'functionName' => $functionName,
681 'signature' => $function['signature'],
682 'startLine' => $function['startLine'],
683 'executableLines' => 0,
684 'executedLines' => 0,
685 'ccn' => $function['ccn'],
686 'coverage' => 0,
687 'crap' => 0,
688 'link' => $link . $function['startLine']
689 ];
690
691 $this->startLines[$function['startLine']] = &$this->functions[$functionName];
692 $this->endLines[$function['endLine']] = &$this->functions[$functionName];
693 }
694 }
695
696 /**
697 * Calculates the Change Risk Anti-Patterns (CRAP) index for a unit of code
698 * based on its cyclomatic complexity and percentage of code coverage.
699 *
700 * @param int $ccn
701 * @param float $coverage
702 *
703 * @return string
704 */
705 protected function crap($ccn, $coverage)
706 {
707 if ($coverage == 0) {
708 return (string) (\pow($ccn, 2) + $ccn);
709 }
710
711 if ($coverage >= 95) {
712 return (string) $ccn;
713 }
714
715 return \sprintf(
716 '%01.2F',
717 \pow($ccn, 2) * \pow(1 - $coverage / 100, 3) + $ccn
718 );
719 }
720
721 /**
722 * @param string $methodName
723 * @param array $method
724 * @param string $link
725 *
726 * @return array
727 */
728 private function newMethod($methodName, array $method, $link)
729 {
730 return [
731 'methodName' => $methodName,
732 'visibility' => $method['visibility'],
733 'signature' => $method['signature'],
734 'startLine' => $method['startLine'],
735 'endLine' => $method['endLine'],
736 'executableLines' => 0,
737 'executedLines' => 0,
738 'ccn' => $method['ccn'],
739 'coverage' => 0,
740 'crap' => 0,
741 'link' => $link . $method['startLine'],
742 ];
743 }
744 }