Completed
Push — master ( 95acd7...e70d94 )
by Naveen
13:02 queued 11:07
created

PHP_CodeCoverage   F

Complexity

Total Complexity 153

Size/Duplication

Total Lines 903
Duplicated Lines 3.32 %

Coupling/Cohesion

Components 1
Dependencies 15
Metric Value
wmc 153
lcom 1
cbo 15
dl 30
loc 903
rs 1.263

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PHP_CodeCoverage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PHP_CodeCoverage, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * This file is part of the PHP_CodeCoverage 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
use SebastianBergmann\Environment\Runtime;
12
13
/**
14
 * Provides collection functionality for PHP code coverage information.
15
 *
16
 * @since Class available since Release 1.0.0
17
 */
18
class PHP_CodeCoverage
19
{
20
    /**
21
     * @var PHP_CodeCoverage_Driver
22
     */
23
    private $driver;
24
25
    /**
26
     * @var PHP_CodeCoverage_Filter
27
     */
28
    private $filter;
29
30
    /**
31
     * @var bool
32
     */
33
    private $cacheTokens = false;
34
35
    /**
36
     * @var bool
37
     */
38
    private $checkForUnintentionallyCoveredCode = false;
39
40
    /**
41
     * @var bool
42
     */
43
    private $forceCoversAnnotation = false;
44
45
    /**
46
     * @var bool
47
     */
48
    private $mapTestClassNameToCoveredClassName = false;
49
50
    /**
51
     * @var bool
52
     */
53
    private $addUncoveredFilesFromWhitelist = true;
54
55
    /**
56
     * @var bool
57
     */
58
    private $processUncoveredFilesFromWhitelist = false;
59
60
    /**
61
     * @var mixed
62
     */
63
    private $currentId;
64
65
    /**
66
     * Code coverage data.
67
     *
68
     * @var array
69
     */
70
    private $data = array();
71
72
    /**
73
     * @var array
74
     */
75
    private $ignoredLines = array();
76
77
    /**
78
     * @var bool
79
     */
80
    private $disableIgnoredLines = false;
81
82
    /**
83
     * Test data.
84
     *
85
     * @var array
86
     */
87
    private $tests = array();
88
89
    /**
90
     * Constructor.
91
     *
92
     * @param  PHP_CodeCoverage_Driver    $driver
93
     * @param  PHP_CodeCoverage_Filter    $filter
94
     * @throws PHP_CodeCoverage_Exception
95
     */
96
    public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null)
97
    {
98
        if ($driver === null) {
99
            $driver = $this->selectDriver();
100
        }
101
102
        if ($filter === null) {
103
            $filter = new PHP_CodeCoverage_Filter;
104
        }
105
106
        $this->driver = $driver;
107
        $this->filter = $filter;
108
    }
109
110
    /**
111
     * Returns the PHP_CodeCoverage_Report_Node_* object graph
112
     * for this PHP_CodeCoverage object.
113
     *
114
     * @return PHP_CodeCoverage_Report_Node_Directory
115
     * @since  Method available since Release 1.1.0
116
     */
117
    public function getReport()
118
    {
119
        $factory = new PHP_CodeCoverage_Report_Factory;
120
121
        return $factory->create($this);
122
    }
123
124
    /**
125
     * Clears collected code coverage data.
126
     */
127
    public function clear()
128
    {
129
        $this->currentId = null;
130
        $this->data      = array();
131
        $this->tests     = array();
132
    }
133
134
    /**
135
     * Returns the PHP_CodeCoverage_Filter used.
136
     *
137
     * @return PHP_CodeCoverage_Filter
138
     */
139
    public function filter()
140
    {
141
        return $this->filter;
142
    }
143
144
    /**
145
     * Returns the collected code coverage data.
146
     * Set $raw = true to bypass all filters.
147
     *
148
     * @param  bool  $raw
149
     * @return array
150
     * @since  Method available since Release 1.1.0
151
     */
152
    public function getData($raw = false)
153
    {
154
        if (!$raw && $this->addUncoveredFilesFromWhitelist) {
155
            $this->addUncoveredFilesFromWhitelist();
156
        }
157
158
        // We need to apply the blacklist filter a second time
159
        // when no whitelist is used.
160
        if (!$raw && !$this->filter->hasWhitelist()) {
161
            $this->applyListsFilter($this->data);
162
        }
163
164
        return $this->data;
165
    }
166
167
    /**
168
     * Sets the coverage data.
169
     *
170
     * @param array $data
171
     * @since Method available since Release 2.0.0
172
     */
