File::calculateStatistics()   F
last analyzed

Complexity

Conditions 22
Paths > 20000

Size

Total Lines 137
Code Lines 81

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 22
eloc 81
nc 64000
nop 0
dl 0
loc 137
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
namespace SebastianBergmann\CodeCoverage\Node;
11
12
/**
13
 * Represents a file in the code coverage information tree.
14
 */
15
final class File extends AbstractNode
16
{
17
    /**
18
     * @var array
19
     */
20
    private $coverageData;
21
22
    /**
23
     * @var array
24
     */
25
    private $testData;
26
27
    /**
28
     * @var int
29
     */
30
    private $numExecutableLines = 0;
31
32
    /**
33
     * @var int
34
     */
35
    private $numExecutedLines = 0;
36
37
    /**
38
     * @var array
39
     */
40
    private $classes = [];
41
42
    /**
43
     * @var array
44
     */
45
    private $traits = [];
46
47
    /**
48
     * @var array
49
     */
50
    private $functions = [];
51
52
    /**
53
     * @var array
54
     */
55
    private $linesOfCode = [];
56
57
    /**
58
     * @var int
59
     */
60
    private $numClasses;
61
62
    /**
63
     * @var int
64
     */
65
    private $numTestedClasses = 0;
66
67
    /**
68
     * @var int
69
     */
70
    private $numTraits;
71
72
    /**
73
     * @var int
74
     */
75
    private $numTestedTraits = 0;
76
77
    /**
78
     * @var int
79
     */
80
    private $numMethods;
81
82
    /**
83
     * @var int
84
     */
85
    private $numTestedMethods;
86
87
    /**
88
     * @var int
89
     */
90
    private $numTestedFunctions;
91
92
    /**
93
     * @var bool
94
     */
95
    private $cacheTokens;
96
97
    /**
98
     * @var array
99
     */
100
    private $codeUnitsByLine = [];
101
102
    public function __construct(string $name, AbstractNode $parent, array $coverageData, array $testData, bool $cacheTokens)
103
    {
104
        parent::__construct($name, $parent);
105
106
        $this->coverageData = $coverageData;
107
        $this->testData     = $testData;
108
        $this->cacheTokens  = $cacheTokens;
109
110
        $this->calculateStatistics();
111
    }
112
113
    /**
114
     * Returns the number of files in/under this node.
115
     */
116
    public function count(): int
117
    {
118
        return 1;
119
    }
120
121
    /**
122
     * Returns the code coverage data of this node.
123
     */
124
    public function getCoverageData(): array
125
    {
126
        return $this->coverageData;
127
    }
128
129
    /**
130
     * Returns the test data of this node.
131
     */
132
    public function getTestData(): array
133
    {
134
        return $this->testData;
135
    }
136
137
    /**
138
     * Returns the classes of this node.
139
     */
140
    public function getClasses(): array
141
    {
142
        return $this->classes;
143
    }
144
145
    /**
146
     * Returns the traits of this node.
147
     */
148
    public function getTraits(): array
149
    {
150
        return $this->traits;
151
    }
152
153
    /**
154
     * Returns the functions of this node.
155
     */
156
    public function getFunctions(): array
157
    {
158
        return $this->functions;
159
    }
160
161
    /**
162
     * Returns the LOC/CLOC/NCLOC of this node.
163
     */
164
    public function getLinesOfCode(): array
165
    {
166
        return $this->linesOfCode;
167
    }
168
169
    /**
170
     * Returns the number of executable lines.
171
     */
172
    public function getNumExecutableLines(): int
173
    {
174
        return $this->numExecutableLines;
175
    }
176
177
    /**
178
     * Returns the number of executed lines.
179
     */
180
    public function getNumExecutedLines(): int
181
    {
182
        return $this->numExecutedLines;
183
    }
184
185
    /**
186
     * Returns the number of classes.
187
     */
188
    public function getNumClasses(): int
189
    {
190
        if ($this->numClasses === null) {
191
            $this->numClasses = 0;
192
193
            foreach ($this->classes as $class) {
194
                foreach ($class['methods'] as $method) {
195
                    if ($method['executableLines'] > 0) {
196
                        $this->numClasses++;
197
198
                        continue 2;
199
                    }
200
                }
201
            }
202
        }
203
204
        return $this->numClasses;
205
    }
206
207
    /**
208
     * Returns the number of tested classes.
209
     */
210
    public function getNumTestedClasses(): int
211
    {
212
        return $this->numTestedClasses;
213
    }
214
215
    /**
216
     * Returns the number of traits.
217
     */
218
    public function getNumTraits(): int
219
    {
220
        if ($this->numTraits === null) {
221
            $this->numTraits = 0;
222
223
            foreach ($this->traits as $trait) {
224
                foreach ($trait['methods'] as $method) {
225
                    if ($method['executableLines'] > 0) {
226
                        $this->numTraits++;
227
228
                        continue 2;
229
                    }
230
                }
231
            }
232
        }
233
234
        return $this->numTraits;
235
    }
236
237
    /**
238
     * Returns the number of tested traits.
239
     */
240
    public function getNumTestedTraits(): int
241
    {
242
        return $this->numTestedTraits;
243
    }
244
245
    /**
246
     * Returns the number of methods.
247
     */
248
    public function getNumMethods(): int
249
    {
250
        if ($this->numMethods === null) {
251
            $this->numMethods = 0;
252
253
            foreach ($this->classes as $class) {
254
                foreach ($class['methods'] as $method) {
255
                    if ($method['executableLines'] > 0) {
256
                        $this->numMethods++;
257
                    }
258
                }
259
            }
260
261
            foreach ($this->traits as $trait) {
262
                foreach ($trait['methods'] as $method) {
263
                    if ($method['executableLines'] > 0) {
264
                        $this->numMethods++;
265
                    }
266
                }
267
            }
268
        }
269
270
        return $this->numMethods;
271
    }
272
273
    /**
274
     * Returns the number of tested methods.
275
     */
276
    public function getNumTestedMethods(): int
277
    {
278
        if ($this->numTestedMethods === null) {
279
            $this->numTestedMethods = 0;
280
281
            foreach ($this->classes as $class) {
282
                foreach ($class['methods'] as $method) {
283
                    if ($method['executableLines'] > 0 &&
284
                        $method['coverage'] === 100) {
285
                        $this->numTestedMethods++;
286
                    }
287
                }
288
            }
289
290
            foreach ($this->traits as $trait) {
291
                foreach ($trait['methods'] as $method) {
292
                    if ($method['executableLines'] > 0 &&
293
                        $method['coverage'] === 100) {
294
                        $this->numTestedMethods++;
295
                    }
296
                }
297
            }
298
        }
299
300
        return $this->numTestedMethods;
301
    }
302
303
    /**
304
     * Returns the number of functions.
305
     */
306
    public function getNumFunctions(): int
307
    {
308
        return \count($this->functions);
309
    }
310
311
    /**
312
     * Returns the number of tested functions.
313
     */
314
    public function getNumTestedFunctions(): int
315
    {
316
        if ($this->numTestedFunctions === null) {
317
            $this->numTestedFunctions = 0;
318
319
            foreach ($this->functions as $function) {
320
                if ($function['executableLines'] > 0 &&
321
                    $function['coverage'] === 100) {
322
                    $this->numTestedFunctions++;
323
                }
324
            }
325
        }
326
327
        return $this->numTestedFunctions;
328
    }
329
330
    private function calculateStatistics(): void
331
    {
332
        if ($this->cacheTokens) {
333
            $tokens = \PHP_Token_Stream_CachingFactory::get($this->getPath());
334
        } else {
335
            $tokens = new \PHP_Token_Stream($this->getPath());
336
        }
337
338
        $this->linesOfCode = $tokens->getLinesOfCode();
339
340
        foreach (\range(1, $this->linesOfCode['loc']) as $lineNumber) {
341
            $this->codeUnitsByLine[$lineNumber] = [];
342
        }
343
344
        try {
345
            $this->processClasses($tokens);
346
            $this->processTraits($tokens);
347
            $this->processFunctions($tokens);
348
        } catch (\OutOfBoundsException $e) {
349
            // This can happen with PHP_Token_Stream if the file is syntactically invalid,
350
            // and probably affects a file that wasn't executed.
351
        }
352
        unset($tokens);
353
354
        foreach (\range(1, $this->linesOfCode['loc']) as $lineNumber) {
355
            if (isset($this->coverageData[$lineNumber])) {
356
                foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) {
357
                    $codeUnit['executableLines']++;
358
                }
359
360
                unset($codeUnit);
361
362
                $this->numExecutableLines++;
363
364
                if (\count($this->coverageData[$lineNumber]) > 0) {
365
                    foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) {
366
                        $codeUnit['executedLines']++;
367
                    }
368
369
                    unset($codeUnit);
370
371
                    $this->numExecutedLines++;
372
                }
373
            }
374
        }
