Subversion Repositories Applications.framework

Rev

Rev 5 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
5 aurelien 1
<?php
2
/**
3
 * PHP_CodeSniffer tokenises PHP code and detects violations of a
4
 * defined set of coding standards.
5
 *
6
 * PHP version 5
7
 *
8
 * @category  PHP
9
 * @package   PHP_CodeSniffer
10
 * @author    Greg Sherwood <gsherwood@squiz.net>
11
 * @author    Marc McIntyre <mmcintyre@squiz.net>
12
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
13
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
34 aurelien 14
 * @version   CVS: $Id: CodeSniffer.php 34 2009-04-09 07:34:39Z aurelien $
5 aurelien 15
 * @link      http://pear.php.net/package/PHP_CodeSniffer
16
 */
17
 
18
spl_autoload_register(array('PHP_CodeSniffer', 'autoload'));
19
 
20
if (class_exists('PHP_CodeSniffer_Exception', true) === false) {
21
    throw new Exception('Class PHP_CodeSniffer_Exception not found');
22
}
23
 
24
if (class_exists('PHP_CodeSniffer_File', true) === false) {
25
    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_File not found');
26
}
27
 
28
if (class_exists('PHP_CodeSniffer_Tokens', true) === false) {
29
    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Tokens not found');
30
}
31
 
32
if (interface_exists('PHP_CodeSniffer_Sniff', true) === false) {
33
    throw new PHP_CodeSniffer_Exception('Interface PHP_CodeSniffer_Sniff not found');
34
}
35
 
36
if (interface_exists('PHP_CodeSniffer_MultiFileSniff', true) === false) {
37
    throw new PHP_CodeSniffer_Exception('Interface PHP_CodeSniffer_MultiFileSniff not found');
38
}
39
 
40
/**
41
 * PHP_CodeSniffer tokenises PHP code and detects violations of a
42
 * defined set of coding standards.
43
 *
44
 * Standards are specified by classes that implement the PHP_CodeSniffer_Sniff
45
 * interface. A sniff registers what token types it wishes to listen for, then
46
 * PHP_CodeSniffer encounters that token, the sniff is invoked and passed
47
 * information about where the token was found in the stack, and the token stack
48
 * itself.
49
 *
50
 * Sniff files and their containing class must be prefixed with Sniff, and
51
 * have an extension of .php.
52
 *
53
 * Multiple PHP_CodeSniffer operations can be performed by re-calling the
54
 * process function with different parameters.
55
 *
56
 * @category  PHP
57
 * @package   PHP_CodeSniffer
58
 * @author    Greg Sherwood <gsherwood@squiz.net>
59
 * @author    Marc McIntyre <mmcintyre@squiz.net>
60
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
61
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
62
 * @version   Release: @package_version@
63
 * @link      http://pear.php.net/package/PHP_CodeSniffer
64
 */