173
    public function setData(array $data)
174
    {
175
        $this->data = $data;
176
    }
177
178
    /**
179
     * Returns the test data.
180
     *
181
     * @return array
182
     * @since  Method available since Release 1.1.0
183
     */
184
    public function getTests()
185
    {
186
        return $this->tests;
187
    }
188
189
    /**
190
     * Sets the test data.
191
     *
192
     * @param array $tests
193
     * @since Method available since Release 2.0.0
194
     */
195
    public function setTests(array $tests)
196
    {
197
        $this->tests = $tests;
198
    }
199
200
    /**
201
     * Start collection of code coverage information.
202
     *
203
     * @param  mixed                      $id
204
     * @param  bool                       $clear
205
     * @throws PHP_CodeCoverage_Exception
206
     */
207
    public function start($id, $clear = false)
208
    {
209
        if (!is_bool($clear)) {
210
            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
211
                1,
212
                'boolean'
213
            );
214
        }
215
216
        if ($clear) {
217
            $this->clear();
218
        }
219
220
        $this->currentId = $id;
221
222
        $this->driver->start();
223
    }
224
225
    /**
226
     * Stop collection of code coverage information.
227
     *
228
     * @param  bool                       $append
229
     * @param  mixed                      $linesToBeCovered
230
     * @param  array                      $linesToBeUsed
231
     * @return array
232
     * @throws PHP_CodeCoverage_Exception
233
     */
234
    public function stop($append = true, $linesToBeCovered = array(), array $linesToBeUsed = array())
235
    {
236
        if (!is_bool($append)) {
237
            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
238
                1,
239
                'boolean'
240
            );
241
        }
242
243
        if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) {
244
            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
245
                2,
246
                'array or false'
247
            );
248
        }
249
250
        $data = $this->driver->stop();
251
        $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed);
252
253
        $this->currentId = null;
254
255
        return $data;
256
    }
257
258
    /**
259
     * Appends code coverage data.
260
     *
261
     * @param  array                      $data
262
     * @param  mixed                      $id
263
     * @param  bool                       $append
264
     * @param  mixed                      $linesToBeCovered
265
     * @param  array                      $linesToBeUsed
266
     * @throws PHP_CodeCoverage_Exception
267
     */
268
    public function append(array $data, $id = null, $append = true, $linesToBeCovered = array(), array $linesToBeUsed = array())
269
    {
270
        if ($id === null) {
271
            $id = $this->currentId;
272
        }
273
274
        if ($id === null) {
275
            throw new PHP_CodeCoverage_Exception;
276
        }
277
278
        $this->applyListsFilter($data);
279
        $this->applyIgnoredLinesFilter($data);
280
        $this->initializeFilesThatAreSeenTheFirstTime($data);
281
282
        if (!$append) {
283
            return;
284
        }
285
286
        if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') {
287
            $this->applyCoversAnnotationFilter(
288
                $data,
289
                $linesToBeCovered,
290
                $linesToBeUsed
291
            );
292
        }
293
294
        if (empty($data)) {
295
            return;
296
        }
297
298
        $size   = 'unknown';
299
        $status = null;
300
301
        if ($id instanceof PHPUnit_Framework_TestCase) {
302
            $_size = $id->getSize();
303
304
            if ($_size == PHPUnit_Util_Test::SMALL) {
305
                $size = 'small';
306
            } elseif ($_size == PHPUnit_Util_Test::MEDIUM) {
307
                $size = 'medium';
308
            } elseif ($_size == PHPUnit_Util_Test::LARGE) {
309
                $size = 'large';
310
            }
311
312
            $status = $id->getStatus();
313
            $id     = get_class($id) . '::' . $id->getName();
314
        } elseif ($id instanceof PHPUnit_Extensions_PhptTestCase) {
315
            $size = 'large';
316
            $id   = $id->getName();
317
        }
318
319
        $this->tests[$id] = array('size' => $size, 'status' => $status);
320
321
        foreach ($data as $file => $lines) {
322
            if (!$this->filter->isFile($file)) {
323
                continue;
324
            }
325
326
            foreach ($lines as $k => $v) {
327
                if ($v == PHP_CodeCoverage_Driver::LINE_EXECUTED) {
328
                    if (empty($this->data[$file][$k]) || !in_array($id, $this->data[$file][$k])) {
329
                        $this->data[$file][$k][] = $id;
330
                    }
331
                }
332
            }
333
        }
334
    }