375
376
        foreach ($this->traits as &$trait) {
377
            foreach ($trait['methods'] as &$method) {
378
                if ($method['executableLines'] > 0) {
379
                    $method['coverage'] = ($method['executedLines'] /
380
                            $method['executableLines']) * 100;
381
                } else {
382
                    $method['coverage'] = 100;
383
                }
384
385
                $method['crap'] = $this->crap(
386
                    $method['ccn'],
387
                    $method['coverage']
388
                );
389
390
                $trait['ccn'] += $method['ccn'];
391
            }
392
393
            unset($method);
394
395
            if ($trait['executableLines'] > 0) {
396
                $trait['coverage'] = ($trait['executedLines'] /
397
                        $trait['executableLines']) * 100;
398
399
                if ($trait['coverage'] === 100) {
400
                    $this->numTestedClasses++;
401
                }
402
            } else {
403
                $trait['coverage'] = 100;
404
            }
405
406
            $trait['crap'] = $this->crap(
407
                $trait['ccn'],
408
                $trait['coverage']
409
            );
410
        }
411
412
        unset($trait);
413
414
        foreach ($this->classes as &$class) {
415
            foreach ($class['methods'] as &$method) {
416
                if ($method['executableLines'] > 0) {
417
                    $method['coverage'] = ($method['executedLines'] /
418
                            $method['executableLines']) * 100;
419
                } else {
420
                    $method['coverage'] = 100;
421
                }
422
423
                $method['crap'] = $this->crap(
424
                    $method['ccn'],
425
                    $method['coverage']
426
                );
427
428
                $class['ccn'] += $method['ccn'];
429
            }
430
431
            unset($method);
432
433
            if ($class['executableLines'] > 0) {
434
                $class['coverage'] = ($class['executedLines'] /
435
                        $class['executableLines']) * 100;
436
437
                if ($class['coverage'] === 100) {
438
                    $this->numTestedClasses++;
439
                }
440
            } else {
441
                $class['coverage'] = 100;
442
            }
443
444
            $class['crap'] = $this->crap(
445
                $class['ccn'],
446
                $class['coverage']
447
            );
448
        }