65
class PHP_CodeSniffer
66
{
67
 
68
    /**
69
     * The file or directory that is currently being processed.
70
     *
71
     * @var string
72
     */
73
    protected $file = array();
74
 
75
    /**
76
     * The directory to search for sniffs in.
77
     *
78
     * @var string
79
     */
80
    protected $standardDir = '';
81
 
82
    /**
83
     * The files that have been processed.
84
     *
85
     * @var array(PHP_CodeSniffer_File)
86
     */
87
    protected $files = array();
88
 
89
    /**
90
     * The listeners array.
91
     *
92
     * @var array(PHP_CodeSniffer_Sniff)
93
     */
94
    protected $listeners = array();
95
 
96
    /**
97
     * The listeners array, indexed by token type.
98
     *
99
     * @var array()
100
     */
101
    private $_tokenListeners = array(
102
                                'file'      => array(),
103
                                'multifile' => array(),
104
                               );
105
 
106
    /**
107
     * An array of patterns to use for skipping files.
108
     *
109
     * @var array()
110
     */
111
    protected $ignorePatterns = array();
112
 
113
    /**
114
     * An array of extensions for files we will check.
115
     *
116
     * @var array
117
     */
118
    public $allowedFileExtensions = array(
119
                                     'php' => 'PHP',
120
                                     'inc' => 'PHP',
121
                                     'js'  => 'JS',
122
                                     'css' => 'CSS',
123
                                    );
124
 
125
    /**
126
     * An array of variable types for param/var we will check.
127
     *
128
     * @var array(string)
129
     */
130
    public static $allowedTypes = array(
131
                                   'array',
132
                                   'boolean',
133
                                   'float',
134
                                   'integer',
135
                                   'mixed',
136
                                   'object',
137
                                   'string',
138
                                  );
139
 
140
 
141
    /**
142
     * Constructs a PHP_CodeSniffer object.
143
     *
144
     * @param int $verbosity The verbosity level.
145
     *                       1: Print progress information.
146
     *                       2: Print developer debug information.
147
     * @param int $tabWidth  The number of spaces each tab represents.
148
     *                       If greater than zero, tabs will be replaced
149
     *                       by spaces before testing each file.
150
     *
151
     * @see process()
152
     */
153
    public function __construct($verbosity=0, $tabWidth=0)
154
    {
155
        define('PHP_CODESNIFFER_VERBOSITY', $verbosity);
156
        define('PHP_CODESNIFFER_TAB_WIDTH', $tabWidth);
157
 
158
        // Change into a directory that we know about to stop any
159
        // relative path conflicts.
160
        chdir(dirname(__FILE__).'/CodeSniffer/');
161
 
162
    }//end __construct()
163
 
164
 
165
    /**
166
     * Autoload static method for loading classes and interfaces.
167
     *
168
     * @param string $className The name of the class or interface.
169
     *
170
     * @return void
171
     */
172
    public static function autoload($className)
173
    {
174
        if (substr($className, 0, 4) === 'PHP_') {
175
            $newClassName = substr($className, 4);
176
        } else {
177
            $newClassName = $className;
178
        }
179
 
180
        $path = str_replace('_', '/', $newClassName).'.php';
181
 
182
        if (is_file(dirname(__FILE__).'/'.$path) === true) {
183
            // Check standard file locations based on class name.
184
            include dirname(__FILE__).'/'.$path;
185
        } else if (is_file(dirname(__FILE__).'/CodeSniffer/Standards/'.$path) === true) {
186
            // Check for included sniffs.
187
            include dirname(__FILE__).'/CodeSniffer/Standards/'.$path;
188
        } else {
189
            // Everything else.
190
            @include $path;
191
        }
192
 
193
    }//end autoload()
194
 
195
 
196
    /**
197
     * Sets an array of file extensions that we will allow checking of.
198
     *
199
     * If the extension is one of the defaults, a specific tokenizer
200
     * will be used. Otherwise, the PHP tokenizer will be used for
201
     * all extensions passed.
202
     *
203
     * @param array $extensions An array of file extensions.
204
     *
205
     * @return void
206
     */
207
    public function setAllowedFileExtensions(array $extensions)
208
    {
209
        $newExtensions = array();
210
        foreach ($extensions as $ext) {
211
            if (isset($this->allowedFileExtensions[$ext]) === true) {
212
                $newExtensions[$ext] = $this->allowedFileExtensions[$ext];
213
            } else {
214
                $newExtensions[$ext] = 'PHP';
215
            }
216
        }
217
 
218
        $this->allowedFileExtensions = $newExtensions;
219
 
220
    }//end setAllowedFileExtensions()
221
 
222
 
223
    /**
224
     * Sets an array of ignore patterns that we use to skip files and folders.
225
     *
226
     * Patterns are not case sensitive.
227
     *
228
     * @param array $patterns An array of ignore patterns.
229
     *
230
     * @return void
231
     */
232
    public function setIgnorePatterns(array $patterns)
233
    {
234
        $this->ignorePatterns = $patterns;
235
 
236
    }//end setIgnorePatterns()
237
 
238
 
239
    /**
240
     * Adds a file to the list of checked files.
241
     *
242
     * Checked files are used to generate error reports after the run.
243
     *
244
     * @param PHP_CodeSniffer_File $phpcsFile The file to add.
245
     *
246
     * @return void
247
     */
248
    public function addFile(PHP_CodeSniffer_File $phpcsFile)
249
    {
250
        $this->files[] = $phpcsFile;
251
 
252
    }//end addFile()
253
 
254
 
255
    /**
256
     * Processes the files/directories that PHP_CodeSniffer was constructed with.
257
     *
258
     * @param string|array $files    The files and directories to process. For
259
     *                               directories, each sub directory will also
260
     *                               be traversed for source files.
261
     * @param string       $standard The set of code sniffs we are testing
262
     *                               against.
263
     * @param array        $sniffs   The sniff names to restrict the allowed
264
     *                               listeners to.
265
     * @param boolean      $local    If true, don't recurse into directories.
266
     *
267
     * @return void
268
     * @throws PHP_CodeSniffer_Exception If files or standard are invalid.
269
     */
270
    public function process($files, $standard, array $sniffs=array(), $local=false)
271
    {
272
        if (is_array($files) === false) {
273
            if (is_string($files) === false || $files === null) {
274
                throw new PHP_CodeSniffer_Exception('$file must be a string');
275
            }
276
 
277
            $files = array($files);
278
        }
279
 
280
        if (is_string($standard) === false || $standard === null) {
281
            throw new PHP_CodeSniffer_Exception('$standard must be a string');
282
        }
283
 
284
        // Reset the members.
285
        $this->listeners       = array();
286
        $this->files           = array();
287
        $this->_tokenListeners = array(
288
                                  'file'      => array(),
289
                                  'multifile' => array(),
290
                                 );
291
 
292
        if (PHP_CODESNIFFER_VERBOSITY > 0) {
293
            echo "Registering sniffs in $standard standard... ";
294
            if (PHP_CODESNIFFER_VERBOSITY > 2) {
295
                echo PHP_EOL;
296
            }
297
        }
298
 
299
        $this->setTokenListeners($standard, $sniffs);
300
        if (PHP_CODESNIFFER_VERBOSITY > 0) {
301
            $numSniffs = count($this->listeners);
302
            echo "DONE ($numSniffs sniffs registered)".PHP_EOL;
303
        }
304
 
305
        // Construct a list of listeners indexed by token being listened for.
306
        foreach ($this->listeners as $listenerClass) {
307
            $listener = new $listenerClass();
308
 
309
            if (($listener instanceof PHP_CodeSniffer_Sniff) === true) {
310
                $tokens = $listener->register();
311
                if (is_array($tokens) === false) {
312
                    $msg = "Sniff $listenerClass register() method must return an array";
313
                    throw new PHP_CodeSniffer_Exception($msg);
314
                }
315
 
316
                foreach ($tokens as $token) {
317
                    if (isset($this->_tokenListeners['file'][$token]) === false) {
318
                        $this->_tokenListeners['file'][$token] = array();
319
                    }
320
 
321
                    if (in_array($listener, $this->_tokenListeners['file'][$token], true) === false) {
322
                        $this->_tokenListeners['file'][$token][] = $listener;
323
                    }
324
                }
325
            } else if (($listener instanceof PHP_CodeSniffer_MultiFileSniff) === true) {
326
                $this->_tokenListeners['multifile'][] = $listener;
327
            }
328
        }//end foreach
329
 
330
        foreach ($files as $file) {
331
            $this->file = $file;
332
            if (is_dir($this->file) === true) {
333
                $this->processFiles($this->file, $local);
334
            } else {
335
                $this->processFile($this->file);
336
            }
337
        }
338
 
339
        // Now process the multi-file sniffs, assuming there are
340
        // multiple files being sniffed.
341
        if (count($files) > 1) {
342
            foreach ($this->_tokenListeners['multifile'] as $listener) {
343
                // Set the name of the listener for error messages.
344
                $activeListener = get_class($listener);
345
                foreach ($this->files as $file) {
346
                    $file->setActiveListener($activeListener);
347
                }
348
 
349
                $listener->process($this->files);
350
            }
351
        }
352
 
353
    }//end process()
354
 
355
 
356
    /**
357
     * Gets installed sniffs in the coding standard being used.
358
     *
359
     * Traverses the standard directory for classes that implement the
360
     * PHP_CodeSniffer_Sniff interface asks them to register. Each of the
361
     * sniff's class names must be exact as the basename of the sniff file.
362
     *
363
     * Returns an array of sniff class names.
364
     *
365
     * @param string $standard The name of the coding standard we are checking.
366
     * @param array  $sniffs   The sniff names to restrict the allowed
367
     *                         listeners to.
368
     *
369
     * @return array
370
     * @throws PHP_CodeSniffer_Exception If any of the tests failed in the
371
     *                                   registration process.
372
     */
373
    public function getTokenListeners($standard, array $sniffs=array())
374
    {
375
        if (is_dir($standard) === true) {
376
            // This is a custom standard.
377
            $this->standardDir = $standard;
378
            $standard          = basename($standard);
379
        } else {
380
            $this->standardDir = realpath(dirname(__FILE__).'/CodeSniffer/Standards/'.$standard);
381
        }
382
 
383
        $files = self::getSniffFiles($this->standardDir, $standard);
384
 
385
        if (empty($sniffs) === false) {
386
            // Convert the allowed sniffs to lower case so
387
            // they are easier to check.
388
            foreach ($sniffs as &$sniff) {
389
                $sniff = strtolower($sniff);
390
            }
391
        }
392
 
393
        $listeners = array();
394
 
395
        foreach ($files as $file) {
396
            // Work out where the position of /StandardName/Sniffs/... is
397
            // so we can determine what the class will be called.
398
            $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR);
399
            if ($sniffPos === false) {
400
                continue;
401
            }
402
 
403
            $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR);
404
            if ($slashPos === false) {
405
                continue;
406
            }
407
 
408
            $className = substr($file, ($slashPos + 1));
409
            $className = substr($className, 0, -4);
410
            $className = str_replace(DIRECTORY_SEPARATOR, '_', $className);
411
 
412
            include_once $file;
413
 
414
            // If they have specified a list of sniffs to restrict to, check
415
            // to see if this sniff is allowed.
416
            $allowed = in_array(strtolower($className), $sniffs);
417
            if (empty($sniffs) === false && $allowed === false) {
418
                continue;
419
            }
420
 
421
            $listeners[] = $className;
422
 
423
            if (PHP_CODESNIFFER_VERBOSITY > 2) {
424
                echo "\tRegistered $className".PHP_EOL;
425
            }
426
        }//end foreach
427
 
428
        return $listeners;
429
 
430
    }//end getTokenListeners()
431
 
432
 
433
    /**
434
     * Sets installed sniffs in the coding standard being used.
435
     *
436
     * @param string $standard The name of the coding standard we are checking.
437
     * @param array  $sniffs   The sniff names to restrict the allowed
438
     *                         listeners to.
439
     *
440
     * @return null
441
     */
442
    public function setTokenListeners($standard, array $sniffs=array())
443
    {
444
        $this->listeners = $this->getTokenListeners($standard, $sniffs);
445
 
446
    }//end setTokenListeners()
447
 
448
 
449
    /**
450
     * Return a list of sniffs that a coding standard has defined.
451
     *
452
     * Sniffs are found by recursing the standard directory and also by
453
     * asking the standard for included sniffs.
454
     *
455
     * @param string $dir      The directory where to look for the files.
456
     * @param string $standard The name of the coding standard. If NULL, no
457
     *                         included sniffs will be checked for.
458
     *
459
     * @return array
460
     * @throws PHP_CodeSniffer_Exception If an included or excluded sniff does
461
     *                                   not exist.
462
     */
463
    public static function getSniffFiles($dir, $standard=null)
