Completed
Push — master ( 96d573...f9f049 )
by Ehsan
07:54
created

File::crap()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 2
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/*
3
 * This file is part of the php-code-coverage package.
4
 *
5
 * (c) Sebastian Bergmann <[email protected]>
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $classStack of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $functionStack of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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
574
    /**
575
     * @param \PHP_Token_Stream $tokens
576
     */
577
    protected function processClasses(\PHP_Token_Stream $tokens)
578
    {
579
        $classes = $tokens->getClasses();
580
        unset($tokens);
581
582
        $link = $this->getId() . '.html#';
583
584
        foreach ($classes as $className => $class) {
585
            $this->classes[$className] = [
586
                'className'       => $className,
587
                'methods'         => [],
588
                'startLine'       => $class['startLine'],
589
                'executableLines' => 0,
590
                'executedLines'   => 0,
591
                'ccn'             => 0,
592
                'coverage'        => 0,
593
                'crap'            => 0,
594
                'package'         => $class['package'],
595
                'link'            => $link . $class['startLine']
596
            ];
597
598
            $this->startLines[$class['startLine']] = &$this->classes[$className];
599
            $this->endLines[$class['endLine']]     = &$this->classes[$className];
600
601
            foreach ($class['methods'] as $methodName => $method) {
602
                $this->classes[$className]['methods'][$methodName] = $this->newMethod($methodName, $method, $link);
603
604
                $this->startLines[$method['startLine']] = &$this->classes[$className]['methods'][$methodName];
605
                $this->endLines[$method['endLine']]     = &$this->classes[$className]['methods'][$methodName];
606
            }
607
        }
608
    }
609
610
    /**
611
     * @param \PHP_Token_Stream $tokens
612
     */
613
    protected function processTraits(\PHP_Token_Stream $tokens)
614
    {
615
        $traits = $tokens->getTraits();
616
        unset($tokens);
617
618
        $link = $this->getId() . '.html#';
619
620
        foreach ($traits as $traitName => $trait) {
621
            $this->traits[$traitName] = [
622
                'traitName'       => $traitName,
623
                'methods'         => [],
624
                'startLine'       => $trait['startLine'],
625
                'executableLines' => 0,
626
                'executedLines'   => 0,
627
                'ccn'             => 0,
628
                'coverage'        => 0,
629
                'crap'            => 0,
630
                'package'         => $trait['package'],
631
                'link'            => $link . $trait['startLine']
632
            ];
633
634
            $this->startLines[$trait['startLine']] = &$this->traits[$traitName];
635
            $this->endLines[$trait['endLine']]     = &$this->traits[$traitName];
636
637
            foreach ($trait['methods'] as $methodName => $method) {
638
                $this->traits[$traitName]['methods'][$methodName] = $this->newMethod($methodName, $method, $link);
639
640
                $this->startLines[$method['startLine']] = &$this->traits[$traitName]['methods'][$methodName];
641
                $this->endLines[$method['endLine']]     = &$this->traits[$traitName]['methods'][$methodName];
642
            }
643
        }
644
    }
645
646
    /**
647
     * @param \PHP_Token_Stream $tokens
648
     */
649
    protected function processFunctions(\PHP_Token_Stream $tokens)
650
    {
651
        $functions = $tokens->getFunctions();
652
        unset($tokens);
653
654
        $link = $this->getId() . '.html#';
655
656
        foreach ($functions as $functionName => $function) {
657
            $this->functions[$functionName] = [
658
                'functionName'    => $functionName,
659
                'signature'       => $function['signature'],
660
                'startLine'       => $function['startLine'],
661
                'executableLines' => 0,
662
                'executedLines'   => 0,
663
                'ccn'             => $function['ccn'],
664
                'coverage'        => 0,
665
                'crap'            => 0,
666
                'link'            => $link . $function['startLine']
667
            ];
668
669
            $this->startLines[$function['startLine']] = &$this->functions[$functionName];
670
            $this->endLines[$function['endLine']]     = &$this->functions[$functionName];
671
        }
672
    }
673
674
    /**
675
     * Calculates the Change Risk Anti-Patterns (CRAP) index for a unit of code
676
     * based on its cyclomatic complexity and percentage of code coverage.
677
     *
678
     * @param int   $ccn
679
     * @param float $coverage
680
     *
681
     * @return string
682
     */
683
    protected function crap($ccn, $coverage)
684
    {
685
        if ($coverage == 0) {
686
            return (string) (pow($ccn, 2) + $ccn);
687
        }
688
689
        if ($coverage >= 95) {
690
            return (string) $ccn;
691
        }
692
693
        return sprintf(
694
            '%01.2F',
695
            pow($ccn, 2) * pow(1 - $coverage / 100, 3) + $ccn
696
        );
697
    }
698
699
    /**
700
     * @param string $methodName
701
     * @param array  $method
702
     * @param string $link
703
     *
704
     * @return array
705
     */
706
    private function newMethod($methodName, array $method, $link)
707
    {
708
        return [
709
            'methodName'      => $methodName,
710
            'visibility'      => $method['visibility'],
711
            'signature'       => $method['signature'],
712
            'startLine'       => $method['startLine'],
713
            'endLine'         => $method['endLine'],
714
            'executableLines' => 0,
715
            'executedLines'   => 0,
716
            'ccn'             => $method['ccn'],
717
            'coverage'        => 0,
718
            'crap'            => 0,
719
            'link'            => $link . $method['startLine'],
720
        ];
721
    }
722
}
723