449
450
        unset($class);
451
452
        foreach ($this->functions as &$function) {
453
            if ($function['executableLines'] > 0) {
454
                $function['coverage'] = ($function['executedLines'] /
455
                        $function['executableLines']) * 100;
456
            } else {
457
                $function['coverage'] = 100;
458
            }
459
460
            if ($function['coverage'] === 100) {
461
                $this->numTestedFunctions++;
462
            }
463
464
            $function['crap'] = $this->crap(
465
                $function['ccn'],
466
                $function['coverage']
467
            );
468
        }
469
    }
470
471
    private function processClasses(\PHP_Token_Stream $tokens): void
472
    {
473
        $classes = $tokens->getClasses();
474
        $link    = $this->getId() . '.html#';
475
476
        foreach ($classes as $className => $class) {
477
            if (\strpos($className, 'anonymous') === 0) {
478
                continue;
479
            }
480
481
            if (!empty($class['package']['namespace'])) {
482
                $className = $class['package']['namespace'] . '\\' . $className;
483
            }
484
485
            $this->classes[$className] = [
486
                'className'       => $className,
487
                'methods'         => [],
488
                'startLine'       => $class['startLine'],
489
                'executableLines' => 0,
490
                'executedLines'   => 0,
491
                'ccn'             => 0,
492
                'coverage'        => 0,
493
                'crap'            => 0,
494
                'package'         => $class['package'],
495
                'link'            => $link . $class['startLine'],
496
            ];
497
498
            foreach ($class['methods'] as $methodName => $method) {
499
                if (\strpos($methodName, 'anonymous') === 0) {
500
                    continue;
501
                }
502
503
                $this->classes[$className]['methods'][$methodName] = $this->newMethod($methodName, $method, $link);
504
505
                foreach (\range($method['startLine'], $method['endLine']) as $lineNumber) {
506
                    $this->codeUnitsByLine[$lineNumber] = [
507
                        &$this->classes[$className],
508
                        &$this->classes[$className]['methods'][$methodName],
509
                    ];
510
                }
511
            }
512
        }
513
    }