464
    {
465
        $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
466
 
467
        $ownSniffs      = array();
468
        $includedSniffs = array();
469
        $excludedSniffs = array();
470
 
471
        foreach ($di as $file) {
472
            // Skip hidden files.
473
            if (substr($file->getFilename(), 0, 1) === '.') {
474
                continue;
475
            }
476
 
477
            // We are only interested in PHP and sniff files.
478
            $fileParts = explode('.', $file);
479
            if (array_pop($fileParts) !== 'php') {
480
                continue;
481
            }
482
 
483
            $basename = basename($file, '.php');
484
            if (substr($basename, -5) !== 'Sniff') {
485
                continue;
486
            }
487
 
488
            $ownSniffs[] = $file->getPathname();
489
        }//end foreach
490
 
491
        // Load the standard class and ask it for a list of external
492
        // sniffs to include in the standard.
493
        if ($standard !== null
494
            && is_file("$dir/{$standard}CodingStandard.php") === true
495
        ) {
496
            include_once "$dir/{$standard}CodingStandard.php";
497
            $standardClassName = "PHP_CodeSniffer_Standards_{$standard}_{$standard}CodingStandard";
498
            $standardClass     = new $standardClassName;
499
 
500
            $included = $standardClass->getIncludedSniffs();
501
            foreach ($included as $sniff) {
502
                if (is_dir($sniff) === true) {
503
                    // Trying to include from a custom standard.
504
                    $sniffDir = $sniff;
505
                    $sniff    = basename($sniff);
506
                } else if (is_file($sniff) === true) {
507
                    // Trying to include a custom sniff.
508
                    $sniffDir = $sniff;
509
                } else {
510
                    $sniffDir = realpath(dirname(__FILE__)."/CodeSniffer/Standards/$sniff");
511
                    if ($sniffDir === false) {
512
                        $error = "Included sniff $sniff does not exist";
513
                        throw new PHP_CodeSniffer_Exception($error);
514
                    }
515
                }
516
 
517
                if (is_dir($sniffDir) === true) {
518
                    if (self::isInstalledStandard($sniff) === true) {
519
                        // We are including a whole coding standard.
520
                        $includedSniffs = array_merge($includedSniffs, self::getSniffFiles($sniffDir, $sniff));
521
                    } else {
522
                        // We are including a whole directory of sniffs.
523
                        $includedSniffs = array_merge($includedSniffs, self::getSniffFiles($sniffDir));
524
                    }
525
                } else {
526
                    if (substr($sniffDir, -9) !== 'Sniff.php') {
527
                        $error = "Included sniff $sniff does not exist";
528
                        throw new PHP_CodeSniffer_Exception($error);
529
                    }
530
 
531
                    $includedSniffs[] = $sniffDir;
532
                }
533
            }//end foreach
534
 
535
            $excluded = $standardClass->getExcludedSniffs();
536
            foreach ($excluded as $sniff) {
537
                if (is_dir($sniff) === true) {
538
                    // Trying to exclude from a custom standard.
539
                    $sniffDir = $sniff;
540
                    $sniff    = basename($sniff);
541
                } else if (is_file($sniff) === true) {
542
                    // Trying to exclude a custom sniff.
543
                    $sniffDir = $sniff;
544
                } else {
545
                    $sniffDir = realpath(dirname(__FILE__)."/CodeSniffer/Standards/$sniff");
546
                    if ($sniffDir === false) {
547
                        $error = "Excluded sniff $sniff does not exist";
548
                        throw new PHP_CodeSniffer_Exception($error);
549
                    }
550
                }
551
 
552
                if (is_dir($sniffDir) === true) {
553
                    if (self::isInstalledStandard($sniff) === true) {
554
                        // We are excluding a whole coding standard.
555
                        $excludedSniffs = array_merge(
556
                            $excludedSniffs,
557
                            self::getSniffFiles($sniffDir, $sniff)
558
                        );
559
                    } else {
560
                        // We are excluding a whole directory of sniffs.
561
                        $excludedSniffs = array_merge(
562
                            $excludedSniffs,
563
                            self::getSniffFiles($sniffDir)
564
                        );
565
                    }
566
                } else {
567
                    if (substr($sniffDir, -9) !== 'Sniff.php') {
568
                        $error = "Excluded sniff $sniff does not exist";
569
                        throw new PHP_CodeSniffer_Exception($error);
570
                    }
571
 
572
                    $excludedSniffs[] = $sniffDir;
573
                }
574
            }//end foreach
575
        }//end if
576
 
577
        // Merge our own sniff list with our exnternally included
578
        // sniff list, but filter out any excluded sniffs.
579
        $files = array();
580
        foreach (array_merge($ownSniffs, $includedSniffs) as $sniff) {
581
            if (in_array($sniff, $excludedSniffs) === true) {
582
                continue;
583
            } else {
584
                $files[] = $sniff;
585
            }
586
        }
587
 
588
        return $files;
589
 
590
    }//end getSniffFiles()
591
 
592
 
593
    /**
594
     * Run the code sniffs over each file in a given directory.
595
     *
596
     * Recusively reads the specified directory and performs the PHP_CodeSniffer
597
     * sniffs on each source file found within the directories.
598
     *
599
     * @param string  $dir   The directory to process.
600
     * @param boolean $local If true, only process files in this directory, not
601
     *                       sub directories.
602
     *
603
     * @return void
604
     * @throws Exception If there was an error opening the directory.
605
     */
606
    public function processFiles($dir, $local=false)
607
    {
608
        try {
609
            if ($local === true) {
610
                $di = new DirectoryIterator($dir);
611
            } else {
612
                $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
613
            }
614
 
615
            foreach ($di as $file) {
616
                $filePath = realpath($file->getPathname());
617
 
618
                if (is_dir($filePath) === true) {
619
                    continue;
620
                }
621
 
622
                // Check that the file's extension is one we are checking.
623
                // Note that because we are doing a whole directory, we
624
                // are strick about checking the extension and we don't
625
                // let files with no extension through.
626
                $fileParts = explode('.', $file);
627
                $extension = array_pop($fileParts);
628
                if ($extension === $file) {
629
                    continue;
630
                }
631
 
632
                if (isset($this->allowedFileExtensions[$extension]) === false) {
633
                    continue;
634
                }
635
 
636
                $this->processFile($filePath);
637
            }//end foreach
638
        } catch (Exception $e) {
639
            $trace = $e->getTrace();
640
 
641
            $filename = $trace[0]['args'][0];
642
            if (is_numeric($filename) === true) {
643
                // See if we can find the PHP_CodeSniffer_File object.
644
                foreach ($trace as $data) {
645
                    if (isset($data['args'][0]) === true && ($data['args'][0] instanceof PHP_CodeSniffer_File) === true) {
646
                        $filename = $data['args'][0]->getFilename();
647
                    }
648
                }
649
            }
650
 
651
            $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage();
652
 
653
            $phpcsFile = new PHP_CodeSniffer_File($filename, $this->listeners, $this->allowedFileExtensions);
654
            $this->addFile($phpcsFile);
655
            $phpcsFile->addError($error, null);
656
            return;
657
        }
658
 
659
    }//end processFiles()
660
 
661
 
662
    /**
663
     * Run the code sniffs over a single given file.
664
     *
665
     * Processes the file and runs the PHP_CodeSniffer sniffs to verify that it
666
     * conforms with the standard.
667
     *
668
     * @param string $file     The file to process.
669
     * @param string $contents The contents to parse. If NULL, the content
670
     *                         is taken from the file system.
671
     *
672
     * @return void
673
     * @throws PHP_CodeSniffer_Exception If the file could not be processed.
674
     */
675
    public function processFile($file, $contents=null)
676
    {
677
        if ($contents === null && file_exists($file) === false) {
678
            throw new PHP_CodeSniffer_Exception("Source file $file does not exist");
679
        }
680
 
681
        // If the file's path matches one of our ignore patterns, skip it.
682
        foreach ($this->ignorePatterns as $pattern) {
683
            $replacements = array(
684
                             '\\,' => ',',
685
                             '*'   => '.*',
686
                            );
687
 
688
            $pattern = strtr($pattern, $replacements);
689
            if (preg_match("|{$pattern}|i", $file) === 1) {
690
                return;
691
            }
692
        }
693
 
694
        if (PHP_CODESNIFFER_VERBOSITY > 0) {
695
            $startTime = time();
696
            echo 'Processing '.basename($file).' ';
697
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
698
                echo PHP_EOL;
699
            }
700
        }
701
 
702
        $phpcsFile = new PHP_CodeSniffer_File(
703
            $file,
704
            $this->_tokenListeners['file'],
705
            $this->allowedFileExtensions
706
        );
707
        $this->addFile($phpcsFile);
708
        $phpcsFile->start($contents);
709
 
710
        // Clean up the test if we can to save memory. This can't be done if
711
        // we need to leave the files around for multi-file sniffs.
712
        if (empty($this->_tokenListeners['multifile']) === true) {
713
            $phpcsFile->cleanUp();
714
        }
715
 
716
        if (PHP_CODESNIFFER_VERBOSITY > 0) {
717
            $timeTaken = (time() - $startTime);
718
            if ($timeTaken === 0) {
719
                echo 'DONE in < 1 second';
720
            } else if ($timeTaken === 1) {
721
                echo 'DONE in 1 second';
722
            } else {
723
                echo "DONE in $timeTaken seconds";
724
            }
725
 
726
            $errors   = $phpcsFile->getErrorCount();
727
            $warnings = $phpcsFile->getWarningCount();
728
            echo " ($errors errors, $warnings warnings)".PHP_EOL;
729
        }
730
 
731
    }//end processFile()
732
 
733
 
734
    /**
735
     * Pre-process and package errors and warnings for all files.
736
     *
737
     * Used by error reports to get a packaged list of all errors and
738
     * warnings in each file.
739
     *
740
     * @param boolean $showWarnings Show warnings as well as errors.
741
     *
742
     * @return array
743
     */