335
336
    /**
337
     * Merges the data from another instance of PHP_CodeCoverage.
338
     *
339
     * @param PHP_CodeCoverage $that
340
     */
341
    public function merge(PHP_CodeCoverage $that)
342
    {
343
        $this->filter->setBlacklistedFiles(
344
            array_merge($this->filter->getBlacklistedFiles(), $that->filter()->getBlacklistedFiles())
345
        );
346
347
        $this->filter->setWhitelistedFiles(
348
            array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles())
349
        );
350
351
        foreach ($that->data as $file => $lines) {
352
            if (!isset($this->data[$file])) {
353
                if (!$this->filter->isFiltered($file)) {
354
                    $this->data[$file] = $lines;
355
                }
356
357
                continue;
358
            }
359
360
            foreach ($lines as $line => $data) {
361
                if ($data !== null) {
362
                    if (!isset($this->data[$file][$line])) {
363
                        $this->data[$file][$line] = $data;
364
                    } else {
365
                        $this->data[$file][$line] = array_unique(
366
                            array_merge($this->data[$file][$line], $data)
367
                        );
368
                    }
369
                }
370
            }
371
        }
372
373
        $this->tests = array_merge($this->tests, $that->getTests());
374
375
    }
376
377
    /**
378
     * @param  bool                       $flag
379
     * @throws PHP_CodeCoverage_Exception
380
     * @since  Method available since Release 1.1.0
381
     */
382
    public function setCacheTokens($flag)
383
    {
384
        if (!is_bool($flag)) {
385
            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
386
                1,
387
                'boolean'
388
            );
389
        }
390
391
        $this->cacheTokens = $flag;
392
    }
393
394
    /**
395
     * @since Method available since Release 1.1.0
396
     */
397
    public function getCacheTokens()
398
    {
399
        return $this->cacheTokens;
400
    }
401
402
    /**
403
     * @param  bool                       $flag
404
     * @throws PHP_CodeCoverage_Exception
405
     * @since  Method available since Release 2.0.0
406
     */
407
    public function setCheckForUnintentionallyCoveredCode($flag)
408
    {
409
        if (!is_bool($flag)) {
410
            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
411
                1,
412
                'boolean'
413
            );
414
        }
415
416
        $this->checkForUnintentionallyCoveredCode = $flag;
417
    }
418
419
    /**
420
     * @param  bool                       $flag
421
     * @throws PHP_CodeCoverage_Exception
422
     */
423
    public function setForceCoversAnnotation($flag)
424
    {
425
        if (!is_bool($flag)) {
426
            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
427
                1,
428
                'boolean'
429
            );
430
        }
431
432
        $this->forceCoversAnnotation = $flag;
433
    }
434
435
    /**
436
     * @param  bool                       $flag
437
     * @throws PHP_CodeCoverage_Exception
438
     */
439
    public function setMapTestClassNameToCoveredClassName($flag)
440
    {
441
        if (!is_bool($flag)) {
442
            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
443
                1,
444
                'boolean'
445
            );
446
        }
447
448
        $this->mapTestClassNameToCoveredClassName = $flag;
449
    }
450
451
    /**
452
     * @param  bool                       $flag
453
     * @throws PHP_CodeCoverage_Exception
454
     */
455
    public function setAddUncoveredFilesFromWhitelist($flag)
456
    {
457
        if (!is_bool($flag)) {
458
            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
459
                1,
460
                'boolean'
461
            );
462
        }
463
464
        $this->addUncoveredFilesFromWhitelist = $flag;
465
    }
466
467
    /**
468
     * @param  bool                       $flag
469
     * @throws PHP_CodeCoverage_Exception
470
     */
471
    public function setProcessUncoveredFilesFromWhitelist($flag)
472
    {
473
        if (!is_bool($flag)) {
474
            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
475
                1,
476
                'boolean'
477
            );
478
        }
479
480
        $this->processUncoveredFilesFromWhitelist = $flag;
481
    }
482
483
    /**
484
     * @param  bool                       $flag
485
     * @throws PHP_CodeCoverage_Exception
486
     */
487
    public function setDisableIgnoredLines($flag)
488
    {
489
        if (!is_bool($flag)) {
490
            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
491
                1,
492
                'boolean'
493
            );
494
        }
495
496
        $this->disableIgnoredLines = $flag;
497
    }
498
499
    /**
500
     * Applies the @covers annotation filtering.
501
     *
502
     * @param  array                                                 $data
503
     * @param  mixed                                                 $linesToBeCovered
504
     * @param  array                                                 $linesToBeUsed
505
     * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode
506
     */