514
515
    private function processTraits(\PHP_Token_Stream $tokens): void
516
    {
517
        $traits = $tokens->getTraits();
518
        $link   = $this->getId() . '.html#';
519
520
        foreach ($traits as $traitName => $trait) {
521
            $this->traits[$traitName] = [
522
                'traitName'       => $traitName,
523
                'methods'         => [],
524
                'startLine'       => $trait['startLine'],
525
                'executableLines' => 0,
526
                'executedLines'   => 0,
527
                'ccn'             => 0,
528
                'coverage'        => 0,
529
                'crap'            => 0,
530
                'package'         => $trait['package'],
531
                'link'            => $link . $trait['startLine'],
532
            ];
533
534
            foreach ($trait['methods'] as $methodName => $method) {
535
                if (\strpos($methodName, 'anonymous') === 0) {
536
                    continue;
537
                }
538
539
                $this->traits[$traitName]['methods'][$methodName] = $this->newMethod($methodName, $method, $link);
540
541
                foreach (\range($method['startLine'], $method['endLine']) as $lineNumber) {
542
                    $this->codeUnitsByLine[$lineNumber] = [
543
                        &$this->traits[$traitName],
544
                        &$this->traits[$traitName]['methods'][$methodName],
545
                    ];
546
                }
547
            }
548
        }
549
    }
550
551
    private function processFunctions(\PHP_Token_Stream $tokens): void
552
    {
553
        $functions = $tokens->getFunctions();
554
        $link      = $this->getId() . '.html#';
555
556
        foreach ($functions as $functionName => $function) {
557
            if (\strpos($functionName, 'anonymous') === 0) {
558
                continue;
559
            }
560
561
            $this->functions[$functionName] = [
562
                'functionName'    => $functionName,
563
                'signature'       => $function['signature'],
564
                'startLine'       => $function['startLine'],
565
                'executableLines' => 0,
566
                'executedLines'   => 0,
567
                'ccn'             => $function['ccn'],
568
                'coverage'        => 0,
569
                'crap'            => 0,
570
                'link'            => $link . $function['startLine'],
571
            ];
572
573
            foreach (\range($function['startLine'], $function['endLine']) as $lineNumber) {
574
                $this->codeUnitsByLine[$lineNumber] = [&$this->functions[$functionName]];
575
            }
576
        }
577
    }
578
579
    private function crap(int $ccn, float $coverage): string
580
    {
581
        if ($coverage === 0) {
0 ignored issues
show
introduced by
The condition $coverage === 0 is always false.
Loading history...
582
            return (string) ($ccn ** 2 + $ccn);
583
        }
584
585
        if ($coverage >= 95) {
586
            return (string) $ccn;
587
        }
588
589
        return \sprintf(
590
            '%01.2F',
591
            $ccn ** 2 * (1 - $coverage / 100) ** 3 + $ccn
592
        );
593
    }
594
595
    private function newMethod(string $methodName, array $method, string $link): array
596
    {
597
        return [
598
            'methodName'      => $methodName,
599
            'visibility'      => $method['visibility'],
600
            'signature'       => $method['signature'],
601
            'startLine'       => $method['startLine'],
602
            'endLine'         => $method['endLine'],
603
            'executableLines' => 0,
604
            'executedLines'   => 0,
605
            'ccn'             => $method['ccn'],
606
            'coverage'        => 0,
607
            'crap'            => 0,
608
            'link'            => $link . $method['startLine'],
609
        ];
610
    }
611
}
612