744
    public function prepareErrorReport($showWarnings=true)
745
    {
746
        $report = array(
747
                   'totals' => array(
748
                                'warnings' => 0,
749
                                'errors'   => 0,
750
                               ),
751
                   'files'  => array(),
752
                  );
753
 
754
        foreach ($this->files as $file) {
755
            $warnings    = $file->getWarnings();
756
            $errors      = $file->getErrors();
757
            $numWarnings = $file->getWarningCount();
758
            $numErrors   = $file->getErrorCount();
759
            $filename    = $file->getFilename();
760
 
761
            $report['files'][$filename] = array(
762
                                           'errors'   => 0,
763
                                           'warnings' => 0,
764
                                           'messages' => array(),
765
                                          );
766
 
767
            if ($numErrors === 0 && $numWarnings === 0) {
768
                // Prefect score!
769
                continue;
770
            }
771
 
772
            if ($numErrors === 0 && $showWarnings === false) {
773
                // Prefect score (sort of).
774
                continue;
775
            }
776
 
777
            $report['files'][$filename]['errors'] = $numErrors;
778
            if ($showWarnings === true) {
779
                $report['files'][$filename]['warnings'] = $numWarnings;
780
            } else {
781
                $report['files'][$filename]['warnings'] = 0;
782
            }
783
 
784
            $report['totals']['errors'] += $numErrors;
785
            if ($showWarnings === true) {
786
                $report['totals']['warnings'] += $numWarnings;
787
            }
788
 
789
            // Merge errors and warnings.
790
            foreach ($errors as $line => $lineErrors) {
791
                foreach ($lineErrors as $column => $colErrors) {
792
                    $newErrors = array();
793
                    foreach ($colErrors as $data) {
794
                        $newErrors[] = array(
795
                                        'message' => $data['message'],
796
                                        'source'  => $data['source'],
797
                                        'type'    => 'ERROR',
798
                                       );
799
                    }
800
 
801
                    $errors[$line][$column] = $newErrors;
802
                }
803
            }//end foreach
804
 
805
            if ($showWarnings === true) {
806
                foreach ($warnings as $line => $lineWarnings) {
807
                    foreach ($lineWarnings as $column => $colWarnings) {
808
                        $newWarnings = array();
809
                        foreach ($colWarnings as $data) {
810
                            $newWarnings[] = array(
811
                                              'message' => $data['message'],
812
                                              'source'  => $data['source'],
813
                                              'type'    => 'WARNING',
814
                                             );
815
                        }
816
 
817
                        if (isset($errors[$line]) === false) {
818
                            $errors[$line] = array();
819
                        }
820
 
821
                        if (isset($errors[$line][$column]) === true) {
822
                            $errors[$line][$column] = array_merge(
823
                                $newWarnings,
824
                                $errors[$line][$column]
825
                            );
826
                        } else {
827
                            $errors[$line][$column] = $newWarnings;
828
                        }
829
                    }
830
                }//end foreach
831
            }//end if
832
 
833
            ksort($errors);
834
 
835
            $report['files'][$filename]['messages'] = $errors;
836
        }//end foreach
837
 
838
        return $report;
839
 
840
    }//end prepareErrorReport()
841
 
842
 
843
    /**
844
     * Prints all errors and warnings for each file processed, in an XML format.
845
     *
846
     * Errors and warnings are displayed together, grouped by file.
847
     *
848
     * @param boolean $showWarnings Show warnings as well as errors.
849
     *
850
     * @return int The number of error and warning messages shown.
851
     */
852
    public function printXMLErrorReport($showWarnings=true)
853
    {
854
        echo '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
855
        echo '<phpcs version="@package_version@">'.PHP_EOL;
856
 
857
        $errorsShown = 0;
858
 
859
        $report = $this->prepareErrorReport($showWarnings);
860
        foreach ($report['files'] as $filename => $file) {
861
            if (empty($file['messages']) === true) {
862
                continue;
863
            }
864
 
865
            echo ' <file name="'.$filename.'" errors="'.$file['errors'].'" warnings="'.$file['warnings'].'">'.PHP_EOL;
866
 
867
            foreach ($file['messages'] as $line => $lineErrors) {
868
                foreach ($lineErrors as $column => $colErrors) {
869
                    foreach ($colErrors as $error) {
870
                        $error['type'] = strtolower($error['type']);
871
                        echo '  <'.$error['type'].' line="'.$line.'" column="'.$column.'" source="'.$error['source'].'">';
872
                        echo htmlspecialchars($error['message']).'</'.$error['type'].'>'.PHP_EOL;
873
                        $errorsShown++;
874
                    }
875
                }
876
            }//end foreach
877
 
878
            echo ' </file>'.PHP_EOL;
879
        }//end foreach
880
 
881
        echo '</phpcs>'.PHP_EOL;
882
 
883
        return $errorsShown;
884
 
885
    }//end printXMLErrorReport()
886
 
887
 
888
    /**
889
     * Prints all errors and warnings for processed files, in a Checkstyle format.
890
     *
891
     * Errors and warnings are displayed together, grouped by file.
892
     *
893
     * @param boolean $showWarnings Show warnings as well as errors.
894
     *
895
     * @return int The number of error and warning messages shown.
896
     */
897
    public function printCheckstyleErrorReport($showWarnings=true)
898
    {
899
        echo '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
900
        echo '<checkstyle version="@package_version@">'.PHP_EOL;
901
 
902
        $errorsShown = 0;
903
 
904
        $report = $this->prepareErrorReport($showWarnings);
905
        foreach ($report['files'] as $filename => $file) {
906
            echo ' <file name="'.$filename.'">'.PHP_EOL;
907
 
908
            foreach ($file['messages'] as $line => $lineErrors) {
909
                foreach ($lineErrors as $column => $colErrors) {
910
                    foreach ($colErrors as $error) {
911
                        $error['type'] = strtolower($error['type']);
912
                        echo '  <error';
913
                        echo ' line="'.$line.'" column="'.$column.'"';
914
                        echo ' severity="'.$error['type'].'"';
915
                        $message = utf8_encode(htmlspecialchars($error['message']));
916
                        echo ' message="'.$message.'"';
917
                        echo ' source="'.$error['source'].'"';
918
                        echo '/>'.PHP_EOL;
919
                        $errorsShown++;
920
                    }
921
                }
922
            }//end foreach
923
 
924
            echo ' </file>'.PHP_EOL;
925
        }//end foreach
926
 
927
        echo '</checkstyle>'.PHP_EOL;
928
 
929
        return $errorsShown;
930
 
931
    }//end printCheckstyleErrorReport()
932
 
933
 
934
    /**
935
     * Prints all errors and warnings for each file processed, in a CSV format.
936
     *
937
     * @param boolean $showWarnings Show warnings as well as errors.
938
     *
939
     * @return int The number of error and warning messages shown.
940
     */
941
    public function printCSVErrorReport($showWarnings=true)
942
    {
943
        echo 'File,Line,Column,Severity,Message,Source'.PHP_EOL;
944
 
945
        $errorsShown = 0;
946
 
947
        $report = $this->prepareErrorReport($showWarnings);
948
        foreach ($report['files'] as $filename => $file) {
949
            foreach ($file['messages'] as $line => $lineErrors) {
950
                foreach ($lineErrors as $column => $colErrors) {
951
                    foreach ($colErrors as $error) {
952
                        $filename = str_replace('"', '\"', $filename);
953
                        $message  = str_replace('"', '\"', $error['message']);
954
                        $type     = strtolower($error['type']);
955
                        $source   = $error['source'];
956
                        echo "\"$filename\",$line,$column,$type,\"$message\",$source".PHP_EOL;
957
                        $errorsShown++;
958
                    }
959
                }
960
            }//end foreach
961
        }//end foreach
962
 
963
        return $errorsShown;
964
 
965
    }//end printCSVErrorReport()
966
 
967
 
968
    /**
969
     * Prints all errors and warnings for each file processed, in a format for emacs.
970
     *
971
     * @param boolean $showWarnings Show warnings as well as errors.
972
     *
973
     * @return int The number of error and warning messages shown.
974
     */
975
    public function printEmacsErrorReport($showWarnings=true)