507
    private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed)
508
    {
509
        if ($linesToBeCovered === false ||
510
            ($this->forceCoversAnnotation && empty($linesToBeCovered))) {
511
            $data = array();
512
513
            return;
514
        }
515
516
        if (empty($linesToBeCovered)) {
517
            return;
518
        }
519
520
        if ($this->checkForUnintentionallyCoveredCode) {
521
            $this->performUnintentionallyCoveredCodeCheck(
522
                $data,
523
                $linesToBeCovered,
524
                $linesToBeUsed
525
            );
526
        }
527
528
        $data = array_intersect_key($data, $linesToBeCovered);
529
530
        foreach (array_keys($data) as $filename) {
531
            $_linesToBeCovered = array_flip($linesToBeCovered[$filename]);
532
533
            $data[$filename] = array_intersect_key(
534
                $data[$filename],
535
                $_linesToBeCovered
536
            );
537
        }
538
    }
539
540
    /**
541
     * Applies the blacklist/whitelist filtering.
542
     *
543
     * @param array $data
544
     */
545
    private function applyListsFilter(array &$data)
546
    {
547
        foreach (array_keys($data) as $filename) {
548
            if ($this->filter->isFiltered($filename)) {
549
                unset($data[$filename]);
550
            }
551
        }
552
    }
553
554
    /**
555
     * Applies the "ignored lines" filtering.
556
     *
557
     * @param array $data
558
     */
559
    private function applyIgnoredLinesFilter(array &$data)
560
    {
561
        foreach (array_keys($data) as $filename) {
562
            if (!$this->filter->isFile($filename)) {
563
                continue;
564
            }
565
566
            foreach ($this->getLinesToBeIgnored($filename) as $line) {
567
                unset($data[$filename][$line]);
568
            }
569
        }
570
    }
571
572
    /**
573
     * @param array $data
574
     * @since Method available since Release 1.1.0
575
     */
576
    private function initializeFilesThatAreSeenTheFirstTime(array $data)
577
    {
578
        foreach ($data as $file => $lines) {
579
            if ($this->filter->isFile($file) && !isset($this->data[$file])) {
580
                $this->data[$file] = array();
581
582
                foreach ($lines as $k => $v) {
583
                    $this->data[$file][$k] = $v == -2 ? null : array();
584
                }
585
            }
586
        }
587
    }
588
589
    /**
590
     * Processes whitelisted files that are not covered.
591
     */
592
    private function addUncoveredFilesFromWhitelist()
593
    {
594
        $data           = array();
595
        $uncoveredFiles = array_diff(
596
            $this->filter->getWhitelist(),
597
            array_keys($this->data)
598
        );
599
600
        foreach ($uncoveredFiles as $uncoveredFile) {
601
            if (!file_exists($uncoveredFile)) {
602
                continue;
603
            }
604
605
            if ($this->processUncoveredFilesFromWhitelist) {
606
                $this->processUncoveredFileFromWhitelist(
607
                    $uncoveredFile,
608
                    $data,
609
                    $uncoveredFiles
610
                );
611
            } else {
612
                $data[$uncoveredFile] = array();
613
614
                $lines = count(file($uncoveredFile));
615
616
                for ($i = 1; $i <= $lines; $i++) {
617
                    $data[$uncoveredFile][$i] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED;
618
                }
619
            }
620
        }
621
622
        $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
623
    }
624
625
    /**
626
     * @param string $uncoveredFile
627
     * @param array  $data
628
     * @param array  $uncoveredFiles
629
     */
630
    private function processUncoveredFileFromWhitelist($uncoveredFile, array &$data, array $uncoveredFiles)
631
    {
632
        $this->driver->start();
633
        include_once $uncoveredFile;
634
        $coverage = $this->driver->stop();
635
636
        foreach ($coverage as $file => $fileCoverage) {
637
            if (!isset($data[$file]) &&
638
                in_array($file, $uncoveredFiles)) {
639
                foreach (array_keys($fileCoverage) as $key) {
640
                    if ($fileCoverage[$key] == PHP_CodeCoverage_Driver::LINE_EXECUTED) {
641
                        $fileCoverage[$key] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED;
642
                    }
643
                }
644
645
                $data[$file] = $fileCoverage;
646
            }
647
        }
648
    }
649
650
    /**
651
     * Returns the lines of a source file that should be ignored.
652
     *
653
     * @param  string                     $filename
654
     * @return array
655
     * @throws PHP_CodeCoverage_Exception
656
     * @since  Method available since Release 2.0.0
657
     */