976
    {
977
        $errorsShown = 0;
978
 
979
        $report = $this->prepareErrorReport($showWarnings);
980
        foreach ($report['files'] as $filename => $file) {
981
            foreach ($file['messages'] as $line => $lineErrors) {
982
                foreach ($lineErrors as $column => $colErrors) {
983
                    foreach ($colErrors as $error) {
984
                        $message = $error['message'];
985
                        $type    = strtolower($error['type']);
986
                        echo "$filename:$line:$column: $type - $message".PHP_EOL;
987
                        $errorsShown++;
988
                    }
989
                }
990
            }//end foreach
991
        }//end foreach
992
 
993
        return $errorsShown;
994
 
995
    }//end printEmacsErrorReport()
996
 
997
 
998
    /**
999
     * Prints all errors and warnings for each file processed.
1000
     *
1001
     * Errors and warnings are displayed together, grouped by file.
1002
     *
1003
     * @param boolean $showWarnings Show warnings as well as errors.
1004
     * @param boolean $showSources  Show error sources in report.
1005
     *
1006
     * @return int The number of error and warning messages shown.
1007
     */
1008
    public function printErrorReport($showWarnings=true, $showSources=false)
1009
    {
1010
        $errorsShown = 0;
1011
 
1012
        $report = $this->prepareErrorReport($showWarnings);
1013
        foreach ($report['files'] as $filename => $file) {
1014
            if (empty($file['messages']) === true) {
1015
                continue;
1016
            }
1017
 
1018
            echo PHP_EOL.'FILE: ';
1019
            if (strlen($filename) <= 71) {
1020
                echo $filename;
1021
            } else {
1022
                echo '...'.substr($filename, (strlen($filename) - 71));
1023
            }
1024
 
1025
            echo PHP_EOL;
1026
            echo str_repeat('-', 80).PHP_EOL;
1027
 
1028
            echo 'FOUND '.$file['errors'].' ERROR(S) ';
1029
 
1030
            if ($showWarnings === true) {
1031
                echo 'AND '.$file['warnings'].' WARNING(S) ';
1032
            }
1033
 
1034
            echo 'AFFECTING '.count($file['messages']).' LINE(S)'.PHP_EOL;
1035
            echo str_repeat('-', 80).PHP_EOL;
1036
 
1037
            // Work out the max line number for formatting.
1038
            $maxLine = 0;
1039
            foreach ($file['messages'] as $line => $lineErrors) {
1040
                if ($line > $maxLine) {
1041
                    $maxLine = $line;
1042
                }
1043
            }
1044
 
1045
            $maxLineLength = strlen($maxLine);
1046
 
1047
            // The length of the word ERROR or WARNING; used for padding.
1048
            if ($showWarnings === true && $file['warnings'] > 0) {
1049
                $typeLength = 7;
1050
            } else {
1051
                $typeLength = 5;
1052
            }
1053
 
1054
            // The padding that all lines will require that are
1055
            // printing an error message overflow.
1056
            $paddingLine2  = str_repeat(' ', ($maxLineLength + 1));
1057
            $paddingLine2 .= ' | ';
1058
            $paddingLine2 .= str_repeat(' ', $typeLength);
1059
            $paddingLine2 .= ' | ';
1060
 
1061
            // The maxium amount of space an error message can use.
1062
            $maxErrorSpace = (79 - strlen($paddingLine2));
1063
 
1064
            foreach ($file['messages'] as $line => $lineErrors) {
1065
                foreach ($lineErrors as $column => $colErrors) {
1066
                    foreach ($colErrors as $error) {
1067
                        $message = $error['message'];
1068
                        if ($showSources === true) {
1069
                            $message .= ' ('.substr($error['source'], 0, -5).')';
1070
                        }
1071
 
1072
                        // The padding that goes on the front of the line.
1073
                        $padding  = ($maxLineLength - strlen($line));
1074
                        $errorMsg = wordwrap(
1075
                            $message,
1076
                            $maxErrorSpace,
1077
                            PHP_EOL."$paddingLine2"
1078
                        );
1079
 
1080
                        echo ' '.str_repeat(' ', $padding).$line.' | '.$error['type'];
1081
                        if ($error['type'] === 'ERROR') {
1082
                            if ($showWarnings === true && $file['warnings'] > 0) {
1083
                                echo '  ';
1084
                            }
1085
                        }
1086
 
1087
                        echo ' | '.$errorMsg.PHP_EOL;
1088
                        $errorsShown++;
1089
                    }//end foreach
1090
                }//end foreach
1091
            }//end foreach
1092
 
1093
            echo str_repeat('-', 80).PHP_EOL.PHP_EOL;
1094
        }//end foreach
1095
 
1096
        return $errorsShown;
1097
 
1098
    }//end printErrorReport()
1099
 
1100
 
1101
    /**
1102
     * Prints a summary of errors and warnings for each file processed.
1103
     *
1104
     * If verbose output is enabled, results are shown for all files, even if
1105
     * they have no errors or warnings. If verbose output is disabled, we only
1106
     * show files that have at least one warning or error.
1107
     *
1108
     * @param boolean $showWarnings Show warnings as well as errors.
1109
     * @param boolean $showSources  Show error sources in report.
1110
     *
1111
     * @return int The number of error and warning messages shown.
1112
     */
1113
    public function printErrorReportSummary($showWarnings=true, $showSources=false)
1114
    {
1115
        $errorFiles = array();
1116
 
1117
        foreach ($this->files as $file) {
1118
            $numWarnings = $file->getWarningCount();
1119
            $numErrors   = $file->getErrorCount();
1120
            $filename    = $file->getFilename();
1121
 
1122
            // If verbose output is enabled, we show the results for all files,
1123
            // but if not, we only show files that had errors or warnings.
1124
            if (PHP_CODESNIFFER_VERBOSITY > 0
1125
                || $numErrors > 0
1126
                || ($numWarnings > 0
1127
                && $showWarnings === true)
1128
            ) {
1129
                $errorFiles[$filename] = array(
1130
                                          'warnings' => $numWarnings,
1131
                                          'errors'   => $numErrors,
1132
                                         );
1133
            }//end if
1134
        }//end foreach
1135
 
1136
        if (empty($errorFiles) === true) {
1137
            // Nothing to print.
1138
            return 0;
1139
        }
1140
 
1141
        echo PHP_EOL.'PHP CODE SNIFFER REPORT SUMMARY'.PHP_EOL;
1142
        echo str_repeat('-', 80).PHP_EOL;
1143
        if ($showWarnings === true) {
1144
            echo 'FILE'.str_repeat(' ', 60).'ERRORS  WARNINGS'.PHP_EOL;
1145
        } else {
1146
            echo 'FILE'.str_repeat(' ', 70).'ERRORS'.PHP_EOL;
1147
        }
1148
 
1149
        echo str_repeat('-', 80).PHP_EOL;
1150
 
1151
        $totalErrors   = 0;
1152
        $totalWarnings = 0;
1153
        $totalFiles    = 0;
1154
 
1155
        foreach ($errorFiles as $file => $errors) {
1156
            if ($showWarnings === true) {
1157
                $padding = (62 - strlen($file));
1158
            } else {
1159
                $padding = (72 - strlen($file));
1160
            }
1161
 
1162
            if ($padding < 0) {
1163
                $file    = '...'.substr($file, (($padding * -1) + 3));
1164
                $padding = 0;
1165
            }
1166
 
1167
            echo $file.str_repeat(' ', $padding).'  ';
1168
            echo $errors['errors'];
1169
            if ($showWarnings === true) {
1170
                echo str_repeat(' ', (8 - strlen((string) $errors['errors'])));
1171
                echo $errors['warnings'];
1172
            }
1173
 
1174
            echo PHP_EOL;
1175
 
1176
            $totalErrors   += $errors['errors'];
1177
            $totalWarnings += $errors['warnings'];
1178
            $totalFiles++;
1179
        }//end foreach
1180
 
1181
        echo str_repeat('-', 80).PHP_EOL;
1182
        echo "A TOTAL OF $totalErrors ERROR(S) ";
1183
        if ($showWarnings === true) {
1184
            echo "AND $totalWarnings WARNING(S) ";
1185
        }
1186
 
1187
        echo "WERE FOUND IN $totalFiles FILE(S)".PHP_EOL;
1188
        echo str_repeat('-', 80).PHP_EOL.PHP_EOL;
1189
 
1190
        if ($showSources === true) {
1191
            $this->printSourceReport($showWarnings, true);
1192
        }
1193
 
1194
        return ($totalErrors + $totalWarnings);
1195
 
1196
    }//end printErrorReportSummary()
1197
 
1198
 
1199
    /**
1200
     * Prints the source of all errors and warnings.
1201
     *
1202
     * @param boolean $showWarnings Show warnings as well as errors.
1203
     * @param boolean $showSources  Show error sources in report.
1204
     *
1205
     * @return int The number of error and warning messages shown.
1206
     */
1207
    public function printSourceReport($showWarnings=true, $showSources=false)
1208
    {
1209
        $sources = array();
1210
 
1211
        $errorsShown = 0;
1212
 
1213
        $report = $this->prepareErrorReport($showWarnings);
1214
        foreach ($report['files'] as $filename => $file) {
1215
            foreach ($file['messages'] as $line => $lineErrors) {
1216
                foreach ($lineErrors as $column => $colErrors) {
1217
                    foreach ($colErrors as $error) {
1218
                        $errorsShown++;
1219
 
1220
                        $source = $error['source'];
1221
                        if (isset($sources[$source]) === false) {
1222
                            $sources[$source] = 1;
1223
                        } else {
1224
                            $sources[$source]++;
1225
                        }
1226
                    }
1227
                }
1228
            }//end foreach
1229
        }//end foreach
1230
 
1231
        if ($errorsShown === 0) {
1232
            // Nothing to show.
1233
            return 0;
1234
        }
1235
 
1236
        asort($sources);
1237
        $sources = array_reverse($sources);
1238
 
1239
        echo PHP_EOL.'PHP CODE SNIFFER VIOLATION SOURCE SUMMARY'.PHP_EOL;
1240
        echo str_repeat('-', 80).PHP_EOL;
1241
        if ($showSources === true) {
1242
            echo 'SOURCE'.str_repeat(' ', 69).'COUNT'.PHP_EOL;
1243
            echo str_repeat('-', 80).PHP_EOL;
1244
        } else {
1245
            echo 'STANDARD    CATEGORY            SNIFF'.str_repeat(' ', 38).'COUNT'.PHP_EOL;
1246
            echo str_repeat('-', 80).PHP_EOL;
1247
        }
1248
 
1249
        foreach ($sources as $source => $count) {
1250
            if ($showSources === true) {
1251
                $source = substr($source, 0, -5);
1252
                echo $source.str_repeat(' ', (75 - strlen($source)));
1253
            } else {
1254
                $parts = explode('.', $source);
1255
 
1256
                if (strlen($parts[0]) > 10) {
1257
                    $parts[0] = substr($parts[0], 0, ((strlen($parts[0]) -10) * -1));
1258
                }
1259
                echo $parts[0].str_repeat(' ', (12 - strlen($parts[0])));
1260
 
1261
                $category = $this->makeFriendlyName($parts[1]);
1262
                if (strlen($category) > 18) {
1263
                    $category = substr($category, 0, ((strlen($category) -18) * -1));
1264
                }
1265
                echo $category.str_repeat(' ', (20 - strlen($category)));
1266
 
1267
                $sniff = substr($parts[2], 0, -5);
1268
                $sniff = $this->makeFriendlyName($sniff);
1269
                if (strlen($sniff) > 41) {
1270
                    $sniff = substr($sniff, 0, ((strlen($sniff) - 41) * -1));
1271
                }
1272
                echo $sniff.str_repeat(' ', (43 - strlen($sniff)));
1273
            }
1274
 
1275
            echo $count.PHP_EOL;
1276
        }//end foreach
1277
 
1278
        echo str_repeat('-', 80).PHP_EOL;
1279
        echo "A TOTAL OF $errorsShown SNIFF VIOLATION(S) ";
1280
        echo 'WERE FOUND IN '.count($sources).' SOURCE(S)'.PHP_EOL;
1281
        echo str_repeat('-', 80).PHP_EOL.PHP_EOL;
1282
 
1283
        return $errorsShown;
1284
 
1285
    }//end printSourceReport()
1286
 
1287
 
1288
    /**
1289
     * Converts a camel caps name into a readable string.
1290
     *
1291
     * @param string $name The camel caps name to convert.
1292
     *
1293
     * @return string
1294
     */
1295
    public function makeFriendlyName($name)
1296
    {
1297
        $friendlyName = '';
1298
        $length = strlen($name);
1299
 
1300
        $lastWasUpper   = false;
1301
        $lastWasNumeric = false;
1302
        for ($i = 0; $i < $length; $i++) {
1303
            if (is_numeric($name[$i]) === true) {
1304
                if ($lastWasNumeric === false) {
1305
                    $friendlyName .= ' ';
1306
                }
1307
 
1308
                $lastWasUpper   = false;
1309
                $lastWasNumeric = true;
1310
            } else {
1311
                $lastWasNumeric = false;
1312
 
1313
                $char = strtolower($name[$i]);
1314
                if ($char === $name[$i]) {
1315
                    // Lowercase.
1316
                    $lastWasUpper = false;
1317
                } else {
1318
                    // Uppercase.
1319
                    if ($lastWasUpper === false) {
1320
                        $friendlyName .= ' ';
1321
                        $next = $name[($i + 1)];
1322
                        if (strtolower($next) === $next) {
1323
                            // Next char is lowercase so it is a word boundary.
1324
                            $name[$i] = strtolower($name[$i]);
1325
                        }
1326
                    }
1327
 
1328
                    $lastWasUpper = true;
1329
                }
1330
            }//end if
1331
 
1332
            $friendlyName .= $name[$i];
1333
        }//end for
1334
 
1335
        $friendlyName    = trim($friendlyName);
1336
        $friendlyName[0] = strtoupper($friendlyName[0]);
1337
 
1338
        return $friendlyName;
1339
 
1340
    }//end makeFriendlyName()
1341
 
1342
 
1343
    /**
1344
     * Generates documentation for a coding standard.
1345
     *
1346
     * @param string $standard  The standard to generate docs for
1347
     * @param array  $sniffs    A list of sniffs to limit the docs to.
1348
     * @param string $generator The name of the generator class to use.
1349
     *
1350
     * @return void
1351
     */
1352
    public function generateDocs($standard, array $sniffs=array(), $generator='Text')
1353
    {
1354
        include_once 'PHP/CodeSniffer/DocGenerators/'.$generator.'.php';
1355
 
1356
        $class     = "PHP_CodeSniffer_DocGenerators_$generator";
1357
        $generator = new $class($standard, $sniffs);
1358
 
1359
        $generator->generate();
1360
 
1361
    }//end generateDocs()
1362
 
1363
 
1364
    /**
1365
     * Returns the PHP_CodeSniffer file objects.
1366
     *
1367
     * @return array(PHP_CodeSniffer_File)
1368
     */
1369
    public function getFiles()
1370
    {
1371
        return $this->files;
1372
 
1373
    }//end getFiles()
1374
 
1375
 
1376
    /**
1377
     * Gets the array of PHP_CodeSniffer_Sniff's.
1378
     *
1379
     * @return array(PHP_CodeSniffer_Sniff)
1380
     */
1381
    public function getSniffs()
1382
    {
1383
        return $this->listeners;
1384
 
1385
    }//end getSniffs()
1386
 
1387
 
1388
    /**
1389
     * Gets the array of PHP_CodeSniffer_Sniff's indexed by token type.
1390
     *
1391
     * @return array()
1392
     */
1393
    public function getTokenSniffs()
1394
    {
1395
        return $this->_tokenListeners;
1396
 
1397
    }//end getTokenSniffs()
1398
 
1399
 
1400
    /**
1401
     * Takes a token produced from <code>token_get_all()</code> and produces a
1402
     * more uniform token.
1403
     *
1404
     * Note that this method also resolves T_STRING tokens into more descrete
1405
     * types, therefore there is no need to call resolveTstringToken()
1406
     *
1407
     * @param string|array $token The token to convert.
1408
     *
1409
     * @return array The new token.
1410
     */
1411
    public static function standardiseToken($token)
1412
    {
1413
        if (is_array($token) === false) {
1414
            $newToken = self::resolveSimpleToken($token);
1415
        } else {
1416
            switch ($token[0]) {
1417
            case T_STRING:
1418
                // Some T_STRING tokens can be more specific.
1419
                $newToken = self::resolveTstringToken($token);
1420
                break;
1421
            case T_CURLY_OPEN:
1422
                $newToken            = array();
1423
                $newToken['code']    = T_OPEN_CURLY_BRACKET;
1424
                $newToken['content'] = $token[1];
1425
                $newToken['type']    = 'T_OPEN_CURLY_BRACKET';
1426
                break;
1427
            default:
1428
                $newToken            = array();
1429
                $newToken['code']    = $token[0];
1430
                $newToken['content'] = $token[1];
1431
                $newToken['type']    = token_name($token[0]);
1432
                break;
1433
            }//end switch
1434
        }//end if
1435
 
1436
        return $newToken;
1437
 
1438
    }//end standardiseToken()