658
    private function getLinesToBeIgnored($filename)
659
    {
660
        if (!is_string($filename)) {
661
            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
662
                1,
663
                'string'
664
            );
665
        }
666
667
        if (!isset($this->ignoredLines[$filename])) {
668
            $this->ignoredLines[$filename] = array();
669
670
            if ($this->disableIgnoredLines) {
671
                return $this->ignoredLines[$filename];
672
            }
673
674
            $ignore   = false;
675
            $stop     = false;
676
            $lines    = file($filename);
677
            $numLines = count($lines);
678
679
            foreach ($lines as $index => $line) {
680
                if (!trim($line)) {
681
                    $this->ignoredLines[$filename][] = $index + 1;
682
                }
683
            }
684
685
            if ($this->cacheTokens) {
686
                $tokens = PHP_Token_Stream_CachingFactory::get($filename);
687
            } else {
688
                $tokens = new PHP_Token_Stream($filename);
689
            }
690
691
            $classes = array_merge($tokens->getClasses(), $tokens->getTraits());
692
            $tokens  = $tokens->tokens();
693
694
            foreach ($tokens as $token) {
695
                switch (get_class($token)) {
696
                    case 'PHP_Token_COMMENT':
697
                    case 'PHP_Token_DOC_COMMENT':
698
                        $_token = trim($token);
699
                        $_line  = trim($lines[$token->getLine() - 1]);
700
701
                        if ($_token == '// @codeCoverageIgnore' ||
702
                            $_token == '//@codeCoverageIgnore') {
703
                            $ignore = true;
704
                            $stop   = true;
705
                        } elseif ($_token == '// @codeCoverageIgnoreStart' ||
706
                            $_token == '//@codeCoverageIgnoreStart') {
707
                            $ignore = true;
708
                        } elseif ($_token == '// @codeCoverageIgnoreEnd' ||
709
                            $_token == '//@codeCoverageIgnoreEnd') {
710
                            $stop = true;
711
                        }
712
713
                        if (!$ignore) {
714
                            $start = $token->getLine();
715
                            $end   = $start + substr_count($token, "\n");
716
717
                            // Do not ignore the first line when there is a token
718
                            // before the comment
719
                            if (0 !== strpos($_token, $_line)) {
720
                                $start++;
721
                            }
722
723
                            for ($i = $start; $i < $end; $i++) {
724
                                $this->ignoredLines[$filename][] = $i;
725
                            }
726
727
                            // A DOC_COMMENT token or a COMMENT token starting with "/*"
728
                            // does not contain the final \n character in its text
729
                            if (isset($lines[$i-1]) && 0 === strpos($_token, '/*') && '*/' === substr(trim($lines[$i-1]), -2)) {
730
                                $this->ignoredLines[$filename][] = $i;
731
                            }
732
                        }
733
                        break;
734
735
                    case 'PHP_Token_INTERFACE':
736
                    case 'PHP_Token_TRAIT':
737
                    case 'PHP_Token_CLASS':
738
                    case 'PHP_Token_FUNCTION':
739
                        $docblock = $token->getDocblock();
740
741
                        $this->ignoredLines[$filename][] = $token->getLine();
742
743
                        if (strpos($docblock, '@codeCoverageIgnore') || strpos($docblock, '@deprecated')) {
744
                            $endLine = $token->getEndLine();
745
746
                            for ($i = $token->getLine(); $i <= $endLine; $i++) {
747
                                $this->ignoredLines[$filename][] = $i;
748
                            }
749
                        } elseif ($token instanceof PHP_Token_INTERFACE ||
750
                            $token instanceof PHP_Token_TRAIT ||
751
                            $token instanceof PHP_Token_CLASS) {
752
                            if (empty($classes[$token->getName()]['methods'])) {
753
                                for ($i = $token->getLine();
754
                                     $i <= $token->getEndLine();
755
                                     $i++) {
756
                                    $this->ignoredLines[$filename][] = $i;
757
                                }
758
                            } else {
759
                                $firstMethod = array_shift(
760
                                    $classes[$token->getName()]['methods']
761
                                );
762
763
                                do {
764
                                    $lastMethod = array_pop(
765
                                        $classes[$token->getName()]['methods']
766
                                    );
767
                                } while ($lastMethod !== null &&
768
                                    substr($lastMethod['signature'], 0, 18) == 'anonymous function');
769
770
                                if ($lastMethod === null) {
771
                                    $lastMethod = $firstMethod;
772
                                }
773
774
                                for ($i = $token->getLine();
775
                                     $i < $firstMethod['startLine'];
776
                                     $i++) {
777
                                    $this->ignoredLines[$filename][] = $i;
778
                                }
779
780
                                for ($i = $token->getEndLine();
781
                                     $i > $lastMethod['endLine'];
782
                                     $i--) {
783
                                    $this->ignoredLines[$filename][] = $i;
784
                                }
785
                            }
786
                        }
787
                        break;
788
789
                    case 'PHP_Token_NAMESPACE':
790
                        $this->ignoredLines[$filename][] = $token->getEndLine();
791
792
                    // Intentional fallthrough
793
                    case 'PHP_Token_OPEN_TAG':
794
                    case 'PHP_Token_CLOSE_TAG':
795
                    case 'PHP_Token_USE':
796
                        $this->ignoredLines[$filename][] = $token->getLine();
797
                        break;
798
                }
799
800
                if ($ignore) {
801
                    $this->ignoredLines[$filename][] = $token->getLine();
802
803
                    if ($stop) {
804
                        $ignore = false;
805
                        $stop   = false;
806
                    }
807
                }
808
            }
809
810
            $this->ignoredLines[$filename][] = $numLines + 1;
811
812
            $this->ignoredLines[$filename] = array_unique(
813
                $this->ignoredLines[$filename]
814
            );
815
816
            sort($this->ignoredLines[$filename]);
817
        }