1439
 
1440
 
1441
    /**
1442
     * Converts T_STRING tokens into more usable token names.
1443
     *
1444
     * The token should be produced using the token_get_all() function.
1445
     * Currently, not all T_STRING tokens are converted.
1446
     *
1447
     * @param string|array $token The T_STRING token to convert as constructed
1448
     *                            by token_get_all().
1449
     *
1450
     * @return array The new token.
1451
     */
1452
    public static function resolveTstringToken(array $token)
1453
    {
1454
        $newToken = array();
1455
        switch (strtolower($token[1])) {
1456
        case 'false':
1457
            $newToken['type'] = 'T_FALSE';
1458
            break;
1459
        case 'true':
1460
            $newToken['type'] = 'T_TRUE';
1461
            break;
1462
        case 'null':
1463
            $newToken['type'] = 'T_NULL';
1464
            break;
1465
        case 'self':
1466
            $newToken['type'] = 'T_SELF';
1467
            break;
1468
        case 'parent':
1469
            $newToken['type'] = 'T_PARENT';
1470
            break;
1471
        default:
1472
            $newToken['type'] = 'T_STRING';
1473
            break;
1474
        }
1475
 
1476
        $newToken['code']    = constant($newToken['type']);
1477
        $newToken['content'] = $token[1];
1478
 
1479
        return $newToken;
1480
 
1481
    }//end resolveTstringToken()
1482
 
1483
 
1484
    /**
1485
     * Converts simple tokens into a format that conforms to complex tokens
1486
     * produced by token_get_all().
1487
     *
1488
     * Simple tokens are tokens that are not in array form when produced from
1489
     * token_get_all().
1490
     *
1491
     * @param string $token The simple token to convert.
1492
     *
1493
     * @return array The new token in array format.
1494
     */
1495
    public static function resolveSimpleToken($token)
1496
    {
1497
        $newToken = array();
1498
 
1499
        switch ($token) {
1500
        case '{':
1501
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
1502
            break;
1503
        case '}':
1504
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
1505
            break;
1506
        case '[':
1507
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
1508
            break;
1509
        case ']':
1510
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
1511
            break;
1512
        case '(':
1513
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
1514
            break;
1515
        case ')':
1516
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
1517
            break;
1518
        case ':':
1519
            $newToken['type'] = 'T_COLON';
1520
            break;
1521
        case '.':
1522
            $newToken['type'] = 'T_STRING_CONCAT';
1523
            break;
1524
        case '?':
1525
            $newToken['type'] = 'T_INLINE_THEN';
1526
            break;
1527
        case ';':
1528
            $newToken['type'] = 'T_SEMICOLON';
1529
            break;
1530
        case '=':
1531
            $newToken['type'] = 'T_EQUAL';
1532
            break;
1533
        case '*':
1534
            $newToken['type'] = 'T_MULTIPLY';
1535
            break;
1536
        case '/':
1537
            $newToken['type'] = 'T_DIVIDE';
1538
            break;
1539
        case '+':
1540
            $newToken['type'] = 'T_PLUS';
1541
            break;
1542
        case '-':
1543
            $newToken['type'] = 'T_MINUS';
1544
            break;
1545
        case '%':
1546
            $newToken['type'] = 'T_MODULUS';
1547
            break;
1548
        case '^':
1549
            $newToken['type'] = 'T_POWER';
1550
            break;
1551
        case '&':
1552
            $newToken['type'] = 'T_BITWISE_AND';
1553
            break;
1554
        case '|':
1555
            $newToken['type'] = 'T_BITWISE_OR';
1556
            break;
1557
        case '<':
1558
            $newToken['type'] = 'T_LESS_THAN';
1559
            break;
1560
        case '>':
1561
            $newToken['type'] = 'T_GREATER_THAN';
1562
            break;
1563
        case '!':
1564
            $newToken['type'] = 'T_BOOLEAN_NOT';
1565
            break;
1566
        case ',':
1567
            $newToken['type'] = 'T_COMMA';
1568
            break;
1569
        case '@':
1570
            $newToken['type'] = 'T_ASPERAND';
1571
            break;
1572
        default:
1573
            $newToken['type'] = 'T_NONE';
1574
            break;
1575
        }//end switch
1576
 
1577
        $newToken['code']    = constant($newToken['type']);
1578
        $newToken['content'] = $token;
1579
 
1580
        return $newToken;
1581
 
1582
    }//end resolveSimpleToken()
1583
 
1584
 
1585
    /**
1586
     * Returns true if the specified string is in the camel caps format.
1587
     *
1588
     * @param string  $string      The string the verify.
1589
     * @param boolean $classFormat If true, check to see if the string is in the
1590
     *                             class format. Class format strings must start
1591
     *                             with a capital letter and contain no
1592
     *                             underscores.
1593
     * @param boolean $public      If true, the first character in the string
1594
     *                             must be an a-z character. If false, the
1595
     *                             character must be an underscore. This
1596
     *                             argument is only applicable if $classFormat
1597
     *                             is false.
1598
     * @param boolean $strict      If true, the string must not have two captial
1599
     *                             letters next to each other. If false, a
1600
     *                             relaxed camel caps policy is used to allow
1601
     *                             for acronyms.
1602
     *
1603
     * @return boolean
1604
     */
1605
    public static function isCamelCaps(
1606
        $string,
1607
        $classFormat=false,
1608
        $public=true,
1609
        $strict=true
1610
    ) {
1611
        // Check the first character first.
1612
        if ($classFormat === false) {
1613
            if ($public === false) {
1614
                $legalFirstChar = '[_][a-z]';
1615
            } else {
1616
                $legalFirstChar = '[a-z]';
1617
            }
1618
        } else {
1619
            $legalFirstChar = '[A-Z]';
1620
        }
1621
 
1622
        if (preg_match("|^$legalFirstChar|", $string) === 0) {
1623
            return false;
1624
        }
1625
 
1626
        // Check that the name only contains legal characters.
1627
        if ($classFormat === false) {
1628
            $legalChars = 'a-zA-Z0-9';
1629
        } else {
1630
            $legalChars = 'a-zA-Z';
1631
        }
1632
 
1633
        if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) {
1634
            return false;
1635
        }
1636
 
1637
        if ($strict === true) {
1638
            // Check that there are not two captial letters next to each other.
1639
            $length          = strlen($string);
1640
            $lastCharWasCaps = $classFormat;
1641
 
1642
            for ($i = 1; $i < $length; $i++) {
1643
                $ascii = ord($string{$i});
1644
                if ($ascii >= 48 && $ascii <= 57) {
1645
                    // The character is a number, so it cant be a captial.
1646
                    $isCaps = false;
1647
                } else {
1648
                    if (strtoupper($string{$i}) === $string{$i}) {
1649
                        $isCaps = true;
1650
                    } else {
1651
                        $isCaps = false;
1652
                    }
1653
                }
1654
 
1655
                if ($isCaps === true && $lastCharWasCaps === true) {
1656
                    return false;
1657
                }
1658
 
1659
                $lastCharWasCaps = $isCaps;
1660
            }
1661
        }//end if
1662
 
1663
        return true;
1664
 
1665
    }//end isCamelCaps()
1666
 
1667
 
1668
    /**
1669
     * Returns true if the specified string is in the underscore caps format.
1670
     *
1671
     * @param string $string The string to verify.
1672
     *
1673
     * @return boolean
1674
     */
1675
    public static function isUnderscoreName($string)
1676
    {
1677
        // If there are space in the name, it can't be valid.
1678
        if (strpos($string, ' ') !== false) {
1679
            return false;
1680
        }
1681
 
1682
        $validName = true;
1683
        $nameBits  = explode('_', $string);
1684
 
1685
        if (preg_match('|^[A-Z]|', $string) === 0) {
1686
            // Name does not begin with a capital letter.
1687
            $validName = false;
1688
        } else {
1689
            foreach ($nameBits as $bit) {
1690
                if ($bit{0} !== strtoupper($bit{0})) {
1691
                    $validName = false;
1692
                    break;
1693
                }
1694
            }
1695
        }
1696
 
1697
        return $validName;
1698
 
1699
    }//end isUnderscoreName()
1700
 
1701
 
1702
    /**
1703
     * Returns a valid variable type for param/var tag.
1704
     *
1705
     * If type is not one of the standard type, it must be a custom type.
1706
     * Returns the correct type name suggestion if type name is invalid.
1707
     *
1708
     * @param string $varType The variable type to process.
1709
     *
1710
     * @return string
1711
     */
1712
    public static function suggestType($varType)
1713
    {
1714
        if ($varType === '') {
1715
            return '';
1716
        }
1717
 
1718
        if (in_array($varType, self::$allowedTypes) === true) {
1719
            return $varType;
1720
        } else {
1721
            $lowerVarType = strtolower($varType);
1722
            switch ($lowerVarType) {
1723
            case 'bool':
1724
                return 'boolean';
1725
            case 'double':
1726
            case 'real':
1727
                return 'float';
1728
            case 'int':
1729
                return 'integer';
1730
            case 'array()':
1731
                return 'array';
1732
            }//end switch
1733
 
1734
            if (strpos($lowerVarType, 'array(') !== false) {
1735
                // Valid array declaration:
1736
                // array, array(type), array(type1 => type2).
1737
                $matches = array();
1738
                $pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i';
1739
                if (preg_match($pattern, $varType, $matches) !== 0) {
1740
                    $type1 = '';
1741
                    if (isset($matches[1]) === true) {
1742
                        $type1 = $matches[1];
1743
                    }
1744
 
1745
                    $type2 = '';
1746
                    if (isset($matches[3]) === true) {
1747
                        $type2 = $matches[3];
1748
                    }
1749
 
1750
                    $type1 = self::suggestType($type1);
1751
                    $type2 = self::suggestType($type2);
1752
                    if ($type2 !== '') {
1753
                        $type2 = ' => '.$type2;
1754
                    }
1755
 
1756
                    return "array($type1$type2)";
1757
                } else {
1758
                    return 'array';
1759
                }//end if
1760
            } else if (in_array($lowerVarType, self::$allowedTypes) === true) {
1761
                // A valid type, but not lower cased.
1762
                return $lowerVarType;
1763
            } else {
1764
                // Must be a custom type name.
1765
                return $varType;
1766
            }//end if
1767
        }//end if
1768
 
1769
    }//end suggestType()
1770
 
1771
 
1772
    /**
1773
     * Get a list of all coding standards installed.
1774
     *
1775
     * Coding standards are directories located in the
1776
     * CodeSniffer/Standards directory. Valid coding standards
1777
     * include a Sniffs subdirectory.
1778
     *
1779
     * @param boolean $includeGeneric If true, the special "Generic"
1780
     *                                coding standard will be included
1781
     *                                if installed.
1782
     * @param string  $standardsDir   A specific directory to look for standards
1783
     *                                in. If not specified, PHP_CodeSniffer will
1784
     *                                look in its default location.
1785
     *
1786
     * @return array
1787
     * @see isInstalledStandard()
1788
     */
1789
    public static function getInstalledStandards(
1790
        $includeGeneric=false,
1791
        $standardsDir=''
1792
    ) {
1793
        $installedStandards = array();
1794
 
1795
        if ($standardsDir === '') {
1796
            $standardsDir = dirname(__FILE__).'/CodeSniffer/Standards';
1797
        }
1798
 
1799
        $di = new DirectoryIterator($standardsDir);
1800
        foreach ($di as $file) {
1801
            if ($file->isDir() === true && $file->isDot() === false) {
1802
                $filename = $file->getFilename();
1803
 
1804
                // Ignore the special "Generic" standard.
1805
                if ($includeGeneric === false && $filename === 'Generic') {
1806
                    continue;
1807
                }
1808
 
1809
                // Valid coding standard dirs include a standard class.
1810
                $csFile = $file->getPathname()."/{$filename}CodingStandard.php";
1811
                if (is_file($csFile) === true) {
1812
                    // We found a coding standard directory.
1813
                    $installedStandards[] = $filename;
1814
                }
1815
            }
1816
        }
1817
 
1818
        return $installedStandards;
1819
 
1820
    }//end getInstalledStandards()
1821
 
1822
 
1823
    /**
1824
     * Determine if a standard is installed.
1825
     *
1826
     * Coding standards are directories located in the
1827
     * CodeSniffer/Standards directory. Valid coding standards
1828
     * include a Sniffs subdirectory.
1829
     *
1830
     * @param string $standard The name of the coding standard.
1831
     *
1832
     * @return boolean
1833
     * @see getInstalledStandards()
1834
     */
1835
    public static function isInstalledStandard($standard)
1836
    {
1837
        $standardDir  = dirname(__FILE__);
1838
        $standardDir .= '/CodeSniffer/Standards/'.$standard;
1839
        if (is_file("$standardDir/{$standard}CodingStandard.php") === true) {
1840
            return true;
1841
        } else {
1842
            // This could be a custom standard, installed outside our
1843
            // standards directory.
1844
            $standardFile = rtrim($standard, ' /\\').DIRECTORY_SEPARATOR.basename($standard).'CodingStandard.php';
1845
            return (is_file($standardFile) === true);
1846
        }
1847
 
1848
    }//end isInstalledStandard()
1849
 
1850
 
1851
    /**
1852
     * Get a single config value.
1853
     *
1854
     * Config data is stored in the data dir, in a file called
1855
     * CodeSniffer.conf. It is a simple PHP array.
1856
     *
1857
     * @param string $key The name of the config value.
1858
     *
1859
     * @return string
1860
     * @see setConfigData()
1861
     * @see getAllConfigData()
1862
     */
1863
    public static function getConfigData($key)
1864
    {
1865
        $phpCodeSnifferConfig = self::getAllConfigData();
1866
 
1867
        if ($phpCodeSnifferConfig === null) {
1868
            return null;
1869
        }
1870
 
1871
        if (isset($phpCodeSnifferConfig[$key]) === false) {
1872
            return null;
1873
        }
1874
 
1875
        return $phpCodeSnifferConfig[$key];
1876
 
1877
    }//end getConfigData()
1878
 
1879
 
1880
    /**
1881
     * Set a single config value.
1882
     *
1883
     * Config data is stored in the data dir, in a file called
1884
     * CodeSniffer.conf. It is a simple PHP array.
1885
     *
1886
     * @param string      $key   The name of the config value.
1887
     * @param string|null $value The value to set. If null, the config
1888
     *                           entry is deleted, reverting it to the
1889
     *                           default value.
1890
     * @param boolean     $temp  Set this config data temporarily for this
1891
     *                           script run. This will not write the config
1892
     *                           data to the config file.
1893
     *
1894
     * @return boolean
1895
     * @see getConfigData()
1896
     * @throws PHP_CodeSniffer_Exception If the config file can not be written.
1897
     */
1898
    public static function setConfigData($key, $value, $temp=false)
1899
    {
1900
        if ($temp === false) {
1901
            $configFile = dirname(__FILE__).'/CodeSniffer.conf';
1902
            if (is_file($configFile) === false) {
1903
                $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
1904
            }
1905
 
1906
            if (is_file($configFile) === true
1907
                && is_writable($configFile) === false
1908
            ) {
1909
                $error = "Config file $configFile is not writable";
1910
                throw new PHP_CodeSniffer_Exception($error);
1911
            }
1912
        }
1913
 
1914
        $phpCodeSnifferConfig = self::getAllConfigData();
1915
 
1916
        if ($value === null) {
1917
            if (isset($phpCodeSnifferConfig[$key]) === true) {
1918
                unset($phpCodeSnifferConfig[$key]);
1919
            }
1920
        } else {
1921
            $phpCodeSnifferConfig[$key] = $value;
1922
        }
1923
 
1924
        if ($temp === false) {
1925
            $output  = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
1926
            $output .= var_export($phpCodeSnifferConfig, true);
1927
            $output .= "\n?".'>';
1928
 
1929
            if (file_put_contents($configFile, $output) === false) {
1930
                return false;
1931
            }
1932
        }
1933
 
1934
        $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig;
1935
 
1936
        return true;
1937
 
1938
    }//end setConfigData()
1939
 
1940
 
1941
    /**
1942
     * Get all config data in an array.
1943
     *
1944
     * @return string
1945
     * @see getConfigData()
1946
     */
1947
    public static function getAllConfigData()
1948
    {
1949
        if (isset($GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']) === true) {
1950
            return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'];
1951
        }
1952
 
1953
        $configFile = dirname(__FILE__).'/CodeSniffer.conf';
1954
        if (is_file($configFile) === false) {
1955
            $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
1956
        }
1957
 
1958
        if (is_file($configFile) === false) {
1959
            return null;
1960
        }
1961
 
1962
        include $configFile;
1963
        $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig;
1964
        return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'];
1965
 
1966
    }//end getAllConfigData()
1967
 
1968
 
1969
}//end class
1970
 
1971
?>