818
819
        return $this->ignoredLines[$filename];
820
    }
821
822
    /**
823
     * @param  array                                                 $data
824
     * @param  array                                                 $linesToBeCovered
825
     * @param  array                                                 $linesToBeUsed
826
     * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode
827
     * @since Method available since Release 2.0.0
828
     */
829
    private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed)
830
    {
831
        $allowedLines = $this->getAllowedLines(
832
            $linesToBeCovered,
833
            $linesToBeUsed
834
        );
835
836
        $message = '';
837
838
        foreach ($data as $file => $_data) {
839
            foreach ($_data as $line => $flag) {
840
                if ($flag == 1 &&
841
                    (!isset($allowedLines[$file]) ||
842
                        !isset($allowedLines[$file][$line]))) {
843
                    $message .= sprintf(
844
                        '- %s:%d' . PHP_EOL,
845
                        $file,
846
                        $line
847
                    );
848
                }
849
            }
850
        }
851
852
        if (!empty($message)) {
853
            throw new PHP_CodeCoverage_Exception_UnintentionallyCoveredCode(
854
                $message
855
            );
856
        }
857
    }
858
859
    /**
860
     * @param  array $linesToBeCovered
861
     * @param  array $linesToBeUsed
862
     * @return array
863
     * @since Method available since Release 2.0.0
864
     */
865
    private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed)
866
    {
867
        $allowedLines = array();
868
869
        foreach (array_keys($linesToBeCovered) as $file) {
870
            if (!isset($allowedLines[$file])) {
871
                $allowedLines[$file] = array();
872
            }
873
874
            $allowedLines[$file] = array_merge(
875
                $allowedLines[$file],
876
                $linesToBeCovered[$file]
877
            );
878
        }
879
880
        foreach (array_keys($linesToBeUsed) as $file) {
881
            if (!isset($allowedLines[$file])) {
882
                $allowedLines[$file] = array();
883
            }
884
885
            $allowedLines[$file] = array_merge(
886
                $allowedLines[$file],
887
                $linesToBeUsed[$file]
888
            );
889
        }
890
891
        foreach (array_keys($allowedLines) as $file) {
892
            $allowedLines[$file] = array_flip(
893
                array_unique($allowedLines[$file])
894
            );
895
        }
896
897
        return $allowedLines;
898
    }
899
900
    /**
901
     * @return PHP_CodeCoverage_Driver
902
     * @throws PHP_CodeCoverage_Exception
903
     */
904
    private function selectDriver()
905
    {
906
        $runtime = new Runtime;
907
908
        if (!$runtime->canCollectCodeCoverage()) {
909
            throw new PHP_CodeCoverage_Exception('No code coverage driver available');
910
        }
911
912
        if ($runtime->isHHVM()) {
913
            return new PHP_CodeCoverage_Driver_HHVM;
914
        } elseif ($runtime->isPHPDBG()) {
915
            return new PHP_CodeCoverage_Driver_PHPDBG;
916
        } else {
917
            return new PHP_CodeCoverage_Driver_Xdebug;
918
        }
919
    }
920
}
921