Completed
Pull Request — develop (#1492)
by Zack
15:57
created
vendor/squizlabs/php_codesniffer/src/Files/File.php 1 patch
Indentation   +2461 added lines, -2461 removed lines patch added patch discarded remove patch
@@ -19,2467 +19,2467 @@
 block discarded – undo
19 19
 class File
20 20
 {
21 21
 
22
-    /**
23
-     * The absolute path to the file associated with this object.
24
-     *
25
-     * @var string
26
-     */
27
-    public $path = '';
28
-
29
-    /**
30
-     * The content of the file.
31
-     *
32
-     * @var string
33
-     */
34
-    protected $content = '';
35
-
36
-    /**
37
-     * The config data for the run.
38
-     *
39
-     * @var \PHP_CodeSniffer\Config
40
-     */
41
-    public $config = null;
42
-
43
-    /**
44
-     * The ruleset used for the run.
45
-     *
46
-     * @var \PHP_CodeSniffer\Ruleset
47
-     */
48
-    public $ruleset = null;
49
-
50
-    /**
51
-     * If TRUE, the entire file is being ignored.
52
-     *
53
-     * @var boolean
54
-     */
55
-    public $ignored = false;
56
-
57
-    /**
58
-     * The EOL character this file uses.
59
-     *
60
-     * @var string
61
-     */
62
-    public $eolChar = '';
63
-
64
-    /**
65
-     * The Fixer object to control fixing errors.
66
-     *
67
-     * @var \PHP_CodeSniffer\Fixer
68
-     */
69
-    public $fixer = null;
70
-
71
-    /**
72
-     * The tokenizer being used for this file.
73
-     *
74
-     * @var \PHP_CodeSniffer\Tokenizers\Tokenizer
75
-     */
76
-    public $tokenizer = null;
77
-
78
-    /**
79
-     * The name of the tokenizer being used for this file.
80
-     *
81
-     * @var string
82
-     */
83
-    public $tokenizerType = 'PHP';
84
-
85
-    /**
86
-     * Was the file loaded from cache?
87
-     *
88
-     * If TRUE, the file was loaded from a local cache.
89
-     * If FALSE, the file was tokenized and processed fully.
90
-     *
91
-     * @var boolean
92
-     */
93
-    public $fromCache = false;
94
-
95
-    /**
96
-     * The number of tokens in this file.
97
-     *
98
-     * Stored here to save calling count() everywhere.
99
-     *
100
-     * @var integer
101
-     */
102
-    public $numTokens = 0;
103
-
104
-    /**
105
-     * The tokens stack map.
106
-     *
107
-     * @var array
108
-     */
109
-    protected $tokens = [];
110
-
111
-    /**
112
-     * The errors raised from sniffs.
113
-     *
114
-     * @var array
115
-     * @see getErrors()
116
-     */
117
-    protected $errors = [];
118
-
119
-    /**
120
-     * The warnings raised from sniffs.
121
-     *
122
-     * @var array
123
-     * @see getWarnings()
124
-     */
125
-    protected $warnings = [];
126
-
127
-    /**
128
-     * The metrics recorded by sniffs.
129
-     *
130
-     * @var array
131
-     * @see getMetrics()
132
-     */
133
-    protected $metrics = [];
134
-
135
-    /**
136
-     * The metrics recorded for each token.
137
-     *
138
-     * Stops the same metric being recorded for the same token twice.
139
-     *
140
-     * @var array
141
-     * @see getMetrics()
142
-     */
143
-    private $metricTokens = [];
144
-
145
-    /**
146
-     * The total number of errors raised.
147
-     *
148
-     * @var integer
149
-     */
150
-    protected $errorCount = 0;
151
-
152
-    /**
153
-     * The total number of warnings raised.
154
-     *
155
-     * @var integer
156
-     */
157
-    protected $warningCount = 0;
158
-
159
-    /**
160
-     * The total number of errors and warnings that can be fixed.
161
-     *
162
-     * @var integer
163
-     */
164
-    protected $fixableCount = 0;
165
-
166
-    /**
167
-     * The total number of errors and warnings that were fixed.
168
-     *
169
-     * @var integer
170
-     */
171
-    protected $fixedCount = 0;
172
-
173
-    /**
174
-     * An array of sniffs that are being ignored.
175
-     *
176
-     * @var array
177
-     */
178
-    protected $ignoredListeners = [];
179
-
180
-    /**
181
-     * An array of message codes that are being ignored.
182
-     *
183
-     * @var array
184
-     */
185
-    protected $ignoredCodes = [];
186
-
187
-    /**
188
-     * An array of sniffs listening to this file's processing.
189
-     *
190
-     * @var \PHP_CodeSniffer\Sniffs\Sniff[]
191
-     */
192
-    protected $listeners = [];
193
-
194
-    /**
195
-     * The class name of the sniff currently processing the file.
196
-     *
197
-     * @var string
198
-     */
199
-    protected $activeListener = '';
200
-
201
-    /**
202
-     * An array of sniffs being processed and how long they took.
203
-     *
204
-     * @var array
205
-     */
206
-    protected $listenerTimes = [];
207
-
208
-    /**
209
-     * A cache of often used config settings to improve performance.
210
-     *
211
-     * Storing them here saves 10k+ calls to __get() in the Config class.
212
-     *
213
-     * @var array
214
-     */
215
-    protected $configCache = [];
216
-
217
-
218
-    /**
219
-     * Constructs a file.
220
-     *
221
-     * @param string                   $path    The absolute path to the file to process.
222
-     * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
223
-     * @param \PHP_CodeSniffer\Config  $config  The config data for the run.
224
-     *
225
-     * @return void
226
-     */
227
-    public function __construct($path, Ruleset $ruleset, Config $config)
228
-    {
229
-        $this->path    = $path;
230
-        $this->ruleset = $ruleset;
231
-        $this->config  = $config;
232
-        $this->fixer   = new Fixer();
233
-
234
-        $parts     = explode('.', $path);
235
-        $extension = array_pop($parts);
236
-        if (isset($config->extensions[$extension]) === true) {
237
-            $this->tokenizerType = $config->extensions[$extension];
238
-        } else {
239
-            // Revert to default.
240
-            $this->tokenizerType = 'PHP';
241
-        }
242
-
243
-        $this->configCache['cache']           = $this->config->cache;
244
-        $this->configCache['sniffs']          = array_map('strtolower', $this->config->sniffs);
245
-        $this->configCache['exclude']         = array_map('strtolower', $this->config->exclude);
246
-        $this->configCache['errorSeverity']   = $this->config->errorSeverity;
247
-        $this->configCache['warningSeverity'] = $this->config->warningSeverity;
248
-        $this->configCache['recordErrors']    = $this->config->recordErrors;
249
-        $this->configCache['ignorePatterns']  = $this->ruleset->ignorePatterns;
250
-        $this->configCache['includePatterns'] = $this->ruleset->includePatterns;
251
-
252
-    }//end __construct()
253
-
254
-
255
-    /**
256
-     * Set the content of the file.
257
-     *
258
-     * Setting the content also calculates the EOL char being used.
259
-     *
260
-     * @param string $content The file content.
261
-     *
262
-     * @return void
263
-     */
264
-    public function setContent($content)
265
-    {
266
-        $this->content = $content;
267
-        $this->tokens  = [];
268
-
269
-        try {
270
-            $this->eolChar = Util\Common::detectLineEndings($content);
271
-        } catch (RuntimeException $e) {
272
-            $this->addWarningOnLine($e->getMessage(), 1, 'Internal.DetectLineEndings');
273
-            return;
274
-        }
275
-
276
-    }//end setContent()
277
-
278
-
279
-    /**
280
-     * Reloads the content of the file.
281
-     *
282
-     * By default, we have no idea where our content comes from,
283
-     * so we can't do anything.
284
-     *
285
-     * @return void
286
-     */
287
-    public function reloadContent()
288
-    {
289
-
290
-    }//end reloadContent()
291
-
292
-
293
-    /**
294
-     * Disables caching of this file.
295
-     *
296
-     * @return void
297
-     */
298
-    public function disableCaching()
299
-    {
300
-        $this->configCache['cache'] = false;
301
-
302
-    }//end disableCaching()
303
-
304
-
305
-    /**
306
-     * Starts the stack traversal and tells listeners when tokens are found.
307
-     *
308
-     * @return void
309
-     */
310
-    public function process()
311
-    {
312
-        if ($this->ignored === true) {
313
-            return;
314
-        }
315
-
316
-        $this->errors       = [];
317
-        $this->warnings     = [];
318
-        $this->errorCount   = 0;
319
-        $this->warningCount = 0;
320
-        $this->fixableCount = 0;
321
-
322
-        $this->parse();
323
-
324
-        // Check if tokenizer errors cause this file to be ignored.
325
-        if ($this->ignored === true) {
326
-            return;
327
-        }
328
-
329
-        $this->fixer->startFile($this);
330
-
331
-        if (PHP_CODESNIFFER_VERBOSITY > 2) {
332
-            echo "\t*** START TOKEN PROCESSING ***".PHP_EOL;
333
-        }
334
-
335
-        $foundCode        = false;
336
-        $listenerIgnoreTo = [];
337
-        $inTests          = defined('PHP_CODESNIFFER_IN_TESTS');
338
-        $checkAnnotations = $this->config->annotations;
339
-
340
-        // Foreach of the listeners that have registered to listen for this
341
-        // token, get them to process it.
342
-        foreach ($this->tokens as $stackPtr => $token) {
343
-            // Check for ignored lines.
344
-            if ($checkAnnotations === true
345
-                && ($token['code'] === T_COMMENT
346
-                || $token['code'] === T_PHPCS_IGNORE_FILE
347
-                || $token['code'] === T_PHPCS_SET
348
-                || $token['code'] === T_DOC_COMMENT_STRING
349
-                || $token['code'] === T_DOC_COMMENT_TAG
350
-                || ($inTests === true && $token['code'] === T_INLINE_HTML))
351
-            ) {
352
-                $commentText      = ltrim($this->tokens[$stackPtr]['content'], ' /*');
353
-                $commentTextLower = strtolower($commentText);
354
-                if (strpos($commentText, '@codingStandards') !== false) {
355
-                    if (strpos($commentText, '@codingStandardsIgnoreFile') !== false) {
356
-                        // Ignoring the whole file, just a little late.
357
-                        $this->errors       = [];
358
-                        $this->warnings     = [];
359
-                        $this->errorCount   = 0;
360
-                        $this->warningCount = 0;
361
-                        $this->fixableCount = 0;
362
-                        return;
363
-                    } else if (strpos($commentText, '@codingStandardsChangeSetting') !== false) {
364
-                        $start   = strpos($commentText, '@codingStandardsChangeSetting');
365
-                        $comment = substr($commentText, ($start + 30));
366
-                        $parts   = explode(' ', $comment);
367
-                        if (count($parts) >= 2) {
368
-                            $sniffParts = explode('.', $parts[0]);
369
-                            if (count($sniffParts) >= 3) {
370
-                                // If the sniff code is not known to us, it has not been registered in this run.
371
-                                // But don't throw an error as it could be there for a different standard to use.
372
-                                if (isset($this->ruleset->sniffCodes[$parts[0]]) === true) {
373
-                                    $listenerCode  = array_shift($parts);
374
-                                    $propertyCode  = array_shift($parts);
375
-                                    $propertyValue = rtrim(implode(' ', $parts), " */\r\n");
376
-                                    $listenerClass = $this->ruleset->sniffCodes[$listenerCode];
377
-                                    $this->ruleset->setSniffProperty($listenerClass, $propertyCode, $propertyValue);
378
-                                }
379
-                            }
380
-                        }
381
-                    }//end if
382
-                } else if (substr($commentTextLower, 0, 16) === 'phpcs:ignorefile'
383
-                    || substr($commentTextLower, 0, 17) === '@phpcs:ignorefile'
384
-                ) {
385
-                    // Ignoring the whole file, just a little late.
386
-                    $this->errors       = [];
387
-                    $this->warnings     = [];
388
-                    $this->errorCount   = 0;
389
-                    $this->warningCount = 0;
390
-                    $this->fixableCount = 0;
391
-                    return;
392
-                } else if (substr($commentTextLower, 0, 9) === 'phpcs:set'
393
-                    || substr($commentTextLower, 0, 10) === '@phpcs:set'
394
-                ) {
395
-                    if (isset($token['sniffCode']) === true) {
396
-                        $listenerCode = $token['sniffCode'];
397
-                        if (isset($this->ruleset->sniffCodes[$listenerCode]) === true) {
398
-                            $propertyCode  = $token['sniffProperty'];
399
-                            $propertyValue = $token['sniffPropertyValue'];
400
-                            $listenerClass = $this->ruleset->sniffCodes[$listenerCode];
401
-                            $this->ruleset->setSniffProperty($listenerClass, $propertyCode, $propertyValue);
402
-                        }
403
-                    }
404
-                }//end if
405
-            }//end if
406
-
407
-            if (PHP_CODESNIFFER_VERBOSITY > 2) {
408
-                $type    = $token['type'];
409
-                $content = Util\Common::prepareForOutput($token['content']);
410
-                echo "\t\tProcess token $stackPtr: $type => $content".PHP_EOL;
411
-            }
412
-
413
-            if ($token['code'] !== T_INLINE_HTML) {
414
-                $foundCode = true;
415
-            }
416
-
417
-            if (isset($this->ruleset->tokenListeners[$token['code']]) === false) {
418
-                continue;
419
-            }
420
-
421
-            foreach ($this->ruleset->tokenListeners[$token['code']] as $listenerData) {
422
-                if (isset($this->ignoredListeners[$listenerData['class']]) === true
423
-                    || (isset($listenerIgnoreTo[$listenerData['class']]) === true
424
-                    && $listenerIgnoreTo[$listenerData['class']] > $stackPtr)
425
-                ) {
426
-                    // This sniff is ignoring past this token, or the whole file.
427
-                    continue;
428
-                }
429
-
430
-                // Make sure this sniff supports the tokenizer
431
-                // we are currently using.
432
-                $class = $listenerData['class'];
433
-
434
-                if (isset($listenerData['tokenizers'][$this->tokenizerType]) === false) {
435
-                    continue;
436
-                }
437
-
438
-                // If the file path matches one of our ignore patterns, skip it.
439
-                // While there is support for a type of each pattern
440
-                // (absolute or relative) we don't actually support it here.
441
-                foreach ($listenerData['ignore'] as $pattern) {
442
-                    // We assume a / directory separator, as do the exclude rules
443
-                    // most developers write, so we need a special case for any system
444
-                    // that is different.
445
-                    if (DIRECTORY_SEPARATOR === '\\') {
446
-                        $pattern = str_replace('/', '\\\\', $pattern);
447
-                    }
448
-
449
-                    $pattern = '`'.$pattern.'`i';
450
-                    if (preg_match($pattern, $this->path) === 1) {
451
-                        $this->ignoredListeners[$class] = true;
452
-                        continue(2);
453
-                    }
454
-                }
455
-
456
-                // If the file path does not match one of our include patterns, skip it.
457
-                // While there is support for a type of each pattern
458
-                // (absolute or relative) we don't actually support it here.
459
-                if (empty($listenerData['include']) === false) {
460
-                    $included = false;
461
-                    foreach ($listenerData['include'] as $pattern) {
462
-                        // We assume a / directory separator, as do the exclude rules
463
-                        // most developers write, so we need a special case for any system
464
-                        // that is different.
465
-                        if (DIRECTORY_SEPARATOR === '\\') {
466
-                            $pattern = str_replace('/', '\\\\', $pattern);
467
-                        }
468
-
469
-                        $pattern = '`'.$pattern.'`i';
470
-                        if (preg_match($pattern, $this->path) === 1) {
471
-                            $included = true;
472
-                            break;
473
-                        }
474
-                    }
475
-
476
-                    if ($included === false) {
477
-                        $this->ignoredListeners[$class] = true;
478
-                        continue;
479
-                    }
480
-                }//end if
481
-
482
-                $this->activeListener = $class;
483
-
484
-                if (PHP_CODESNIFFER_VERBOSITY > 2) {
485
-                    $startTime = microtime(true);
486
-                    echo "\t\t\tProcessing ".$this->activeListener.'... ';
487
-                }
488
-
489
-                $ignoreTo = $this->ruleset->sniffs[$class]->process($this, $stackPtr);
490
-                if ($ignoreTo !== null) {
491
-                    $listenerIgnoreTo[$this->activeListener] = $ignoreTo;
492
-                }
493
-
494
-                if (PHP_CODESNIFFER_VERBOSITY > 2) {
495
-                    $timeTaken = (microtime(true) - $startTime);
496
-                    if (isset($this->listenerTimes[$this->activeListener]) === false) {
497
-                        $this->listenerTimes[$this->activeListener] = 0;
498
-                    }
499
-
500
-                    $this->listenerTimes[$this->activeListener] += $timeTaken;
501
-
502
-                    $timeTaken = round(($timeTaken), 4);
503
-                    echo "DONE in $timeTaken seconds".PHP_EOL;
504
-                }
505
-
506
-                $this->activeListener = '';
507
-            }//end foreach
508
-        }//end foreach
509
-
510
-        // If short open tags are off but the file being checked uses
511
-        // short open tags, the whole content will be inline HTML
512
-        // and nothing will be checked. So try and handle this case.
513
-        // We don't show this error for STDIN because we can't be sure the content
514
-        // actually came directly from the user. It could be something like
515
-        // refs from a Git pre-push hook.
516
-        if ($foundCode === false && $this->tokenizerType === 'PHP' && $this->path !== 'STDIN') {
517
-            $shortTags = (bool) ini_get('short_open_tag');
518
-            if ($shortTags === false) {
519
-                $error = 'No PHP code was found in this file and short open tags are not allowed by this install of PHP. This file may be using short open tags but PHP does not allow them.';
520
-                $this->addWarning($error, null, 'Internal.NoCodeFound');
521
-            }
522
-        }
523
-
524
-        if (PHP_CODESNIFFER_VERBOSITY > 2) {
525
-            echo "\t*** END TOKEN PROCESSING ***".PHP_EOL;
526
-            echo "\t*** START SNIFF PROCESSING REPORT ***".PHP_EOL;
527
-
528
-            asort($this->listenerTimes, SORT_NUMERIC);
529
-            $this->listenerTimes = array_reverse($this->listenerTimes, true);
530
-            foreach ($this->listenerTimes as $listener => $timeTaken) {
531
-                echo "\t$listener: ".round(($timeTaken), 4).' secs'.PHP_EOL;
532
-            }
533
-
534
-            echo "\t*** END SNIFF PROCESSING REPORT ***".PHP_EOL;
535
-        }
536
-
537
-        $this->fixedCount += $this->fixer->getFixCount();
538
-
539
-    }//end process()
540
-
541
-
542
-    /**
543
-     * Tokenizes the file and prepares it for the test run.
544
-     *
545
-     * @return void
546
-     */
547
-    public function parse()
548
-    {
549
-        if (empty($this->tokens) === false) {
550
-            // File has already been parsed.
551
-            return;
552
-        }
553
-
554
-        try {
555
-            $tokenizerClass  = 'PHP_CodeSniffer\Tokenizers\\'.$this->tokenizerType;
556
-            $this->tokenizer = new $tokenizerClass($this->content, $this->config, $this->eolChar);
557
-            $this->tokens    = $this->tokenizer->getTokens();
558
-        } catch (TokenizerException $e) {
559
-            $this->ignored = true;
560
-            $this->addWarning($e->getMessage(), null, 'Internal.Tokenizer.Exception');
561
-            if (PHP_CODESNIFFER_VERBOSITY > 0) {
562
-                echo "[$this->tokenizerType => tokenizer error]... ";
563
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
564
-                    echo PHP_EOL;
565
-                }
566
-            }
567
-
568
-            return;
569
-        }
570
-
571
-        $this->numTokens = count($this->tokens);
572
-
573
-        // Check for mixed line endings as these can cause tokenizer errors and we
574
-        // should let the user know that the results they get may be incorrect.
575
-        // This is done by removing all backslashes, removing the newline char we
576
-        // detected, then converting newlines chars into text. If any backslashes
577
-        // are left at the end, we have additional newline chars in use.
578
-        $contents = str_replace('\\', '', $this->content);
579
-        $contents = str_replace($this->eolChar, '', $contents);
580
-        $contents = str_replace("\n", '\n', $contents);
581
-        $contents = str_replace("\r", '\r', $contents);
582
-        if (strpos($contents, '\\') !== false) {
583
-            $error = 'File has mixed line endings; this may cause incorrect results';
584
-            $this->addWarningOnLine($error, 1, 'Internal.LineEndings.Mixed');
585
-        }
586
-
587
-        if (PHP_CODESNIFFER_VERBOSITY > 0) {
588
-            if ($this->numTokens === 0) {
589
-                $numLines = 0;
590
-            } else {
591
-                $numLines = $this->tokens[($this->numTokens - 1)]['line'];
592
-            }
593
-
594
-            echo "[$this->tokenizerType => $this->numTokens tokens in $numLines lines]... ";
595
-            if (PHP_CODESNIFFER_VERBOSITY > 1) {
596
-                echo PHP_EOL;
597
-            }
598
-        }
599
-
600
-    }//end parse()
601
-
602
-
603
-    /**
604
-     * Returns the token stack for this file.
605
-     *
606
-     * @return array
607
-     */
608
-    public function getTokens()
609
-    {
610
-        return $this->tokens;
611
-
612
-    }//end getTokens()
613
-
614
-
615
-    /**
616
-     * Remove vars stored in this file that are no longer required.
617
-     *
618
-     * @return void
619
-     */
620
-    public function cleanUp()
621
-    {
622
-        $this->listenerTimes = null;
623
-        $this->content       = null;
624
-        $this->tokens        = null;
625
-        $this->metricTokens  = null;
626
-        $this->tokenizer     = null;
627
-        $this->fixer         = null;
628
-        $this->config        = null;
629
-        $this->ruleset       = null;
630
-
631
-    }//end cleanUp()
632
-
633
-
634
-    /**
635
-     * Records an error against a specific token in the file.
636
-     *
637
-     * @param string  $error    The error message.
638
-     * @param int     $stackPtr The stack position where the error occurred.
639
-     * @param string  $code     A violation code unique to the sniff message.
640
-     * @param array   $data     Replacements for the error message.
641
-     * @param int     $severity The severity level for this error. A value of 0
642
-     *                          will be converted into the default severity level.
643
-     * @param boolean $fixable  Can the error be fixed by the sniff?
644
-     *
645
-     * @return boolean
646
-     */
647
-    public function addError(
648
-        $error,
649
-        $stackPtr,
650
-        $code,
651
-        $data=[],
652
-        $severity=0,
653
-        $fixable=false
654
-    ) {
655
-        if ($stackPtr === null) {
656
-            $line   = 1;
657
-            $column = 1;
658
-        } else {
659
-            $line   = $this->tokens[$stackPtr]['line'];
660
-            $column = $this->tokens[$stackPtr]['column'];
661
-        }
662
-
663
-        return $this->addMessage(true, $error, $line, $column, $code, $data, $severity, $fixable);
664
-
665
-    }//end addError()
666
-
667
-
668
-    /**
669
-     * Records a warning against a specific token in the file.
670
-     *
671
-     * @param string  $warning  The error message.
672
-     * @param int     $stackPtr The stack position where the error occurred.
673
-     * @param string  $code     A violation code unique to the sniff message.
674
-     * @param array   $data     Replacements for the warning message.
675
-     * @param int     $severity The severity level for this warning. A value of 0
676
-     *                          will be converted into the default severity level.
677
-     * @param boolean $fixable  Can the warning be fixed by the sniff?
678
-     *
679
-     * @return boolean
680
-     */
681
-    public function addWarning(
682
-        $warning,
683
-        $stackPtr,
684
-        $code,
685
-        $data=[],
686
-        $severity=0,
687
-        $fixable=false
688
-    ) {
689
-        if ($stackPtr === null) {
690
-            $line   = 1;
691
-            $column = 1;
692
-        } else {
693
-            $line   = $this->tokens[$stackPtr]['line'];
694
-            $column = $this->tokens[$stackPtr]['column'];
695
-        }
696
-
697
-        return $this->addMessage(false, $warning, $line, $column, $code, $data, $severity, $fixable);
698
-
699
-    }//end addWarning()
700
-
701
-
702
-    /**
703
-     * Records an error against a specific line in the file.
704
-     *
705
-     * @param string $error    The error message.
706
-     * @param int    $line     The line on which the error occurred.
707
-     * @param string $code     A violation code unique to the sniff message.
708
-     * @param array  $data     Replacements for the error message.
709
-     * @param int    $severity The severity level for this error. A value of 0
710
-     *                         will be converted into the default severity level.
711
-     *
712
-     * @return boolean
713
-     */
714
-    public function addErrorOnLine(
715
-        $error,
716
-        $line,
717
-        $code,
718
-        $data=[],
719
-        $severity=0
720
-    ) {
721
-        return $this->addMessage(true, $error, $line, 1, $code, $data, $severity, false);
722
-
723
-    }//end addErrorOnLine()
724
-
725
-
726
-    /**
727
-     * Records a warning against a specific token in the file.
728
-     *
729
-     * @param string $warning  The error message.
730
-     * @param int    $line     The line on which the warning occurred.
731
-     * @param string $code     A violation code unique to the sniff message.
732
-     * @param array  $data     Replacements for the warning message.
733
-     * @param int    $severity The severity level for this warning. A value of 0 will
734
-     *                         will be converted into the default severity level.
735
-     *
736
-     * @return boolean
737
-     */
738
-    public function addWarningOnLine(
739
-        $warning,
740
-        $line,
741
-        $code,
742
-        $data=[],
743
-        $severity=0
744
-    ) {
745
-        return $this->addMessage(false, $warning, $line, 1, $code, $data, $severity, false);
746
-
747
-    }//end addWarningOnLine()
748
-
749
-
750
-    /**
751
-     * Records a fixable error against a specific token in the file.
752
-     *
753
-     * Returns true if the error was recorded and should be fixed.
754
-     *
755
-     * @param string $error    The error message.
756
-     * @param int    $stackPtr The stack position where the error occurred.
757
-     * @param string $code     A violation code unique to the sniff message.
758
-     * @param array  $data     Replacements for the error message.
759
-     * @param int    $severity The severity level for this error. A value of 0
760
-     *                         will be converted into the default severity level.
761
-     *
762
-     * @return boolean
763
-     */
764
-    public function addFixableError(
765
-        $error,
766
-        $stackPtr,
767
-        $code,
768
-        $data=[],
769
-        $severity=0
770
-    ) {
771
-        $recorded = $this->addError($error, $stackPtr, $code, $data, $severity, true);
772
-        if ($recorded === true && $this->fixer->enabled === true) {
773
-            return true;
774
-        }
775
-
776
-        return false;
777
-
778
-    }//end addFixableError()
779
-
780
-
781
-    /**
782
-     * Records a fixable warning against a specific token in the file.
783
-     *
784
-     * Returns true if the warning was recorded and should be fixed.
785
-     *
786
-     * @param string $warning  The error message.
787
-     * @param int    $stackPtr The stack position where the error occurred.
788
-     * @param string $code     A violation code unique to the sniff message.
789
-     * @param array  $data     Replacements for the warning message.
790
-     * @param int    $severity The severity level for this warning. A value of 0
791
-     *                         will be converted into the default severity level.
792
-     *
793
-     * @return boolean
794
-     */
795
-    public function addFixableWarning(
796
-        $warning,
797
-        $stackPtr,
798
-        $code,
799
-        $data=[],
800
-        $severity=0
801
-    ) {
802
-        $recorded = $this->addWarning($warning, $stackPtr, $code, $data, $severity, true);
803
-        if ($recorded === true && $this->fixer->enabled === true) {
804
-            return true;
805
-        }
806
-
807
-        return false;
808
-
809
-    }//end addFixableWarning()
810
-
811
-
812
-    /**
813
-     * Adds an error to the error stack.
814
-     *
815
-     * @param boolean $error    Is this an error message?
816
-     * @param string  $message  The text of the message.
817
-     * @param int     $line     The line on which the message occurred.
818
-     * @param int     $column   The column at which the message occurred.
819
-     * @param string  $code     A violation code unique to the sniff message.
820
-     * @param array   $data     Replacements for the message.
821
-     * @param int     $severity The severity level for this message. A value of 0
822
-     *                          will be converted into the default severity level.
823
-     * @param boolean $fixable  Can the problem be fixed by the sniff?
824
-     *
825
-     * @return boolean
826
-     */
827
-    protected function addMessage($error, $message, $line, $column, $code, $data, $severity, $fixable)
828
-    {
829
-        // Check if this line is ignoring all message codes.
830
-        if (isset($this->tokenizer->ignoredLines[$line]['.all']) === true) {
831
-            return false;
832
-        }
833
-
834
-        // Work out which sniff generated the message.
835
-        $parts = explode('.', $code);
836
-        if ($parts[0] === 'Internal') {
837
-            // An internal message.
838
-            $listenerCode = Util\Common::getSniffCode($this->activeListener);
839
-            $sniffCode    = $code;
840
-            $checkCodes   = [$sniffCode];
841
-        } else {
842
-            if ($parts[0] !== $code) {
843
-                // The full message code has been passed in.
844
-                $sniffCode    = $code;
845
-                $listenerCode = substr($sniffCode, 0, strrpos($sniffCode, '.'));
846
-            } else {
847
-                $listenerCode = Util\Common::getSniffCode($this->activeListener);
848
-                $sniffCode    = $listenerCode.'.'.$code;
849
-                $parts        = explode('.', $sniffCode);
850
-            }
851
-
852
-            $checkCodes = [
853
-                $sniffCode,
854
-                $parts[0].'.'.$parts[1].'.'.$parts[2],
855
-                $parts[0].'.'.$parts[1],
856
-                $parts[0],
857
-            ];
858
-        }//end if
859
-
860
-        if (isset($this->tokenizer->ignoredLines[$line]) === true) {
861
-            // Check if this line is ignoring this specific message.
862
-            $ignored = false;
863
-            foreach ($checkCodes as $checkCode) {
864
-                if (isset($this->tokenizer->ignoredLines[$line][$checkCode]) === true) {
865
-                    $ignored = true;
866
-                    break;
867
-                }
868
-            }
869
-
870
-            // If it is ignored, make sure it's not whitelisted.
871
-            if ($ignored === true
872
-                && isset($this->tokenizer->ignoredLines[$line]['.except']) === true
873
-            ) {
874
-                foreach ($checkCodes as $checkCode) {
875
-                    if (isset($this->tokenizer->ignoredLines[$line]['.except'][$checkCode]) === true) {
876
-                        $ignored = false;
877
-                        break;
878
-                    }
879
-                }
880
-            }
881
-
882
-            if ($ignored === true) {
883
-                return false;
884
-            }
885
-        }//end if
886
-
887
-        $includeAll = true;
888
-        if ($this->configCache['cache'] === false
889
-            || $this->configCache['recordErrors'] === false
890
-        ) {
891
-            $includeAll = false;
892
-        }
893
-
894
-        // Filter out any messages for sniffs that shouldn't have run
895
-        // due to the use of the --sniffs command line argument.
896
-        if ($includeAll === false
897
-            && ((empty($this->configCache['sniffs']) === false
898
-            && in_array(strtolower($listenerCode), $this->configCache['sniffs'], true) === false)
899
-            || (empty($this->configCache['exclude']) === false
900
-            && in_array(strtolower($listenerCode), $this->configCache['exclude'], true) === true))
901
-        ) {
902
-            return false;
903
-        }
904
-
905
-        // If we know this sniff code is being ignored for this file, return early.
906
-        foreach ($checkCodes as $checkCode) {
907
-            if (isset($this->ignoredCodes[$checkCode]) === true) {
908
-                return false;
909
-            }
910
-        }
911
-
912
-        $oppositeType = 'warning';
913
-        if ($error === false) {
914
-            $oppositeType = 'error';
915
-        }
916
-
917
-        foreach ($checkCodes as $checkCode) {
918
-            // Make sure this message type has not been set to the opposite message type.
919
-            if (isset($this->ruleset->ruleset[$checkCode]['type']) === true
920
-                && $this->ruleset->ruleset[$checkCode]['type'] === $oppositeType
921
-            ) {
922
-                $error = !$error;
923
-                break;
924
-            }
925
-        }
926
-
927
-        if ($error === true) {
928
-            $configSeverity = $this->configCache['errorSeverity'];
929
-            $messageCount   = &$this->errorCount;
930
-            $messages       = &$this->errors;
931
-        } else {
932
-            $configSeverity = $this->configCache['warningSeverity'];
933
-            $messageCount   = &$this->warningCount;
934
-            $messages       = &$this->warnings;
935
-        }
936
-
937
-        if ($includeAll === false && $configSeverity === 0) {
938
-            // Don't bother doing any processing as these messages are just going to
939
-            // be hidden in the reports anyway.
940
-            return false;
941
-        }
942
-
943
-        if ($severity === 0) {
944
-            $severity = 5;
945
-        }
946
-
947
-        foreach ($checkCodes as $checkCode) {
948
-            // Make sure we are interested in this severity level.
949
-            if (isset($this->ruleset->ruleset[$checkCode]['severity']) === true) {
950
-                $severity = $this->ruleset->ruleset[$checkCode]['severity'];
951
-                break;
952
-            }
953
-        }
954
-
955
-        if ($includeAll === false && $configSeverity > $severity) {
956
-            return false;
957
-        }
958
-
959
-        // Make sure we are not ignoring this file.
960
-        $included = null;
961
-        foreach ($checkCodes as $checkCode) {
962
-            $patterns = null;
963
-
964
-            if (isset($this->configCache['includePatterns'][$checkCode]) === true) {
965
-                $patterns  = $this->configCache['includePatterns'][$checkCode];
966
-                $excluding = false;
967
-            } else if (isset($this->configCache['ignorePatterns'][$checkCode]) === true) {
968
-                $patterns  = $this->configCache['ignorePatterns'][$checkCode];
969
-                $excluding = true;
970
-            }
971
-
972
-            if ($patterns === null) {
973
-                continue;
974
-            }
975
-
976
-            foreach ($patterns as $pattern => $type) {
977
-                // While there is support for a type of each pattern
978
-                // (absolute or relative) we don't actually support it here.
979
-                $replacements = [
980
-                    '\\,' => ',',
981
-                    '*'   => '.*',
982
-                ];
983
-
984
-                // We assume a / directory separator, as do the exclude rules
985
-                // most developers write, so we need a special case for any system
986
-                // that is different.
987
-                if (DIRECTORY_SEPARATOR === '\\') {
988
-                    $replacements['/'] = '\\\\';
989
-                }
990
-
991
-                $pattern = '`'.strtr($pattern, $replacements).'`i';
992
-                $matched = preg_match($pattern, $this->path);
993
-
994
-                if ($matched === 0) {
995
-                    if ($excluding === false && $included === null) {
996
-                        // This file path is not being included.
997
-                        $included = false;
998
-                    }
999
-
1000
-                    continue;
1001
-                }
1002
-
1003
-                if ($excluding === true) {
1004
-                    // This file path is being excluded.
1005
-                    $this->ignoredCodes[$checkCode] = true;
1006
-                    return false;
1007
-                }
1008
-
1009
-                // This file path is being included.
1010
-                $included = true;
1011
-                break;
1012
-            }//end foreach
1013
-        }//end foreach
1014
-
1015
-        if ($included === false) {
1016
-            // There were include rules set, but this file
1017
-            // path didn't match any of them.
1018
-            return false;
1019
-        }
1020
-
1021
-        $messageCount++;
1022
-        if ($fixable === true) {
1023
-            $this->fixableCount++;
1024
-        }
1025
-
1026
-        if ($this->configCache['recordErrors'] === false
1027
-            && $includeAll === false
1028
-        ) {
1029
-            return true;
1030
-        }
1031
-
1032
-        // Work out the error message.
1033
-        if (isset($this->ruleset->ruleset[$sniffCode]['message']) === true) {
1034
-            $message = $this->ruleset->ruleset[$sniffCode]['message'];
1035
-        }
1036
-
1037
-        if (empty($data) === false) {
1038
-            $message = vsprintf($message, $data);
1039
-        }
1040
-
1041
-        if (isset($messages[$line]) === false) {
1042
-            $messages[$line] = [];
1043
-        }
1044
-
1045
-        if (isset($messages[$line][$column]) === false) {
1046
-            $messages[$line][$column] = [];
1047
-        }
1048
-
1049
-        $messages[$line][$column][] = [
1050
-            'message'  => $message,
1051
-            'source'   => $sniffCode,
1052
-            'listener' => $this->activeListener,
1053
-            'severity' => $severity,
1054
-            'fixable'  => $fixable,
1055
-        ];
1056
-
1057
-        if (PHP_CODESNIFFER_VERBOSITY > 1
1058
-            && $this->fixer->enabled === true
1059
-            && $fixable === true
1060
-        ) {
1061
-            @ob_end_clean();
1062
-            echo "\tE: [Line $line] $message ($sniffCode)".PHP_EOL;
1063
-            ob_start();
1064
-        }
1065
-
1066
-        return true;
1067
-
1068
-    }//end addMessage()
1069
-
1070
-
1071
-    /**
1072
-     * Record a metric about the file being examined.
1073
-     *
1074
-     * @param int    $stackPtr The stack position where the metric was recorded.
1075
-     * @param string $metric   The name of the metric being recorded.
1076
-     * @param string $value    The value of the metric being recorded.
1077
-     *
1078
-     * @return boolean
1079
-     */
1080
-    public function recordMetric($stackPtr, $metric, $value)
1081
-    {
1082
-        if (isset($this->metrics[$metric]) === false) {
1083
-            $this->metrics[$metric] = ['values' => [$value => 1]];
1084
-            $this->metricTokens[$metric][$stackPtr] = true;
1085
-        } else if (isset($this->metricTokens[$metric][$stackPtr]) === false) {
1086
-            $this->metricTokens[$metric][$stackPtr] = true;
1087
-            if (isset($this->metrics[$metric]['values'][$value]) === false) {
1088
-                $this->metrics[$metric]['values'][$value] = 1;
1089
-            } else {
1090
-                $this->metrics[$metric]['values'][$value]++;
1091
-            }
1092
-        }
1093
-
1094
-        return true;
1095
-
1096
-    }//end recordMetric()
1097
-
1098
-
1099
-    /**
1100
-     * Returns the number of errors raised.
1101
-     *
1102
-     * @return int
1103
-     */
1104
-    public function getErrorCount()
1105
-    {
1106
-        return $this->errorCount;
1107
-
1108
-    }//end getErrorCount()
1109
-
1110
-
1111
-    /**
1112
-     * Returns the number of warnings raised.
1113
-     *
1114
-     * @return int
1115
-     */
1116
-    public function getWarningCount()
1117
-    {
1118
-        return $this->warningCount;
1119
-
1120
-    }//end getWarningCount()
1121
-
1122
-
1123
-    /**
1124
-     * Returns the number of fixable errors/warnings raised.
1125
-     *
1126
-     * @return int
1127
-     */
1128
-    public function getFixableCount()
1129
-    {
1130
-        return $this->fixableCount;
1131
-
1132
-    }//end getFixableCount()
1133
-
1134
-
1135
-    /**
1136
-     * Returns the number of fixed errors/warnings.
1137
-     *
1138
-     * @return int
1139
-     */
1140
-    public function getFixedCount()
1141
-    {
1142
-        return $this->fixedCount;
1143
-
1144
-    }//end getFixedCount()
1145
-
1146
-
1147
-    /**
1148
-     * Returns the list of ignored lines.
1149
-     *
1150
-     * @return array
1151
-     */
1152
-    public function getIgnoredLines()
1153
-    {
1154
-        return $this->tokenizer->ignoredLines;
1155
-
1156
-    }//end getIgnoredLines()
1157
-
1158
-
1159
-    /**
1160
-     * Returns the errors raised from processing this file.
1161
-     *
1162
-     * @return array
1163
-     */
1164
-    public function getErrors()
1165
-    {
1166
-        return $this->errors;
1167
-
1168
-    }//end getErrors()
1169
-
1170
-
1171
-    /**
1172
-     * Returns the warnings raised from processing this file.
1173
-     *
1174
-     * @return array
1175
-     */
1176
-    public function getWarnings()
1177
-    {
1178
-        return $this->warnings;
1179
-
1180
-    }//end getWarnings()
1181
-
1182
-
1183
-    /**
1184
-     * Returns the metrics found while processing this file.
1185
-     *
1186
-     * @return array
1187
-     */
1188
-    public function getMetrics()
1189
-    {
1190
-        return $this->metrics;
1191
-
1192
-    }//end getMetrics()
1193
-
1194
-
1195
-    /**
1196
-     * Returns the absolute filename of this file.
1197
-     *
1198
-     * @return string
1199
-     */
1200
-    public function getFilename()
1201
-    {
1202
-        return $this->path;
1203
-
1204
-    }//end getFilename()
1205
-
1206
-
1207
-    /**
1208
-     * Returns the declaration names for classes, interfaces, traits, and functions.
1209
-     *
1210
-     * @param int $stackPtr The position of the declaration token which
1211
-     *                      declared the class, interface, trait, or function.
1212
-     *
1213
-     * @return string|null The name of the class, interface, trait, or function;
1214
-     *                     or NULL if the function or class is anonymous.
1215
-     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token is not of type
1216
-     *                                                      T_FUNCTION, T_CLASS, T_ANON_CLASS,
1217
-     *                                                      T_CLOSURE, T_TRAIT, or T_INTERFACE.
1218
-     */
1219
-    public function getDeclarationName($stackPtr)
1220
-    {
1221
-        $tokenCode = $this->tokens[$stackPtr]['code'];
1222
-
1223
-        if ($tokenCode === T_ANON_CLASS || $tokenCode === T_CLOSURE) {
1224
-            return null;
1225
-        }
1226
-
1227
-        if ($tokenCode !== T_FUNCTION
1228
-            && $tokenCode !== T_CLASS
1229
-            && $tokenCode !== T_INTERFACE
1230
-            && $tokenCode !== T_TRAIT
1231
-        ) {
1232
-            throw new RuntimeException('Token type "'.$this->tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT');
1233
-        }
1234
-
1235
-        if ($tokenCode === T_FUNCTION
1236
-            && strtolower($this->tokens[$stackPtr]['content']) !== 'function'
1237
-        ) {
1238
-            // This is a function declared without the "function" keyword.
1239
-            // So this token is the function name.
1240
-            return $this->tokens[$stackPtr]['content'];
1241
-        }
1242
-
1243
-        $content = null;
1244
-        for ($i = $stackPtr; $i < $this->numTokens; $i++) {
1245
-            if ($this->tokens[$i]['code'] === T_STRING) {
1246
-                $content = $this->tokens[$i]['content'];
1247
-                break;
1248
-            }
1249
-        }
1250
-
1251
-        return $content;
1252
-
1253
-    }//end getDeclarationName()
1254
-
1255
-
1256
-    /**
1257
-     * Returns the method parameters for the specified function token.
1258
-     *
1259
-     * Each parameter is in the following format:
1260
-     *
1261
-     * <code>
1262
-     *   0 => array(
1263
-     *         'name'              => '$var',  // The variable name.
1264
-     *         'token'             => integer, // The stack pointer to the variable name.
1265
-     *         'content'           => string,  // The full content of the variable definition.
1266
-     *         'pass_by_reference' => boolean, // Is the variable passed by reference?
1267
-     *         'variable_length'   => boolean, // Is the param of variable length through use of `...` ?
1268
-     *         'type_hint'         => string,  // The type hint for the variable.
1269
-     *         'type_hint_token'   => integer, // The stack pointer to the type hint
1270
-     *                                         // or false if there is no type hint.
1271
-     *         'nullable_type'     => boolean, // Is the variable using a nullable type?
1272
-     *        )
1273
-     * </code>
1274
-     *
1275
-     * Parameters with default values have an additional array index of
1276
-     * 'default' with the value of the default as a string.
1277
-     *
1278
-     * @param int $stackPtr The position in the stack of the function token
1279
-     *                      to acquire the parameters for.
1280
-     *
1281
-     * @return array
1282
-     * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified $stackPtr is not of
1283
-     *                                                        type T_FUNCTION or T_CLOSURE.
1284
-     */
1285
-    public function getMethodParameters($stackPtr)
1286
-    {
1287
-        if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION
1288
-            && $this->tokens[$stackPtr]['code'] !== T_CLOSURE
1289
-        ) {
1290
-            throw new TokenizerException('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
1291
-        }
1292
-
1293
-        $opener = $this->tokens[$stackPtr]['parenthesis_opener'];
1294
-        $closer = $this->tokens[$stackPtr]['parenthesis_closer'];
1295
-
1296
-        $vars            = [];
1297
-        $currVar         = null;
1298
-        $paramStart      = ($opener + 1);
1299
-        $defaultStart    = null;
1300
-        $paramCount      = 0;
1301
-        $passByReference = false;
1302
-        $variableLength  = false;
1303
-        $typeHint        = '';
1304
-        $typeHintToken   = false;
1305
-        $nullableType    = false;
1306
-
1307
-        for ($i = $paramStart; $i <= $closer; $i++) {
1308
-            // Check to see if this token has a parenthesis or bracket opener. If it does
1309
-            // it's likely to be an array which might have arguments in it. This
1310
-            // could cause problems in our parsing below, so lets just skip to the
1311
-            // end of it.
1312
-            if (isset($this->tokens[$i]['parenthesis_opener']) === true) {
1313
-                // Don't do this if it's the close parenthesis for the method.
1314
-                if ($i !== $this->tokens[$i]['parenthesis_closer']) {
1315
-                    $i = ($this->tokens[$i]['parenthesis_closer'] + 1);
1316
-                }
1317
-            }
1318
-
1319
-            if (isset($this->tokens[$i]['bracket_opener']) === true) {
1320
-                // Don't do this if it's the close parenthesis for the method.
1321
-                if ($i !== $this->tokens[$i]['bracket_closer']) {
1322
-                    $i = ($this->tokens[$i]['bracket_closer'] + 1);
1323
-                }
1324
-            }
1325
-
1326
-            switch ($this->tokens[$i]['code']) {
1327
-            case T_BITWISE_AND:
1328
-                if ($defaultStart === null) {
1329
-                    $passByReference = true;
1330
-                }
1331
-                break;
1332
-            case T_VARIABLE:
1333
-                $currVar = $i;
1334
-                break;
1335
-            case T_ELLIPSIS:
1336
-                $variableLength = true;
1337
-                break;
1338
-            case T_CALLABLE:
1339
-                if ($typeHintToken === false) {
1340
-                    $typeHintToken = $i;
1341
-                }
1342
-
1343
-                $typeHint .= $this->tokens[$i]['content'];
1344
-                break;
1345
-            case T_SELF:
1346
-            case T_PARENT:
1347
-            case T_STATIC:
1348
-                // Self and parent are valid, static invalid, but was probably intended as type hint.
1349
-                if (isset($defaultStart) === false) {
1350
-                    if ($typeHintToken === false) {
1351
-                        $typeHintToken = $i;
1352
-                    }
1353
-
1354
-                    $typeHint .= $this->tokens[$i]['content'];
1355
-                }
1356
-                break;
1357
-            case T_STRING:
1358
-                // This is a string, so it may be a type hint, but it could
1359
-                // also be a constant used as a default value.
1360
-                $prevComma = false;
1361
-                for ($t = $i; $t >= $opener; $t--) {
1362
-                    if ($this->tokens[$t]['code'] === T_COMMA) {
1363
-                        $prevComma = $t;
1364
-                        break;
1365
-                    }
1366
-                }
1367
-
1368
-                if ($prevComma !== false) {
1369
-                    $nextEquals = false;
1370
-                    for ($t = $prevComma; $t < $i; $t++) {
1371
-                        if ($this->tokens[$t]['code'] === T_EQUAL) {
1372
-                            $nextEquals = $t;
1373
-                            break;
1374
-                        }
1375
-                    }
1376
-
1377
-                    if ($nextEquals !== false) {
1378
-                        break;
1379
-                    }
1380
-                }
1381
-
1382
-                if ($defaultStart === null) {
1383
-                    if ($typeHintToken === false) {
1384
-                        $typeHintToken = $i;
1385
-                    }
1386
-
1387
-                    $typeHint .= $this->tokens[$i]['content'];
1388
-                }
1389
-                break;
1390
-            case T_NS_SEPARATOR:
1391
-                // Part of a type hint or default value.
1392
-                if ($defaultStart === null) {
1393
-                    if ($typeHintToken === false) {
1394
-                        $typeHintToken = $i;
1395
-                    }
1396
-
1397
-                    $typeHint .= $this->tokens[$i]['content'];
1398
-                }
1399
-                break;
1400
-            case T_NULLABLE:
1401
-                if ($defaultStart === null) {
1402
-                    $nullableType = true;
1403
-                    $typeHint    .= $this->tokens[$i]['content'];
1404
-                }
1405
-                break;
1406
-            case T_CLOSE_PARENTHESIS:
1407
-            case T_COMMA:
1408
-                // If it's null, then there must be no parameters for this
1409
-                // method.
1410
-                if ($currVar === null) {
1411
-                    continue 2;
1412
-                }
1413
-
1414
-                $vars[$paramCount]            = [];
1415
-                $vars[$paramCount]['token']   = $currVar;
1416
-                $vars[$paramCount]['name']    = $this->tokens[$currVar]['content'];
1417
-                $vars[$paramCount]['content'] = trim($this->getTokensAsString($paramStart, ($i - $paramStart)));
1418
-
1419
-                if ($defaultStart !== null) {
1420
-                    $vars[$paramCount]['default'] = trim($this->getTokensAsString($defaultStart, ($i - $defaultStart)));
1421
-                }
1422
-
1423
-                $vars[$paramCount]['pass_by_reference'] = $passByReference;
1424
-                $vars[$paramCount]['variable_length']   = $variableLength;
1425
-                $vars[$paramCount]['type_hint']         = $typeHint;
1426
-                $vars[$paramCount]['type_hint_token']   = $typeHintToken;
1427
-                $vars[$paramCount]['nullable_type']     = $nullableType;
1428
-
1429
-                // Reset the vars, as we are about to process the next parameter.
1430
-                $defaultStart    = null;
1431
-                $paramStart      = ($i + 1);
1432
-                $passByReference = false;
1433
-                $variableLength  = false;
1434
-                $typeHint        = '';
1435
-                $typeHintToken   = false;
1436
-                $nullableType    = false;
1437
-
1438
-                $paramCount++;
1439
-                break;
1440
-            case T_EQUAL:
1441
-                $defaultStart = ($i + 1);
1442
-                break;
1443
-            }//end switch
1444
-        }//end for
1445
-
1446
-        return $vars;
1447
-
1448
-    }//end getMethodParameters()
1449
-
1450
-
1451
-    /**
1452
-     * Returns the visibility and implementation properties of a method.
1453
-     *
1454
-     * The format of the array is:
1455
-     * <code>
1456
-     *   array(
1457
-     *    'scope'                => 'public', // public protected or protected
1458
-     *    'scope_specified'      => true,     // true is scope keyword was found.
1459
-     *    'return_type'          => '',       // the return type of the method.
1460
-     *    'return_type_token'    => integer,  // The stack pointer to the start of the return type
1461
-     *                                        // or false if there is no return type.
1462
-     *    'nullable_return_type' => false,    // true if the return type is nullable.
1463
-     *    'is_abstract'          => false,    // true if the abstract keyword was found.
1464
-     *    'is_final'             => false,    // true if the final keyword was found.
1465
-     *    'is_static'            => false,    // true if the static keyword was found.
1466
-     *    'has_body'             => false,    // true if the method has a body
1467
-     *   );
1468
-     * </code>
1469
-     *
1470
-     * @param int $stackPtr The position in the stack of the function token to
1471
-     *                      acquire the properties for.
1472
-     *
1473
-     * @return array
1474
-     * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
1475
-     *                                                        T_FUNCTION token.
1476
-     */
1477
-    public function getMethodProperties($stackPtr)
1478
-    {
1479
-        if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION
1480
-            && $this->tokens[$stackPtr]['code'] !== T_CLOSURE
1481
-        ) {
1482
-            throw new TokenizerException('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
1483
-        }
1484
-
1485
-        if ($this->tokens[$stackPtr]['code'] === T_FUNCTION) {
1486
-            $valid = [
1487
-                T_PUBLIC      => T_PUBLIC,
1488
-                T_PRIVATE     => T_PRIVATE,
1489
-                T_PROTECTED   => T_PROTECTED,
1490
-                T_STATIC      => T_STATIC,
1491
-                T_FINAL       => T_FINAL,
1492
-                T_ABSTRACT    => T_ABSTRACT,
1493
-                T_WHITESPACE  => T_WHITESPACE,
1494
-                T_COMMENT     => T_COMMENT,
1495
-                T_DOC_COMMENT => T_DOC_COMMENT,
1496
-            ];
1497
-        } else {
1498
-            $valid = [
1499
-                T_STATIC      => T_STATIC,
1500
-                T_WHITESPACE  => T_WHITESPACE,
1501
-                T_COMMENT     => T_COMMENT,
1502
-                T_DOC_COMMENT => T_DOC_COMMENT,
1503
-            ];
1504
-        }
1505
-
1506
-        $scope          = 'public';
1507
-        $scopeSpecified = false;
1508
-        $isAbstract     = false;
1509
-        $isFinal        = false;
1510
-        $isStatic       = false;
1511
-
1512
-        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1513
-            if (isset($valid[$this->tokens[$i]['code']]) === false) {
1514
-                break;
1515
-            }
1516
-
1517
-            switch ($this->tokens[$i]['code']) {
1518
-            case T_PUBLIC:
1519
-                $scope          = 'public';
1520
-                $scopeSpecified = true;
1521
-                break;
1522
-            case T_PRIVATE:
1523
-                $scope          = 'private';
1524
-                $scopeSpecified = true;
1525
-                break;
1526
-            case T_PROTECTED:
1527
-                $scope          = 'protected';
1528
-                $scopeSpecified = true;
1529
-                break;
1530
-            case T_ABSTRACT:
1531
-                $isAbstract = true;
1532
-                break;
1533
-            case T_FINAL:
1534
-                $isFinal = true;
1535
-                break;
1536
-            case T_STATIC:
1537
-                $isStatic = true;
1538
-                break;
1539
-            }//end switch
1540
-        }//end for
1541
-
1542
-        $returnType         = '';
1543
-        $returnTypeToken    = false;
1544
-        $nullableReturnType = false;
1545
-        $hasBody            = true;
1546
-
1547
-        if (isset($this->tokens[$stackPtr]['parenthesis_closer']) === true) {
1548
-            $scopeOpener = null;
1549
-            if (isset($this->tokens[$stackPtr]['scope_opener']) === true) {
1550
-                $scopeOpener = $this->tokens[$stackPtr]['scope_opener'];
1551
-            }
1552
-
1553
-            $valid = [
1554
-                T_STRING       => T_STRING,
1555
-                T_CALLABLE     => T_CALLABLE,
1556
-                T_SELF         => T_SELF,
1557
-                T_PARENT       => T_PARENT,
1558
-                T_NS_SEPARATOR => T_NS_SEPARATOR,
1559
-            ];
1560
-
1561
-            for ($i = $this->tokens[$stackPtr]['parenthesis_closer']; $i < $this->numTokens; $i++) {
1562
-                if (($scopeOpener === null && $this->tokens[$i]['code'] === T_SEMICOLON)
1563
-                    || ($scopeOpener !== null && $i === $scopeOpener)
1564
-                ) {
1565
-                    // End of function definition.
1566
-                    break;
1567
-                }
1568
-
1569
-                if ($this->tokens[$i]['code'] === T_NULLABLE) {
1570
-                    $nullableReturnType = true;
1571
-                }
1572
-
1573
-                if (isset($valid[$this->tokens[$i]['code']]) === true) {
1574
-                    if ($returnTypeToken === false) {
1575
-                        $returnTypeToken = $i;
1576
-                    }
1577
-
1578
-                    $returnType .= $this->tokens[$i]['content'];
1579
-                }
1580
-            }
1581
-
1582
-            $end     = $this->findNext([T_OPEN_CURLY_BRACKET, T_SEMICOLON], $this->tokens[$stackPtr]['parenthesis_closer']);
1583
-            $hasBody = $this->tokens[$end]['code'] === T_OPEN_CURLY_BRACKET;
1584
-        }//end if
1585
-
1586
-        if ($returnType !== '' && $nullableReturnType === true) {
1587
-            $returnType = '?'.$returnType;
1588
-        }
1589
-
1590
-        return [
1591
-            'scope'                => $scope,
1592
-            'scope_specified'      => $scopeSpecified,
1593
-            'return_type'          => $returnType,
1594
-            'return_type_token'    => $returnTypeToken,
1595
-            'nullable_return_type' => $nullableReturnType,
1596
-            'is_abstract'          => $isAbstract,
1597
-            'is_final'             => $isFinal,
1598
-            'is_static'            => $isStatic,
1599
-            'has_body'             => $hasBody,
1600
-        ];
1601
-
1602
-    }//end getMethodProperties()
1603
-
1604
-
1605
-    /**
1606
-     * Returns the visibility and implementation properties of the class member
1607
-     * variable found at the specified position in the stack.
1608
-     *
1609
-     * The format of the array is:
1610
-     *
1611
-     * <code>
1612
-     *   array(
1613
-     *    'scope'           => 'public', // public protected or protected.
1614
-     *    'scope_specified' => false,    // true if the scope was explicitly specified.
1615
-     *    'is_static'       => false,    // true if the static keyword was found.
1616
-     *   );
1617
-     * </code>
1618
-     *
1619
-     * @param int $stackPtr The position in the stack of the T_VARIABLE token to
1620
-     *                      acquire the properties for.
1621
-     *
1622
-     * @return array
1623
-     * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
1624
-     *                                                        T_VARIABLE token, or if the position is not
1625
-     *                                                        a class member variable.
1626
-     */
1627
-    public function getMemberProperties($stackPtr)
1628
-    {
1629
-        if ($this->tokens[$stackPtr]['code'] !== T_VARIABLE) {
1630
-            throw new TokenizerException('$stackPtr must be of type T_VARIABLE');
1631
-        }
1632
-
1633
-        $conditions = array_keys($this->tokens[$stackPtr]['conditions']);
1634
-        $ptr        = array_pop($conditions);
1635
-        if (isset($this->tokens[$ptr]) === false
1636
-            || ($this->tokens[$ptr]['code'] !== T_CLASS
1637
-            && $this->tokens[$ptr]['code'] !== T_ANON_CLASS
1638
-            && $this->tokens[$ptr]['code'] !== T_TRAIT)
1639
-        ) {
1640
-            if (isset($this->tokens[$ptr]) === true
1641
-                && $this->tokens[$ptr]['code'] === T_INTERFACE
1642
-            ) {
1643
-                // T_VARIABLEs in interfaces can actually be method arguments
1644
-                // but they wont be seen as being inside the method because there
1645
-                // are no scope openers and closers for abstract methods. If it is in
1646
-                // parentheses, we can be pretty sure it is a method argument.
1647
-                if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === false
1648
-                    || empty($this->tokens[$stackPtr]['nested_parenthesis']) === true
1649
-                ) {
1650
-                    $error = 'Possible parse error: interfaces may not include member vars';
1651
-                    $this->addWarning($error, $stackPtr, 'Internal.ParseError.InterfaceHasMemberVar');
1652
-                    return [];
1653
-                }
1654
-            } else {
1655
-                throw new TokenizerException('$stackPtr is not a class member var');
1656
-            }
1657
-        }
1658
-
1659
-        // Make sure it's not a method parameter.
1660
-        if (empty($this->tokens[$stackPtr]['nested_parenthesis']) === false) {
1661
-            $parenthesis = array_keys($this->tokens[$stackPtr]['nested_parenthesis']);
1662
-            $deepestOpen = array_pop($parenthesis);
1663
-            if ($deepestOpen > $ptr
1664
-                && isset($this->tokens[$deepestOpen]['parenthesis_owner']) === true
1665
-                && $this->tokens[$this->tokens[$deepestOpen]['parenthesis_owner']]['code'] === T_FUNCTION
1666
-            ) {
1667
-                throw new TokenizerException('$stackPtr is not a class member var');
1668
-            }
1669
-        }
1670
-
1671
-        $valid = [
1672
-            T_PUBLIC    => T_PUBLIC,
1673
-            T_PRIVATE   => T_PRIVATE,
1674
-            T_PROTECTED => T_PROTECTED,
1675
-            T_STATIC    => T_STATIC,
1676
-            T_VAR       => T_VAR,
1677
-        ];
1678
-
1679
-        $valid += Util\Tokens::$emptyTokens;
1680
-
1681
-        $scope          = 'public';
1682
-        $scopeSpecified = false;
1683
-        $isStatic       = false;
1684
-
1685
-        $startOfStatement = $this->findPrevious(
1686
-            [
1687
-                T_SEMICOLON,
1688
-                T_OPEN_CURLY_BRACKET,
1689
-                T_CLOSE_CURLY_BRACKET,
1690
-            ],
1691
-            ($stackPtr - 1)
1692
-        );
1693
-
1694
-        for ($i = ($startOfStatement + 1); $i < $stackPtr; $i++) {
1695
-            if (isset($valid[$this->tokens[$i]['code']]) === false) {
1696
-                break;
1697
-            }
1698
-
1699
-            switch ($this->tokens[$i]['code']) {
1700
-            case T_PUBLIC:
1701
-                $scope          = 'public';
1702
-                $scopeSpecified = true;
1703
-                break;
1704
-            case T_PRIVATE:
1705
-                $scope          = 'private';
1706
-                $scopeSpecified = true;
1707
-                break;
1708
-            case T_PROTECTED:
1709
-                $scope          = 'protected';
1710
-                $scopeSpecified = true;
1711
-                break;
1712
-            case T_STATIC:
1713
-                $isStatic = true;
1714
-                break;
1715
-            }
1716
-        }//end for
1717
-
1718
-        return [
1719
-            'scope'           => $scope,
1720
-            'scope_specified' => $scopeSpecified,
1721
-            'is_static'       => $isStatic,
1722
-        ];
1723
-
1724
-    }//end getMemberProperties()
1725
-
1726
-
1727
-    /**
1728
-     * Returns the visibility and implementation properties of a class.
1729
-     *
1730
-     * The format of the array is:
1731
-     * <code>
1732
-     *   array(
1733
-     *    'is_abstract' => false, // true if the abstract keyword was found.
1734
-     *    'is_final'    => false, // true if the final keyword was found.
1735
-     *   );
1736
-     * </code>
1737
-     *
1738
-     * @param int $stackPtr The position in the stack of the T_CLASS token to
1739
-     *                      acquire the properties for.
1740
-     *
1741
-     * @return array
1742
-     * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
1743
-     *                                                        T_CLASS token.
1744
-     */
1745
-    public function getClassProperties($stackPtr)
1746
-    {
1747
-        if ($this->tokens[$stackPtr]['code'] !== T_CLASS) {
1748
-            throw new TokenizerException('$stackPtr must be of type T_CLASS');
1749
-        }
1750
-
1751
-        $valid = [
1752
-            T_FINAL       => T_FINAL,
1753
-            T_ABSTRACT    => T_ABSTRACT,
1754
-            T_WHITESPACE  => T_WHITESPACE,
1755
-            T_COMMENT     => T_COMMENT,
1756
-            T_DOC_COMMENT => T_DOC_COMMENT,
1757
-        ];
1758
-
1759
-        $isAbstract = false;
1760
-        $isFinal    = false;
1761
-
1762
-        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1763
-            if (isset($valid[$this->tokens[$i]['code']]) === false) {
1764
-                break;
1765
-            }
1766
-
1767
-            switch ($this->tokens[$i]['code']) {
1768
-            case T_ABSTRACT:
1769
-                $isAbstract = true;
1770
-                break;
1771
-
1772
-            case T_FINAL:
1773
-                $isFinal = true;
1774
-                break;
1775
-            }
1776
-        }//end for
1777
-
1778
-        return [
1779
-            'is_abstract' => $isAbstract,
1780
-            'is_final'    => $isFinal,
1781
-        ];
1782
-
1783
-    }//end getClassProperties()
1784
-
1785
-
1786
-    /**
1787
-     * Determine if the passed token is a reference operator.
1788
-     *
1789
-     * Returns true if the specified token position represents a reference.
1790
-     * Returns false if the token represents a bitwise operator.
1791
-     *
1792
-     * @param int $stackPtr The position of the T_BITWISE_AND token.
1793
-     *
1794
-     * @return boolean
1795
-     */
1796
-    public function isReference($stackPtr)
1797
-    {
1798
-        if ($this->tokens[$stackPtr]['code'] !== T_BITWISE_AND) {
1799
-            return false;
1800
-        }
1801
-
1802
-        $tokenBefore = $this->findPrevious(
1803
-            Util\Tokens::$emptyTokens,
1804
-            ($stackPtr - 1),
1805
-            null,
1806
-            true
1807
-        );
1808
-
1809
-        if ($this->tokens[$tokenBefore]['code'] === T_FUNCTION) {
1810
-            // Function returns a reference.
1811
-            return true;
1812
-        }
1813
-
1814
-        if ($this->tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) {
1815
-            // Inside a foreach loop or array assignment, this is a reference.
1816
-            return true;
1817
-        }
1818
-
1819
-        if ($this->tokens[$tokenBefore]['code'] === T_AS) {
1820
-            // Inside a foreach loop, this is a reference.
1821
-            return true;
1822
-        }
1823
-
1824
-        if (isset(Util\Tokens::$assignmentTokens[$this->tokens[$tokenBefore]['code']]) === true) {
1825
-            // This is directly after an assignment. It's a reference. Even if
1826
-            // it is part of an operation, the other tests will handle it.
1827
-            return true;
1828
-        }
1829
-
1830
-        $tokenAfter = $this->findNext(
1831
-            Util\Tokens::$emptyTokens,
1832
-            ($stackPtr + 1),
1833
-            null,
1834
-            true
1835
-        );
1836
-
1837
-        if ($this->tokens[$tokenAfter]['code'] === T_NEW) {
1838
-            return true;
1839
-        }
1840
-
1841
-        if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === true) {
1842
-            $brackets    = $this->tokens[$stackPtr]['nested_parenthesis'];
1843
-            $lastBracket = array_pop($brackets);
1844
-            if (isset($this->tokens[$lastBracket]['parenthesis_owner']) === true) {
1845
-                $owner = $this->tokens[$this->tokens[$lastBracket]['parenthesis_owner']];
1846
-                if ($owner['code'] === T_FUNCTION
1847
-                    || $owner['code'] === T_CLOSURE
1848
-                ) {
1849
-                    $params = $this->getMethodParameters($this->tokens[$lastBracket]['parenthesis_owner']);
1850
-                    foreach ($params as $param) {
1851
-                        $varToken = $tokenAfter;
1852
-                        if ($param['variable_length'] === true) {
1853
-                            $varToken = $this->findNext(
1854
-                                (Util\Tokens::$emptyTokens + [T_ELLIPSIS]),
1855
-                                ($stackPtr + 1),
1856
-                                null,
1857
-                                true
1858
-                            );
1859
-                        }
1860
-
1861
-                        if ($param['token'] === $varToken
1862
-                            && $param['pass_by_reference'] === true
1863
-                        ) {
1864
-                            // Function parameter declared to be passed by reference.
1865
-                            return true;
1866
-                        }
1867
-                    }
1868
-                }//end if
1869
-            } else {
1870
-                $prev = false;
1871
-                for ($t = ($this->tokens[$lastBracket]['parenthesis_opener'] - 1); $t >= 0; $t--) {
1872
-                    if ($this->tokens[$t]['code'] !== T_WHITESPACE) {
1873
-                        $prev = $t;
1874
-                        break;
1875
-                    }
1876
-                }
1877
-
1878
-                if ($prev !== false && $this->tokens[$prev]['code'] === T_USE) {
1879
-                    // Closure use by reference.
1880
-                    return true;
1881
-                }
1882
-            }//end if
1883
-        }//end if
1884
-
1885
-        // Pass by reference in function calls and assign by reference in arrays.
1886
-        if ($this->tokens[$tokenBefore]['code'] === T_OPEN_PARENTHESIS
1887
-            || $this->tokens[$tokenBefore]['code'] === T_COMMA
1888
-            || $this->tokens[$tokenBefore]['code'] === T_OPEN_SHORT_ARRAY
1889
-        ) {
1890
-            if ($this->tokens[$tokenAfter]['code'] === T_VARIABLE) {
1891
-                return true;
1892
-            } else {
1893
-                $skip   = Util\Tokens::$emptyTokens;
1894
-                $skip[] = T_NS_SEPARATOR;
1895
-                $skip[] = T_SELF;
1896
-                $skip[] = T_PARENT;
1897
-                $skip[] = T_STATIC;
1898
-                $skip[] = T_STRING;
1899
-                $skip[] = T_NAMESPACE;
1900
-                $skip[] = T_DOUBLE_COLON;
1901
-
1902
-                $nextSignificantAfter = $this->findNext(
1903
-                    $skip,
1904
-                    ($stackPtr + 1),
1905
-                    null,
1906
-                    true
1907
-                );
1908
-                if ($this->tokens[$nextSignificantAfter]['code'] === T_VARIABLE) {
1909
-                    return true;
1910
-                }
1911
-            }//end if
1912
-        }//end if
1913
-
1914
-        return false;
1915
-
1916
-    }//end isReference()
1917
-
1918
-
1919
-    /**
1920
-     * Returns the content of the tokens from the specified start position in
1921
-     * the token stack for the specified length.
1922
-     *
1923
-     * @param int  $start       The position to start from in the token stack.
1924
-     * @param int  $length      The length of tokens to traverse from the start pos.
1925
-     * @param bool $origContent Whether the original content or the tab replaced
1926
-     *                          content should be used.
1927
-     *
1928
-     * @return string The token contents.
1929
-     */
1930
-    public function getTokensAsString($start, $length, $origContent=false)
1931
-    {
1932
-        if (is_int($start) === false || isset($this->tokens[$start]) === false) {
1933
-            throw new RuntimeException('The $start position for getTokensAsString() must exist in the token stack');
1934
-        }
1935
-
1936
-        if (is_int($length) === false || $length <= 0) {
1937
-            return '';
1938
-        }
1939
-
1940
-        $str = '';
1941
-        $end = ($start + $length);
1942
-        if ($end > $this->numTokens) {
1943
-            $end = $this->numTokens;
1944
-        }
1945
-
1946
-        for ($i = $start; $i < $end; $i++) {
1947
-            // If tabs are being converted to spaces by the tokeniser, the
1948
-            // original content should be used instead of the converted content.
1949
-            if ($origContent === true && isset($this->tokens[$i]['orig_content']) === true) {
1950
-                $str .= $this->tokens[$i]['orig_content'];
1951
-            } else {
1952
-                $str .= $this->tokens[$i]['content'];
1953
-            }
1954
-        }
1955
-
1956
-        return $str;
1957
-
1958
-    }//end getTokensAsString()
1959
-
1960
-
1961
-    /**
1962
-     * Returns the position of the previous specified token(s).
1963
-     *
1964
-     * If a value is specified, the previous token of the specified type(s)
1965
-     * containing the specified value will be returned.
1966
-     *
1967
-     * Returns false if no token can be found.
1968
-     *
1969
-     * @param int|string|array $types   The type(s) of tokens to search for.
1970
-     * @param int              $start   The position to start searching from in the
1971
-     *                                  token stack.
1972
-     * @param int              $end     The end position to fail if no token is found.
1973
-     *                                  if not specified or null, end will default to
1974
-     *                                  the start of the token stack.
1975
-     * @param bool             $exclude If true, find the previous token that is NOT of
1976
-     *                                  the types specified in $types.
1977
-     * @param string           $value   The value that the token(s) must be equal to.
1978
-     *                                  If value is omitted, tokens with any value will
1979
-     *                                  be returned.
1980
-     * @param bool             $local   If true, tokens outside the current statement
1981
-     *                                  will not be checked. IE. checking will stop
1982
-     *                                  at the previous semi-colon found.
1983
-     *
1984
-     * @return int|bool
1985
-     * @see    findNext()
1986
-     */
1987
-    public function findPrevious(
1988
-        $types,
1989
-        $start,
1990
-        $end=null,
1991
-        $exclude=false,
1992
-        $value=null,
1993
-        $local=false
1994
-    ) {
1995
-        $types = (array) $types;
1996
-
1997
-        if ($end === null) {
1998
-            $end = 0;
1999
-        }
2000
-
2001
-        for ($i = $start; $i >= $end; $i--) {
2002
-            $found = (bool) $exclude;
2003
-            foreach ($types as $type) {
2004
-                if ($this->tokens[$i]['code'] === $type) {
2005
-                    $found = !$exclude;
2006
-                    break;
2007
-                }
2008
-            }
2009
-
2010
-            if ($found === true) {
2011
-                if ($value === null) {
2012
-                    return $i;
2013
-                } else if ($this->tokens[$i]['content'] === $value) {
2014
-                    return $i;
2015
-                }
2016
-            }
2017
-
2018
-            if ($local === true) {
2019
-                if (isset($this->tokens[$i]['scope_opener']) === true
2020
-                    && $i === $this->tokens[$i]['scope_closer']
2021
-                ) {
2022
-                    $i = $this->tokens[$i]['scope_opener'];
2023
-                } else if (isset($this->tokens[$i]['bracket_opener']) === true
2024
-                    && $i === $this->tokens[$i]['bracket_closer']
2025
-                ) {
2026
-                    $i = $this->tokens[$i]['bracket_opener'];
2027
-                } else if (isset($this->tokens[$i]['parenthesis_opener']) === true
2028
-                    && $i === $this->tokens[$i]['parenthesis_closer']
2029
-                ) {
2030
-                    $i = $this->tokens[$i]['parenthesis_opener'];
2031
-                } else if ($this->tokens[$i]['code'] === T_SEMICOLON) {
2032
-                    break;
2033
-                }
2034
-            }
2035
-        }//end for
2036
-
2037
-        return false;
2038
-
2039
-    }//end findPrevious()
2040
-
2041
-
2042
-    /**
2043
-     * Returns the position of the next specified token(s).
2044
-     *
2045
-     * If a value is specified, the next token of the specified type(s)
2046
-     * containing the specified value will be returned.
2047
-     *
2048
-     * Returns false if no token can be found.
2049
-     *
2050
-     * @param int|string|array $types   The type(s) of tokens to search for.
2051
-     * @param int              $start   The position to start searching from in the
2052
-     *                                  token stack.
2053
-     * @param int              $end     The end position to fail if no token is found.
2054
-     *                                  if not specified or null, end will default to
2055
-     *                                  the end of the token stack.
2056
-     * @param bool             $exclude If true, find the next token that is NOT of
2057
-     *                                  a type specified in $types.
2058
-     * @param string           $value   The value that the token(s) must be equal to.
2059
-     *                                  If value is omitted, tokens with any value will
2060
-     *                                  be returned.
2061
-     * @param bool             $local   If true, tokens outside the current statement
2062
-     *                                  will not be checked. i.e., checking will stop
2063
-     *                                  at the next semi-colon found.
2064
-     *
2065
-     * @return int|bool
2066
-     * @see    findPrevious()
2067
-     */
2068
-    public function findNext(
2069
-        $types,
2070
-        $start,
2071
-        $end=null,
2072
-        $exclude=false,
2073
-        $value=null,
2074
-        $local=false
2075
-    ) {
2076
-        $types = (array) $types;
2077
-
2078
-        if ($end === null || $end > $this->numTokens) {
2079
-            $end = $this->numTokens;
2080
-        }
2081
-
2082
-        for ($i = $start; $i < $end; $i++) {
2083
-            $found = (bool) $exclude;
2084
-            foreach ($types as $type) {
2085
-                if ($this->tokens[$i]['code'] === $type) {
2086
-                    $found = !$exclude;
2087
-                    break;
2088
-                }
2089
-            }
2090
-
2091
-            if ($found === true) {
2092
-                if ($value === null) {
2093
-                    return $i;
2094
-                } else if ($this->tokens[$i]['content'] === $value) {
2095
-                    return $i;
2096
-                }
2097
-            }
2098
-
2099
-            if ($local === true && $this->tokens[$i]['code'] === T_SEMICOLON) {
2100
-                break;
2101
-            }
2102
-        }//end for
2103
-
2104
-        return false;
2105
-
2106
-    }//end findNext()
2107
-
2108
-
2109
-    /**
2110
-     * Returns the position of the first non-whitespace token in a statement.
2111
-     *
2112
-     * @param int       $start  The position to start searching from in the token stack.
2113
-     * @param int|array $ignore Token types that should not be considered stop points.
2114
-     *
2115
-     * @return int
2116
-     */
2117
-    public function findStartOfStatement($start, $ignore=null)
2118
-    {
2119
-        $endTokens = Util\Tokens::$blockOpeners;
2120
-
2121
-        $endTokens[T_COLON]            = true;
2122
-        $endTokens[T_COMMA]            = true;
2123
-        $endTokens[T_DOUBLE_ARROW]     = true;
2124
-        $endTokens[T_SEMICOLON]        = true;
2125
-        $endTokens[T_OPEN_TAG]         = true;
2126
-        $endTokens[T_CLOSE_TAG]        = true;
2127
-        $endTokens[T_OPEN_SHORT_ARRAY] = true;
2128
-
2129
-        if ($ignore !== null) {
2130
-            $ignore = (array) $ignore;
2131
-            foreach ($ignore as $code) {
2132
-                unset($endTokens[$code]);
2133
-            }
2134
-        }
2135
-
2136
-        $lastNotEmpty = $start;
2137
-
2138
-        for ($i = $start; $i >= 0; $i--) {
2139
-            if (isset($endTokens[$this->tokens[$i]['code']]) === true) {
2140
-                // Found the end of the previous statement.
2141
-                return $lastNotEmpty;
2142
-            }
2143
-
2144
-            if (isset($this->tokens[$i]['scope_opener']) === true
2145
-                && $i === $this->tokens[$i]['scope_closer']
2146
-            ) {
2147
-                // Found the end of the previous scope block.
2148
-                return $lastNotEmpty;
2149
-            }
2150
-
2151
-            // Skip nested statements.
2152
-            if (isset($this->tokens[$i]['bracket_opener']) === true
2153
-                && $i === $this->tokens[$i]['bracket_closer']
2154
-            ) {
2155
-                $i = $this->tokens[$i]['bracket_opener'];
2156
-            } else if (isset($this->tokens[$i]['parenthesis_opener']) === true
2157
-                && $i === $this->tokens[$i]['parenthesis_closer']
2158
-            ) {
2159
-                $i = $this->tokens[$i]['parenthesis_opener'];
2160
-            }
2161
-
2162
-            if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === false) {
2163
-                $lastNotEmpty = $i;
2164
-            }
2165
-        }//end for
2166
-
2167
-        return 0;
2168
-
2169
-    }//end findStartOfStatement()
2170
-
2171
-
2172
-    /**
2173
-     * Returns the position of the last non-whitespace token in a statement.
2174
-     *
2175
-     * @param int       $start  The position to start searching from in the token stack.
2176
-     * @param int|array $ignore Token types that should not be considered stop points.
2177
-     *
2178
-     * @return int
2179
-     */
2180
-    public function findEndOfStatement($start, $ignore=null)
2181
-    {
2182
-        $endTokens = [
2183
-            T_COLON                => true,
2184
-            T_COMMA                => true,
2185
-            T_DOUBLE_ARROW         => true,
2186
-            T_SEMICOLON            => true,
2187
-            T_CLOSE_PARENTHESIS    => true,
2188
-            T_CLOSE_SQUARE_BRACKET => true,
2189
-            T_CLOSE_CURLY_BRACKET  => true,
2190
-            T_CLOSE_SHORT_ARRAY    => true,
2191
-            T_OPEN_TAG             => true,
2192
-            T_CLOSE_TAG            => true,
2193
-        ];
2194
-
2195
-        if ($ignore !== null) {
2196
-            $ignore = (array) $ignore;
2197
-            foreach ($ignore as $code) {
2198
-                unset($endTokens[$code]);
2199
-            }
2200
-        }
2201
-
2202
-        $lastNotEmpty = $start;
2203
-
2204
-        for ($i = $start; $i < $this->numTokens; $i++) {
2205
-            if ($i !== $start && isset($endTokens[$this->tokens[$i]['code']]) === true) {
2206
-                // Found the end of the statement.
2207
-                if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
2208
-                    || $this->tokens[$i]['code'] === T_CLOSE_SQUARE_BRACKET
2209
-                    || $this->tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET
2210
-                    || $this->tokens[$i]['code'] === T_CLOSE_SHORT_ARRAY
2211
-                    || $this->tokens[$i]['code'] === T_OPEN_TAG
2212
-                    || $this->tokens[$i]['code'] === T_CLOSE_TAG
2213
-                ) {
2214
-                    return $lastNotEmpty;
2215
-                }
2216
-
2217
-                return $i;
2218
-            }
2219
-
2220
-            // Skip nested statements.
2221
-            if (isset($this->tokens[$i]['scope_closer']) === true
2222
-                && ($i === $this->tokens[$i]['scope_opener']
2223
-                || $i === $this->tokens[$i]['scope_condition'])
2224
-            ) {
2225
-                if ($i === $start && isset(Util\Tokens::$scopeOpeners[$this->tokens[$i]['code']]) === true) {
2226
-                    return $this->tokens[$i]['scope_closer'];
2227
-                }
2228
-
2229
-                $i = $this->tokens[$i]['scope_closer'];
2230
-            } else if (isset($this->tokens[$i]['bracket_closer']) === true
2231
-                && $i === $this->tokens[$i]['bracket_opener']
2232
-            ) {
2233
-                $i = $this->tokens[$i]['bracket_closer'];
2234
-            } else if (isset($this->tokens[$i]['parenthesis_closer']) === true
2235
-                && $i === $this->tokens[$i]['parenthesis_opener']
2236
-            ) {
2237
-                $i = $this->tokens[$i]['parenthesis_closer'];
2238
-            }
2239
-
2240
-            if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === false) {
2241
-                $lastNotEmpty = $i;
2242
-            }
2243
-        }//end for
2244
-
2245
-        return ($this->numTokens - 1);
2246
-
2247
-    }//end findEndOfStatement()
2248
-
2249
-
2250
-    /**
2251
-     * Returns the position of the first token on a line, matching given type.
2252
-     *
2253
-     * Returns false if no token can be found.
2254
-     *
2255
-     * @param int|string|array $types   The type(s) of tokens to search for.
2256
-     * @param int              $start   The position to start searching from in the
2257
-     *                                  token stack. The first token matching on
2258
-     *                                  this line before this token will be returned.
2259
-     * @param bool             $exclude If true, find the token that is NOT of
2260
-     *                                  the types specified in $types.
2261
-     * @param string           $value   The value that the token must be equal to.
2262
-     *                                  If value is omitted, tokens with any value will
2263
-     *                                  be returned.
2264
-     *
2265
-     * @return int | bool
2266
-     */
2267
-    public function findFirstOnLine($types, $start, $exclude=false, $value=null)
2268
-    {
2269
-        if (is_array($types) === false) {
2270
-            $types = [$types];
2271
-        }
2272
-
2273
-        $foundToken = false;
2274
-
2275
-        for ($i = $start; $i >= 0; $i--) {
2276
-            if ($this->tokens[$i]['line'] < $this->tokens[$start]['line']) {
2277
-                break;
2278
-            }
2279
-
2280
-            $found = $exclude;
2281
-            foreach ($types as $type) {
2282
-                if ($exclude === false) {
2283
-                    if ($this->tokens[$i]['code'] === $type) {
2284
-                        $found = true;
2285
-                        break;
2286
-                    }
2287
-                } else {
2288
-                    if ($this->tokens[$i]['code'] === $type) {
2289
-                        $found = false;
2290
-                        break;
2291
-                    }
2292
-                }
2293
-            }
2294
-
2295
-            if ($found === true) {
2296
-                if ($value === null) {
2297
-                    $foundToken = $i;
2298
-                } else if ($this->tokens[$i]['content'] === $value) {
2299
-                    $foundToken = $i;
2300
-                }
2301
-            }
2302
-        }//end for
2303
-
2304
-        return $foundToken;
2305
-
2306
-    }//end findFirstOnLine()
2307
-
2308
-
2309
-    /**
2310
-     * Determine if the passed token has a condition of one of the passed types.
2311
-     *
2312
-     * @param int              $stackPtr The position of the token we are checking.
2313
-     * @param int|string|array $types    The type(s) of tokens to search for.
2314
-     *
2315
-     * @return boolean
2316
-     */
2317
-    public function hasCondition($stackPtr, $types)
2318
-    {
2319
-        // Check for the existence of the token.
2320
-        if (isset($this->tokens[$stackPtr]) === false) {
2321
-            return false;
2322
-        }
2323
-
2324
-        // Make sure the token has conditions.
2325
-        if (isset($this->tokens[$stackPtr]['conditions']) === false) {
2326
-            return false;
2327
-        }
2328
-
2329
-        $types      = (array) $types;
2330
-        $conditions = $this->tokens[$stackPtr]['conditions'];
2331
-
2332
-        foreach ($types as $type) {
2333
-            if (in_array($type, $conditions, true) === true) {
2334
-                // We found a token with the required type.
2335
-                return true;
2336
-            }
2337
-        }
2338
-
2339
-        return false;
2340
-
2341
-    }//end hasCondition()
2342
-
2343
-
2344
-    /**
2345
-     * Return the position of the condition for the passed token.
2346
-     *
2347
-     * Returns FALSE if the token does not have the condition.
2348
-     *
2349
-     * @param int        $stackPtr The position of the token we are checking.
2350
-     * @param int|string $type     The type of token to search for.
2351
-     *
2352
-     * @return int
2353
-     */
2354
-    public function getCondition($stackPtr, $type)
2355
-    {
2356
-        // Check for the existence of the token.
2357
-        if (isset($this->tokens[$stackPtr]) === false) {
2358
-            return false;
2359
-        }
2360
-
2361
-        // Make sure the token has conditions.
2362
-        if (isset($this->tokens[$stackPtr]['conditions']) === false) {
2363
-            return false;
2364
-        }
2365
-
2366
-        $conditions = $this->tokens[$stackPtr]['conditions'];
2367
-        foreach ($conditions as $token => $condition) {
2368
-            if ($condition === $type) {
2369
-                return $token;
2370
-            }
2371
-        }
2372
-
2373
-        return false;
2374
-
2375
-    }//end getCondition()
2376
-
2377
-
2378
-    /**
2379
-     * Returns the name of the class that the specified class extends.
2380
-     * (works for classes, anonymous classes and interfaces)
2381
-     *
2382
-     * Returns FALSE on error or if there is no extended class name.
2383
-     *
2384
-     * @param int $stackPtr The stack position of the class.
2385
-     *
2386
-     * @return string|false
2387
-     */
2388
-    public function findExtendedClassName($stackPtr)
2389
-    {
2390
-        // Check for the existence of the token.
2391
-        if (isset($this->tokens[$stackPtr]) === false) {
2392
-            return false;
2393
-        }
2394
-
2395
-        if ($this->tokens[$stackPtr]['code'] !== T_CLASS
2396
-            && $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
2397
-            && $this->tokens[$stackPtr]['code'] !== T_INTERFACE
2398
-        ) {
2399
-            return false;
2400
-        }
2401
-
2402
-        if (isset($this->tokens[$stackPtr]['scope_opener']) === false) {
2403
-            return false;
2404
-        }
2405
-
2406
-        $classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
2407
-        $extendsIndex     = $this->findNext(T_EXTENDS, $stackPtr, $classOpenerIndex);
2408
-        if (false === $extendsIndex) {
2409
-            return false;
2410
-        }
2411
-
2412
-        $find = [
2413
-            T_NS_SEPARATOR,
2414
-            T_STRING,
2415
-            T_WHITESPACE,
2416
-        ];
2417
-
2418
-        $end  = $this->findNext($find, ($extendsIndex + 1), ($classOpenerIndex + 1), true);
2419
-        $name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1));
2420
-        $name = trim($name);
2421
-
2422
-        if ($name === '') {
2423
-            return false;
2424
-        }
2425
-
2426
-        return $name;
2427
-
2428
-    }//end findExtendedClassName()
2429
-
2430
-
2431
-    /**
2432
-     * Returns the names of the interfaces that the specified class implements.
2433
-     *
2434
-     * Returns FALSE on error or if there are no implemented interface names.
2435
-     *
2436
-     * @param int $stackPtr The stack position of the class.
2437
-     *
2438
-     * @return array|false
2439
-     */
2440
-    public function findImplementedInterfaceNames($stackPtr)
2441
-    {
2442
-        // Check for the existence of the token.
2443
-        if (isset($this->tokens[$stackPtr]) === false) {
2444
-            return false;
2445
-        }
2446
-
2447
-        if ($this->tokens[$stackPtr]['code'] !== T_CLASS
2448
-            && $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
2449
-        ) {
2450
-            return false;
2451
-        }
2452
-
2453
-        if (isset($this->tokens[$stackPtr]['scope_closer']) === false) {
2454
-            return false;
2455
-        }
2456
-
2457
-        $classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
2458
-        $implementsIndex  = $this->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
2459
-        if ($implementsIndex === false) {
2460
-            return false;
2461
-        }
2462
-
2463
-        $find = [
2464
-            T_NS_SEPARATOR,
2465
-            T_STRING,
2466
-            T_WHITESPACE,
2467
-            T_COMMA,
2468
-        ];
2469
-
2470
-        $end  = $this->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
2471
-        $name = $this->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
2472
-        $name = trim($name);
2473
-
2474
-        if ($name === '') {
2475
-            return false;
2476
-        } else {
2477
-            $names = explode(',', $name);
2478
-            $names = array_map('trim', $names);
2479
-            return $names;
2480
-        }
2481
-
2482
-    }//end findImplementedInterfaceNames()
22
+	/**
23
+	 * The absolute path to the file associated with this object.
24
+	 *
25
+	 * @var string
26
+	 */
27
+	public $path = '';
28
+
29
+	/**
30
+	 * The content of the file.
31
+	 *
32
+	 * @var string
33
+	 */
34
+	protected $content = '';
35
+
36
+	/**
37
+	 * The config data for the run.
38
+	 *
39
+	 * @var \PHP_CodeSniffer\Config
40
+	 */
41
+	public $config = null;
42
+
43
+	/**
44
+	 * The ruleset used for the run.
45
+	 *
46
+	 * @var \PHP_CodeSniffer\Ruleset
47
+	 */
48
+	public $ruleset = null;
49
+
50
+	/**
51
+	 * If TRUE, the entire file is being ignored.
52
+	 *
53
+	 * @var boolean
54
+	 */
55
+	public $ignored = false;
56
+
57
+	/**
58
+	 * The EOL character this file uses.
59
+	 *
60
+	 * @var string
61
+	 */
62
+	public $eolChar = '';
63
+
64
+	/**
65
+	 * The Fixer object to control fixing errors.
66
+	 *
67
+	 * @var \PHP_CodeSniffer\Fixer
68
+	 */
69
+	public $fixer = null;
70
+
71
+	/**
72
+	 * The tokenizer being used for this file.
73
+	 *
74
+	 * @var \PHP_CodeSniffer\Tokenizers\Tokenizer
75
+	 */
76
+	public $tokenizer = null;
77
+
78
+	/**
79
+	 * The name of the tokenizer being used for this file.
80
+	 *
81
+	 * @var string
82
+	 */
83
+	public $tokenizerType = 'PHP';
84
+
85
+	/**
86
+	 * Was the file loaded from cache?
87
+	 *
88
+	 * If TRUE, the file was loaded from a local cache.
89
+	 * If FALSE, the file was tokenized and processed fully.
90
+	 *
91
+	 * @var boolean
92
+	 */
93
+	public $fromCache = false;
94
+
95
+	/**
96
+	 * The number of tokens in this file.
97
+	 *
98
+	 * Stored here to save calling count() everywhere.
99
+	 *
100
+	 * @var integer
101
+	 */
102
+	public $numTokens = 0;
103
+
104
+	/**
105
+	 * The tokens stack map.
106
+	 *
107
+	 * @var array
108
+	 */
109
+	protected $tokens = [];
110
+
111
+	/**
112
+	 * The errors raised from sniffs.
113
+	 *
114
+	 * @var array
115
+	 * @see getErrors()
116
+	 */
117
+	protected $errors = [];
118
+
119
+	/**
120
+	 * The warnings raised from sniffs.
121
+	 *
122
+	 * @var array
123
+	 * @see getWarnings()
124
+	 */
125
+	protected $warnings = [];
126
+
127
+	/**
128
+	 * The metrics recorded by sniffs.
129
+	 *
130
+	 * @var array
131
+	 * @see getMetrics()
132
+	 */
133
+	protected $metrics = [];
134
+
135
+	/**
136
+	 * The metrics recorded for each token.
137
+	 *
138
+	 * Stops the same metric being recorded for the same token twice.
139
+	 *
140
+	 * @var array
141
+	 * @see getMetrics()
142
+	 */
143
+	private $metricTokens = [];
144
+
145
+	/**
146
+	 * The total number of errors raised.
147
+	 *
148
+	 * @var integer
149
+	 */
150
+	protected $errorCount = 0;
151
+
152
+	/**
153
+	 * The total number of warnings raised.
154
+	 *
155
+	 * @var integer
156
+	 */
157
+	protected $warningCount = 0;
158
+
159
+	/**
160
+	 * The total number of errors and warnings that can be fixed.
161
+	 *
162
+	 * @var integer
163
+	 */
164
+	protected $fixableCount = 0;
165
+
166
+	/**
167
+	 * The total number of errors and warnings that were fixed.
168
+	 *
169
+	 * @var integer
170
+	 */
171
+	protected $fixedCount = 0;
172
+
173
+	/**
174
+	 * An array of sniffs that are being ignored.
175
+	 *
176
+	 * @var array
177
+	 */
178
+	protected $ignoredListeners = [];
179
+
180
+	/**
181
+	 * An array of message codes that are being ignored.
182
+	 *
183
+	 * @var array
184
+	 */
185
+	protected $ignoredCodes = [];
186
+
187
+	/**
188
+	 * An array of sniffs listening to this file's processing.
189
+	 *
190
+	 * @var \PHP_CodeSniffer\Sniffs\Sniff[]
191
+	 */
192
+	protected $listeners = [];
193
+
194
+	/**
195
+	 * The class name of the sniff currently processing the file.
196
+	 *
197
+	 * @var string
198
+	 */
199
+	protected $activeListener = '';
200
+
201
+	/**
202
+	 * An array of sniffs being processed and how long they took.
203
+	 *
204
+	 * @var array
205
+	 */
206
+	protected $listenerTimes = [];
207
+
208
+	/**
209
+	 * A cache of often used config settings to improve performance.
210
+	 *
211
+	 * Storing them here saves 10k+ calls to __get() in the Config class.
212
+	 *
213
+	 * @var array
214
+	 */
215
+	protected $configCache = [];
216
+
217
+
218
+	/**
219
+	 * Constructs a file.
220
+	 *
221
+	 * @param string                   $path    The absolute path to the file to process.
222
+	 * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
223
+	 * @param \PHP_CodeSniffer\Config  $config  The config data for the run.
224
+	 *
225
+	 * @return void
226
+	 */
227
+	public function __construct($path, Ruleset $ruleset, Config $config)
228
+	{
229
+		$this->path    = $path;
230
+		$this->ruleset = $ruleset;
231
+		$this->config  = $config;
232
+		$this->fixer   = new Fixer();
233
+
234
+		$parts     = explode('.', $path);
235
+		$extension = array_pop($parts);
236
+		if (isset($config->extensions[$extension]) === true) {
237
+			$this->tokenizerType = $config->extensions[$extension];
238
+		} else {
239
+			// Revert to default.
240
+			$this->tokenizerType = 'PHP';
241
+		}
242
+
243
+		$this->configCache['cache']           = $this->config->cache;
244
+		$this->configCache['sniffs']          = array_map('strtolower', $this->config->sniffs);
245
+		$this->configCache['exclude']         = array_map('strtolower', $this->config->exclude);
246
+		$this->configCache['errorSeverity']   = $this->config->errorSeverity;
247
+		$this->configCache['warningSeverity'] = $this->config->warningSeverity;
248
+		$this->configCache['recordErrors']    = $this->config->recordErrors;
249
+		$this->configCache['ignorePatterns']  = $this->ruleset->ignorePatterns;
250
+		$this->configCache['includePatterns'] = $this->ruleset->includePatterns;
251
+
252
+	}//end __construct()
253
+
254
+
255
+	/**
256
+	 * Set the content of the file.
257
+	 *
258
+	 * Setting the content also calculates the EOL char being used.
259
+	 *
260
+	 * @param string $content The file content.
261
+	 *
262
+	 * @return void
263
+	 */
264
+	public function setContent($content)
265
+	{
266
+		$this->content = $content;
267
+		$this->tokens  = [];
268
+
269
+		try {
270
+			$this->eolChar = Util\Common::detectLineEndings($content);
271
+		} catch (RuntimeException $e) {
272
+			$this->addWarningOnLine($e->getMessage(), 1, 'Internal.DetectLineEndings');
273
+			return;
274
+		}
275
+
276
+	}//end setContent()
277
+
278
+
279
+	/**
280
+	 * Reloads the content of the file.
281
+	 *
282
+	 * By default, we have no idea where our content comes from,
283
+	 * so we can't do anything.
284
+	 *
285
+	 * @return void
286
+	 */
287
+	public function reloadContent()
288
+	{
289
+
290
+	}//end reloadContent()
291
+
292
+
293
+	/**
294
+	 * Disables caching of this file.
295
+	 *
296
+	 * @return void
297
+	 */
298
+	public function disableCaching()
299
+	{
300
+		$this->configCache['cache'] = false;
301
+
302
+	}//end disableCaching()
303
+
304
+
305
+	/**
306
+	 * Starts the stack traversal and tells listeners when tokens are found.
307
+	 *
308
+	 * @return void
309
+	 */
310
+	public function process()
311
+	{
312
+		if ($this->ignored === true) {
313
+			return;
314
+		}
315
+
316
+		$this->errors       = [];
317
+		$this->warnings     = [];
318
+		$this->errorCount   = 0;
319
+		$this->warningCount = 0;
320
+		$this->fixableCount = 0;
321
+
322
+		$this->parse();
323
+
324
+		// Check if tokenizer errors cause this file to be ignored.
325
+		if ($this->ignored === true) {
326
+			return;
327
+		}
328
+
329
+		$this->fixer->startFile($this);
330
+
331
+		if (PHP_CODESNIFFER_VERBOSITY > 2) {
332
+			echo "\t*** START TOKEN PROCESSING ***".PHP_EOL;
333
+		}
334
+
335
+		$foundCode        = false;
336
+		$listenerIgnoreTo = [];
337
+		$inTests          = defined('PHP_CODESNIFFER_IN_TESTS');
338
+		$checkAnnotations = $this->config->annotations;
339
+
340
+		// Foreach of the listeners that have registered to listen for this
341
+		// token, get them to process it.
342
+		foreach ($this->tokens as $stackPtr => $token) {
343
+			// Check for ignored lines.
344
+			if ($checkAnnotations === true
345
+				&& ($token['code'] === T_COMMENT
346
+				|| $token['code'] === T_PHPCS_IGNORE_FILE
347
+				|| $token['code'] === T_PHPCS_SET
348
+				|| $token['code'] === T_DOC_COMMENT_STRING
349
+				|| $token['code'] === T_DOC_COMMENT_TAG
350
+				|| ($inTests === true && $token['code'] === T_INLINE_HTML))
351
+			) {
352
+				$commentText      = ltrim($this->tokens[$stackPtr]['content'], ' /*');
353
+				$commentTextLower = strtolower($commentText);
354
+				if (strpos($commentText, '@codingStandards') !== false) {
355
+					if (strpos($commentText, '@codingStandardsIgnoreFile') !== false) {
356
+						// Ignoring the whole file, just a little late.
357
+						$this->errors       = [];
358
+						$this->warnings     = [];
359
+						$this->errorCount   = 0;
360
+						$this->warningCount = 0;
361
+						$this->fixableCount = 0;
362
+						return;
363
+					} else if (strpos($commentText, '@codingStandardsChangeSetting') !== false) {
364
+						$start   = strpos($commentText, '@codingStandardsChangeSetting');
365
+						$comment = substr($commentText, ($start + 30));
366
+						$parts   = explode(' ', $comment);
367
+						if (count($parts) >= 2) {
368
+							$sniffParts = explode('.', $parts[0]);
369
+							if (count($sniffParts) >= 3) {
370
+								// If the sniff code is not known to us, it has not been registered in this run.
371
+								// But don't throw an error as it could be there for a different standard to use.
372
+								if (isset($this->ruleset->sniffCodes[$parts[0]]) === true) {
373
+									$listenerCode  = array_shift($parts);
374
+									$propertyCode  = array_shift($parts);
375
+									$propertyValue = rtrim(implode(' ', $parts), " */\r\n");
376
+									$listenerClass = $this->ruleset->sniffCodes[$listenerCode];
377
+									$this->ruleset->setSniffProperty($listenerClass, $propertyCode, $propertyValue);
378
+								}
379
+							}
380
+						}
381
+					}//end if
382
+				} else if (substr($commentTextLower, 0, 16) === 'phpcs:ignorefile'
383
+					|| substr($commentTextLower, 0, 17) === '@phpcs:ignorefile'
384
+				) {
385
+					// Ignoring the whole file, just a little late.
386
+					$this->errors       = [];
387
+					$this->warnings     = [];
388
+					$this->errorCount   = 0;
389
+					$this->warningCount = 0;
390
+					$this->fixableCount = 0;
391
+					return;
392
+				} else if (substr($commentTextLower, 0, 9) === 'phpcs:set'
393
+					|| substr($commentTextLower, 0, 10) === '@phpcs:set'
394
+				) {
395
+					if (isset($token['sniffCode']) === true) {
396
+						$listenerCode = $token['sniffCode'];
397
+						if (isset($this->ruleset->sniffCodes[$listenerCode]) === true) {
398
+							$propertyCode  = $token['sniffProperty'];
399
+							$propertyValue = $token['sniffPropertyValue'];
400
+							$listenerClass = $this->ruleset->sniffCodes[$listenerCode];
401
+							$this->ruleset->setSniffProperty($listenerClass, $propertyCode, $propertyValue);
402
+						}
403
+					}
404
+				}//end if
405
+			}//end if
406
+
407
+			if (PHP_CODESNIFFER_VERBOSITY > 2) {
408
+				$type    = $token['type'];
409
+				$content = Util\Common::prepareForOutput($token['content']);
410
+				echo "\t\tProcess token $stackPtr: $type => $content".PHP_EOL;
411
+			}
412
+
413
+			if ($token['code'] !== T_INLINE_HTML) {
414
+				$foundCode = true;
415
+			}
416
+
417
+			if (isset($this->ruleset->tokenListeners[$token['code']]) === false) {
418
+				continue;
419
+			}
420
+
421
+			foreach ($this->ruleset->tokenListeners[$token['code']] as $listenerData) {
422
+				if (isset($this->ignoredListeners[$listenerData['class']]) === true
423
+					|| (isset($listenerIgnoreTo[$listenerData['class']]) === true
424
+					&& $listenerIgnoreTo[$listenerData['class']] > $stackPtr)
425
+				) {
426
+					// This sniff is ignoring past this token, or the whole file.
427
+					continue;
428
+				}
429
+
430
+				// Make sure this sniff supports the tokenizer
431
+				// we are currently using.
432
+				$class = $listenerData['class'];
433
+
434
+				if (isset($listenerData['tokenizers'][$this->tokenizerType]) === false) {
435
+					continue;
436
+				}
437
+
438
+				// If the file path matches one of our ignore patterns, skip it.
439
+				// While there is support for a type of each pattern
440
+				// (absolute or relative) we don't actually support it here.
441
+				foreach ($listenerData['ignore'] as $pattern) {
442
+					// We assume a / directory separator, as do the exclude rules
443
+					// most developers write, so we need a special case for any system
444
+					// that is different.
445
+					if (DIRECTORY_SEPARATOR === '\\') {
446
+						$pattern = str_replace('/', '\\\\', $pattern);
447
+					}
448
+
449
+					$pattern = '`'.$pattern.'`i';
450
+					if (preg_match($pattern, $this->path) === 1) {
451
+						$this->ignoredListeners[$class] = true;
452
+						continue(2);
453
+					}
454
+				}
455
+
456
+				// If the file path does not match one of our include patterns, skip it.
457
+				// While there is support for a type of each pattern
458
+				// (absolute or relative) we don't actually support it here.
459
+				if (empty($listenerData['include']) === false) {
460
+					$included = false;
461
+					foreach ($listenerData['include'] as $pattern) {
462
+						// We assume a / directory separator, as do the exclude rules
463
+						// most developers write, so we need a special case for any system
464
+						// that is different.
465
+						if (DIRECTORY_SEPARATOR === '\\') {
466
+							$pattern = str_replace('/', '\\\\', $pattern);
467
+						}
468
+
469
+						$pattern = '`'.$pattern.'`i';
470
+						if (preg_match($pattern, $this->path) === 1) {
471
+							$included = true;
472
+							break;
473
+						}
474
+					}
475
+
476
+					if ($included === false) {
477
+						$this->ignoredListeners[$class] = true;
478
+						continue;
479
+					}
480
+				}//end if
481
+
482
+				$this->activeListener = $class;
483
+
484
+				if (PHP_CODESNIFFER_VERBOSITY > 2) {
485
+					$startTime = microtime(true);
486
+					echo "\t\t\tProcessing ".$this->activeListener.'... ';
487
+				}
488
+
489
+				$ignoreTo = $this->ruleset->sniffs[$class]->process($this, $stackPtr);
490
+				if ($ignoreTo !== null) {
491
+					$listenerIgnoreTo[$this->activeListener] = $ignoreTo;
492
+				}
493
+
494
+				if (PHP_CODESNIFFER_VERBOSITY > 2) {
495
+					$timeTaken = (microtime(true) - $startTime);
496
+					if (isset($this->listenerTimes[$this->activeListener]) === false) {
497
+						$this->listenerTimes[$this->activeListener] = 0;
498
+					}
499
+
500
+					$this->listenerTimes[$this->activeListener] += $timeTaken;
501
+
502
+					$timeTaken = round(($timeTaken), 4);
503
+					echo "DONE in $timeTaken seconds".PHP_EOL;
504
+				}
505
+
506
+				$this->activeListener = '';
507
+			}//end foreach
508
+		}//end foreach
509
+
510
+		// If short open tags are off but the file being checked uses
511
+		// short open tags, the whole content will be inline HTML
512
+		// and nothing will be checked. So try and handle this case.
513
+		// We don't show this error for STDIN because we can't be sure the content
514
+		// actually came directly from the user. It could be something like
515
+		// refs from a Git pre-push hook.
516
+		if ($foundCode === false && $this->tokenizerType === 'PHP' && $this->path !== 'STDIN') {
517
+			$shortTags = (bool) ini_get('short_open_tag');
518
+			if ($shortTags === false) {
519
+				$error = 'No PHP code was found in this file and short open tags are not allowed by this install of PHP. This file may be using short open tags but PHP does not allow them.';
520
+				$this->addWarning($error, null, 'Internal.NoCodeFound');
521
+			}
522
+		}
523
+
524
+		if (PHP_CODESNIFFER_VERBOSITY > 2) {
525
+			echo "\t*** END TOKEN PROCESSING ***".PHP_EOL;
526
+			echo "\t*** START SNIFF PROCESSING REPORT ***".PHP_EOL;
527
+
528
+			asort($this->listenerTimes, SORT_NUMERIC);
529
+			$this->listenerTimes = array_reverse($this->listenerTimes, true);
530
+			foreach ($this->listenerTimes as $listener => $timeTaken) {
531
+				echo "\t$listener: ".round(($timeTaken), 4).' secs'.PHP_EOL;
532
+			}
533
+
534
+			echo "\t*** END SNIFF PROCESSING REPORT ***".PHP_EOL;
535
+		}
536
+
537
+		$this->fixedCount += $this->fixer->getFixCount();
538
+
539
+	}//end process()
540
+
541
+
542
+	/**
543
+	 * Tokenizes the file and prepares it for the test run.
544
+	 *
545
+	 * @return void
546
+	 */
547
+	public function parse()
548
+	{
549
+		if (empty($this->tokens) === false) {
550
+			// File has already been parsed.
551
+			return;
552
+		}
553
+
554
+		try {
555
+			$tokenizerClass  = 'PHP_CodeSniffer\Tokenizers\\'.$this->tokenizerType;
556
+			$this->tokenizer = new $tokenizerClass($this->content, $this->config, $this->eolChar);
557
+			$this->tokens    = $this->tokenizer->getTokens();
558
+		} catch (TokenizerException $e) {
559
+			$this->ignored = true;
560
+			$this->addWarning($e->getMessage(), null, 'Internal.Tokenizer.Exception');
561
+			if (PHP_CODESNIFFER_VERBOSITY > 0) {
562
+				echo "[$this->tokenizerType => tokenizer error]... ";
563
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
564
+					echo PHP_EOL;
565
+				}
566
+			}
567
+
568
+			return;
569
+		}
570
+
571
+		$this->numTokens = count($this->tokens);
572
+
573
+		// Check for mixed line endings as these can cause tokenizer errors and we
574
+		// should let the user know that the results they get may be incorrect.
575
+		// This is done by removing all backslashes, removing the newline char we
576
+		// detected, then converting newlines chars into text. If any backslashes
577
+		// are left at the end, we have additional newline chars in use.
578
+		$contents = str_replace('\\', '', $this->content);
579
+		$contents = str_replace($this->eolChar, '', $contents);
580
+		$contents = str_replace("\n", '\n', $contents);
581
+		$contents = str_replace("\r", '\r', $contents);
582
+		if (strpos($contents, '\\') !== false) {
583
+			$error = 'File has mixed line endings; this may cause incorrect results';
584
+			$this->addWarningOnLine($error, 1, 'Internal.LineEndings.Mixed');
585
+		}
586
+
587
+		if (PHP_CODESNIFFER_VERBOSITY > 0) {
588
+			if ($this->numTokens === 0) {
589
+				$numLines = 0;
590
+			} else {
591
+				$numLines = $this->tokens[($this->numTokens - 1)]['line'];
592
+			}
593
+
594
+			echo "[$this->tokenizerType => $this->numTokens tokens in $numLines lines]... ";
595
+			if (PHP_CODESNIFFER_VERBOSITY > 1) {
596
+				echo PHP_EOL;
597
+			}
598
+		}
599
+
600
+	}//end parse()
601
+
602
+
603
+	/**
604
+	 * Returns the token stack for this file.
605
+	 *
606
+	 * @return array
607
+	 */
608
+	public function getTokens()
609
+	{
610
+		return $this->tokens;
611
+
612
+	}//end getTokens()
613
+
614
+
615
+	/**
616
+	 * Remove vars stored in this file that are no longer required.
617
+	 *
618
+	 * @return void
619
+	 */
620
+	public function cleanUp()
621
+	{
622
+		$this->listenerTimes = null;
623
+		$this->content       = null;
624
+		$this->tokens        = null;
625
+		$this->metricTokens  = null;
626
+		$this->tokenizer     = null;
627
+		$this->fixer         = null;
628
+		$this->config        = null;
629
+		$this->ruleset       = null;
630
+
631
+	}//end cleanUp()
632
+
633
+
634
+	/**
635
+	 * Records an error against a specific token in the file.
636
+	 *
637
+	 * @param string  $error    The error message.
638
+	 * @param int     $stackPtr The stack position where the error occurred.
639
+	 * @param string  $code     A violation code unique to the sniff message.
640
+	 * @param array   $data     Replacements for the error message.
641
+	 * @param int     $severity The severity level for this error. A value of 0
642
+	 *                          will be converted into the default severity level.
643
+	 * @param boolean $fixable  Can the error be fixed by the sniff?
644
+	 *
645
+	 * @return boolean
646
+	 */
647
+	public function addError(
648
+		$error,
649
+		$stackPtr,
650
+		$code,
651
+		$data=[],
652
+		$severity=0,
653
+		$fixable=false
654
+	) {
655
+		if ($stackPtr === null) {
656
+			$line   = 1;
657
+			$column = 1;
658
+		} else {
659
+			$line   = $this->tokens[$stackPtr]['line'];
660
+			$column = $this->tokens[$stackPtr]['column'];
661
+		}
662
+
663
+		return $this->addMessage(true, $error, $line, $column, $code, $data, $severity, $fixable);
664
+
665
+	}//end addError()
666
+
667
+
668
+	/**
669
+	 * Records a warning against a specific token in the file.
670
+	 *
671
+	 * @param string  $warning  The error message.
672
+	 * @param int     $stackPtr The stack position where the error occurred.
673
+	 * @param string  $code     A violation code unique to the sniff message.
674
+	 * @param array   $data     Replacements for the warning message.
675
+	 * @param int     $severity The severity level for this warning. A value of 0
676
+	 *                          will be converted into the default severity level.
677
+	 * @param boolean $fixable  Can the warning be fixed by the sniff?
678
+	 *
679
+	 * @return boolean
680
+	 */
681
+	public function addWarning(
682
+		$warning,
683
+		$stackPtr,
684
+		$code,
685
+		$data=[],
686
+		$severity=0,
687
+		$fixable=false
688
+	) {
689
+		if ($stackPtr === null) {
690
+			$line   = 1;
691
+			$column = 1;
692
+		} else {
693
+			$line   = $this->tokens[$stackPtr]['line'];
694
+			$column = $this->tokens[$stackPtr]['column'];
695
+		}
696
+
697
+		return $this->addMessage(false, $warning, $line, $column, $code, $data, $severity, $fixable);
698
+
699
+	}//end addWarning()
700
+
701
+
702
+	/**
703
+	 * Records an error against a specific line in the file.
704
+	 *
705
+	 * @param string $error    The error message.
706
+	 * @param int    $line     The line on which the error occurred.
707
+	 * @param string $code     A violation code unique to the sniff message.
708
+	 * @param array  $data     Replacements for the error message.
709
+	 * @param int    $severity The severity level for this error. A value of 0
710
+	 *                         will be converted into the default severity level.
711
+	 *
712
+	 * @return boolean
713
+	 */
714
+	public function addErrorOnLine(
715
+		$error,
716
+		$line,
717
+		$code,
718
+		$data=[],
719
+		$severity=0
720
+	) {
721
+		return $this->addMessage(true, $error, $line, 1, $code, $data, $severity, false);
722
+
723
+	}//end addErrorOnLine()
724
+
725
+
726
+	/**
727
+	 * Records a warning against a specific token in the file.
728
+	 *
729
+	 * @param string $warning  The error message.
730
+	 * @param int    $line     The line on which the warning occurred.
731
+	 * @param string $code     A violation code unique to the sniff message.
732
+	 * @param array  $data     Replacements for the warning message.
733
+	 * @param int    $severity The severity level for this warning. A value of 0 will
734
+	 *                         will be converted into the default severity level.
735
+	 *
736
+	 * @return boolean
737
+	 */
738
+	public function addWarningOnLine(
739
+		$warning,
740
+		$line,
741
+		$code,
742
+		$data=[],
743
+		$severity=0
744
+	) {
745
+		return $this->addMessage(false, $warning, $line, 1, $code, $data, $severity, false);
746
+
747
+	}//end addWarningOnLine()
748
+
749
+
750
+	/**
751
+	 * Records a fixable error against a specific token in the file.
752
+	 *
753
+	 * Returns true if the error was recorded and should be fixed.
754
+	 *
755
+	 * @param string $error    The error message.
756
+	 * @param int    $stackPtr The stack position where the error occurred.
757
+	 * @param string $code     A violation code unique to the sniff message.
758
+	 * @param array  $data     Replacements for the error message.
759
+	 * @param int    $severity The severity level for this error. A value of 0
760
+	 *                         will be converted into the default severity level.
761
+	 *
762
+	 * @return boolean
763
+	 */
764
+	public function addFixableError(
765
+		$error,
766
+		$stackPtr,
767
+		$code,
768
+		$data=[],
769
+		$severity=0
770
+	) {
771
+		$recorded = $this->addError($error, $stackPtr, $code, $data, $severity, true);
772
+		if ($recorded === true && $this->fixer->enabled === true) {
773
+			return true;
774
+		}
775
+
776
+		return false;
777
+
778
+	}//end addFixableError()
779
+
780
+
781
+	/**
782
+	 * Records a fixable warning against a specific token in the file.
783
+	 *
784
+	 * Returns true if the warning was recorded and should be fixed.
785
+	 *
786
+	 * @param string $warning  The error message.
787
+	 * @param int    $stackPtr The stack position where the error occurred.
788
+	 * @param string $code     A violation code unique to the sniff message.
789
+	 * @param array  $data     Replacements for the warning message.
790
+	 * @param int    $severity The severity level for this warning. A value of 0
791
+	 *                         will be converted into the default severity level.
792
+	 *
793
+	 * @return boolean
794
+	 */
795
+	public function addFixableWarning(
796
+		$warning,
797
+		$stackPtr,
798
+		$code,
799
+		$data=[],
800
+		$severity=0
801
+	) {
802
+		$recorded = $this->addWarning($warning, $stackPtr, $code, $data, $severity, true);
803
+		if ($recorded === true && $this->fixer->enabled === true) {
804
+			return true;
805
+		}
806
+
807
+		return false;
808
+
809
+	}//end addFixableWarning()
810
+
811
+
812
+	/**
813
+	 * Adds an error to the error stack.
814
+	 *
815
+	 * @param boolean $error    Is this an error message?
816
+	 * @param string  $message  The text of the message.
817
+	 * @param int     $line     The line on which the message occurred.
818
+	 * @param int     $column   The column at which the message occurred.
819
+	 * @param string  $code     A violation code unique to the sniff message.
820
+	 * @param array   $data     Replacements for the message.
821
+	 * @param int     $severity The severity level for this message. A value of 0
822
+	 *                          will be converted into the default severity level.
823
+	 * @param boolean $fixable  Can the problem be fixed by the sniff?
824
+	 *
825
+	 * @return boolean
826
+	 */
827
+	protected function addMessage($error, $message, $line, $column, $code, $data, $severity, $fixable)
828
+	{
829
+		// Check if this line is ignoring all message codes.
830
+		if (isset($this->tokenizer->ignoredLines[$line]['.all']) === true) {
831
+			return false;
832
+		}
833
+
834
+		// Work out which sniff generated the message.
835
+		$parts = explode('.', $code);
836
+		if ($parts[0] === 'Internal') {
837
+			// An internal message.
838
+			$listenerCode = Util\Common::getSniffCode($this->activeListener);
839
+			$sniffCode    = $code;
840
+			$checkCodes   = [$sniffCode];
841
+		} else {
842
+			if ($parts[0] !== $code) {
843
+				// The full message code has been passed in.
844
+				$sniffCode    = $code;
845
+				$listenerCode = substr($sniffCode, 0, strrpos($sniffCode, '.'));
846
+			} else {
847
+				$listenerCode = Util\Common::getSniffCode($this->activeListener);
848
+				$sniffCode    = $listenerCode.'.'.$code;
849
+				$parts        = explode('.', $sniffCode);
850
+			}
851
+
852
+			$checkCodes = [
853
+				$sniffCode,
854
+				$parts[0].'.'.$parts[1].'.'.$parts[2],
855
+				$parts[0].'.'.$parts[1],
856
+				$parts[0],
857
+			];
858
+		}//end if
859
+
860
+		if (isset($this->tokenizer->ignoredLines[$line]) === true) {
861
+			// Check if this line is ignoring this specific message.
862
+			$ignored = false;
863
+			foreach ($checkCodes as $checkCode) {
864
+				if (isset($this->tokenizer->ignoredLines[$line][$checkCode]) === true) {
865
+					$ignored = true;
866
+					break;
867
+				}
868
+			}
869
+
870
+			// If it is ignored, make sure it's not whitelisted.
871
+			if ($ignored === true
872
+				&& isset($this->tokenizer->ignoredLines[$line]['.except']) === true
873
+			) {
874
+				foreach ($checkCodes as $checkCode) {
875
+					if (isset($this->tokenizer->ignoredLines[$line]['.except'][$checkCode]) === true) {
876
+						$ignored = false;
877
+						break;
878
+					}
879
+				}
880
+			}
881
+
882
+			if ($ignored === true) {
883
+				return false;
884
+			}
885
+		}//end if
886
+
887
+		$includeAll = true;
888
+		if ($this->configCache['cache'] === false
889
+			|| $this->configCache['recordErrors'] === false
890
+		) {
891
+			$includeAll = false;
892
+		}
893
+
894
+		// Filter out any messages for sniffs that shouldn't have run
895
+		// due to the use of the --sniffs command line argument.
896
+		if ($includeAll === false
897
+			&& ((empty($this->configCache['sniffs']) === false
898
+			&& in_array(strtolower($listenerCode), $this->configCache['sniffs'], true) === false)
899
+			|| (empty($this->configCache['exclude']) === false
900
+			&& in_array(strtolower($listenerCode), $this->configCache['exclude'], true) === true))
901
+		) {
902
+			return false;
903
+		}
904
+
905
+		// If we know this sniff code is being ignored for this file, return early.
906
+		foreach ($checkCodes as $checkCode) {
907
+			if (isset($this->ignoredCodes[$checkCode]) === true) {
908
+				return false;
909
+			}
910
+		}
911
+
912
+		$oppositeType = 'warning';
913
+		if ($error === false) {
914
+			$oppositeType = 'error';
915
+		}
916
+
917
+		foreach ($checkCodes as $checkCode) {
918
+			// Make sure this message type has not been set to the opposite message type.
919
+			if (isset($this->ruleset->ruleset[$checkCode]['type']) === true
920
+				&& $this->ruleset->ruleset[$checkCode]['type'] === $oppositeType
921
+			) {
922
+				$error = !$error;
923
+				break;
924
+			}
925
+		}
926
+
927
+		if ($error === true) {
928
+			$configSeverity = $this->configCache['errorSeverity'];
929
+			$messageCount   = &$this->errorCount;
930
+			$messages       = &$this->errors;
931
+		} else {
932
+			$configSeverity = $this->configCache['warningSeverity'];
933
+			$messageCount   = &$this->warningCount;
934
+			$messages       = &$this->warnings;
935
+		}
936
+
937
+		if ($includeAll === false && $configSeverity === 0) {
938
+			// Don't bother doing any processing as these messages are just going to
939
+			// be hidden in the reports anyway.
940
+			return false;
941
+		}
942
+
943
+		if ($severity === 0) {
944
+			$severity = 5;
945
+		}
946
+
947
+		foreach ($checkCodes as $checkCode) {
948
+			// Make sure we are interested in this severity level.
949
+			if (isset($this->ruleset->ruleset[$checkCode]['severity']) === true) {
950
+				$severity = $this->ruleset->ruleset[$checkCode]['severity'];
951
+				break;
952
+			}
953
+		}
954
+
955
+		if ($includeAll === false && $configSeverity > $severity) {
956
+			return false;
957
+		}
958
+
959
+		// Make sure we are not ignoring this file.
960
+		$included = null;
961
+		foreach ($checkCodes as $checkCode) {
962
+			$patterns = null;
963
+
964
+			if (isset($this->configCache['includePatterns'][$checkCode]) === true) {
965
+				$patterns  = $this->configCache['includePatterns'][$checkCode];
966
+				$excluding = false;
967
+			} else if (isset($this->configCache['ignorePatterns'][$checkCode]) === true) {
968
+				$patterns  = $this->configCache['ignorePatterns'][$checkCode];
969
+				$excluding = true;
970
+			}
971
+
972
+			if ($patterns === null) {
973
+				continue;
974
+			}
975
+
976
+			foreach ($patterns as $pattern => $type) {
977
+				// While there is support for a type of each pattern
978
+				// (absolute or relative) we don't actually support it here.
979
+				$replacements = [
980
+					'\\,' => ',',
981
+					'*'   => '.*',
982
+				];
983
+
984
+				// We assume a / directory separator, as do the exclude rules
985
+				// most developers write, so we need a special case for any system
986
+				// that is different.
987
+				if (DIRECTORY_SEPARATOR === '\\') {
988
+					$replacements['/'] = '\\\\';
989
+				}
990
+
991
+				$pattern = '`'.strtr($pattern, $replacements).'`i';
992
+				$matched = preg_match($pattern, $this->path);
993
+
994
+				if ($matched === 0) {
995
+					if ($excluding === false && $included === null) {
996
+						// This file path is not being included.
997
+						$included = false;
998
+					}
999
+
1000
+					continue;
1001
+				}
1002
+
1003
+				if ($excluding === true) {
1004
+					// This file path is being excluded.
1005
+					$this->ignoredCodes[$checkCode] = true;
1006
+					return false;
1007
+				}
1008
+
1009
+				// This file path is being included.
1010
+				$included = true;
1011
+				break;
1012
+			}//end foreach
1013
+		}//end foreach
1014
+
1015
+		if ($included === false) {
1016
+			// There were include rules set, but this file
1017
+			// path didn't match any of them.
1018
+			return false;
1019
+		}
1020
+
1021
+		$messageCount++;
1022
+		if ($fixable === true) {
1023
+			$this->fixableCount++;
1024
+		}
1025
+
1026
+		if ($this->configCache['recordErrors'] === false
1027
+			&& $includeAll === false
1028
+		) {
1029
+			return true;
1030
+		}
1031
+
1032
+		// Work out the error message.
1033
+		if (isset($this->ruleset->ruleset[$sniffCode]['message']) === true) {
1034
+			$message = $this->ruleset->ruleset[$sniffCode]['message'];
1035
+		}
1036
+
1037
+		if (empty($data) === false) {
1038
+			$message = vsprintf($message, $data);
1039
+		}
1040
+
1041
+		if (isset($messages[$line]) === false) {
1042
+			$messages[$line] = [];
1043
+		}
1044
+
1045
+		if (isset($messages[$line][$column]) === false) {
1046
+			$messages[$line][$column] = [];
1047
+		}
1048
+
1049
+		$messages[$line][$column][] = [
1050
+			'message'  => $message,
1051
+			'source'   => $sniffCode,
1052
+			'listener' => $this->activeListener,
1053
+			'severity' => $severity,
1054
+			'fixable'  => $fixable,
1055
+		];
1056
+
1057
+		if (PHP_CODESNIFFER_VERBOSITY > 1
1058
+			&& $this->fixer->enabled === true
1059
+			&& $fixable === true
1060
+		) {
1061
+			@ob_end_clean();
1062
+			echo "\tE: [Line $line] $message ($sniffCode)".PHP_EOL;
1063
+			ob_start();
1064
+		}
1065
+
1066
+		return true;
1067
+
1068
+	}//end addMessage()
1069
+
1070
+
1071
+	/**
1072
+	 * Record a metric about the file being examined.
1073
+	 *
1074
+	 * @param int    $stackPtr The stack position where the metric was recorded.
1075
+	 * @param string $metric   The name of the metric being recorded.
1076
+	 * @param string $value    The value of the metric being recorded.
1077
+	 *
1078
+	 * @return boolean
1079
+	 */
1080
+	public function recordMetric($stackPtr, $metric, $value)
1081
+	{
1082
+		if (isset($this->metrics[$metric]) === false) {
1083
+			$this->metrics[$metric] = ['values' => [$value => 1]];
1084
+			$this->metricTokens[$metric][$stackPtr] = true;
1085
+		} else if (isset($this->metricTokens[$metric][$stackPtr]) === false) {
1086
+			$this->metricTokens[$metric][$stackPtr] = true;
1087
+			if (isset($this->metrics[$metric]['values'][$value]) === false) {
1088
+				$this->metrics[$metric]['values'][$value] = 1;
1089
+			} else {
1090
+				$this->metrics[$metric]['values'][$value]++;
1091
+			}
1092
+		}
1093
+
1094
+		return true;
1095
+
1096
+	}//end recordMetric()
1097
+
1098
+
1099
+	/**
1100
+	 * Returns the number of errors raised.
1101
+	 *
1102
+	 * @return int
1103
+	 */
1104
+	public function getErrorCount()
1105
+	{
1106
+		return $this->errorCount;
1107
+
1108
+	}//end getErrorCount()
1109
+
1110
+
1111
+	/**
1112
+	 * Returns the number of warnings raised.
1113
+	 *
1114
+	 * @return int
1115
+	 */
1116
+	public function getWarningCount()
1117
+	{
1118
+		return $this->warningCount;
1119
+
1120
+	}//end getWarningCount()
1121
+
1122
+
1123
+	/**
1124
+	 * Returns the number of fixable errors/warnings raised.
1125
+	 *
1126
+	 * @return int
1127
+	 */
1128
+	public function getFixableCount()
1129
+	{
1130
+		return $this->fixableCount;
1131
+
1132
+	}//end getFixableCount()
1133
+
1134
+
1135
+	/**
1136
+	 * Returns the number of fixed errors/warnings.
1137
+	 *
1138
+	 * @return int
1139
+	 */
1140
+	public function getFixedCount()
1141
+	{
1142
+		return $this->fixedCount;
1143
+
1144
+	}//end getFixedCount()
1145
+
1146
+
1147
+	/**
1148
+	 * Returns the list of ignored lines.
1149
+	 *
1150
+	 * @return array
1151
+	 */
1152
+	public function getIgnoredLines()
1153
+	{
1154
+		return $this->tokenizer->ignoredLines;
1155
+
1156
+	}//end getIgnoredLines()
1157
+
1158
+
1159
+	/**
1160
+	 * Returns the errors raised from processing this file.
1161
+	 *
1162
+	 * @return array
1163
+	 */
1164
+	public function getErrors()
1165
+	{
1166
+		return $this->errors;
1167
+
1168
+	}//end getErrors()
1169
+
1170
+
1171
+	/**
1172
+	 * Returns the warnings raised from processing this file.
1173
+	 *
1174
+	 * @return array
1175
+	 */
1176
+	public function getWarnings()
1177
+	{
1178
+		return $this->warnings;
1179
+
1180
+	}//end getWarnings()
1181
+
1182
+
1183
+	/**
1184
+	 * Returns the metrics found while processing this file.
1185
+	 *
1186
+	 * @return array
1187
+	 */
1188
+	public function getMetrics()
1189
+	{
1190
+		return $this->metrics;
1191
+
1192
+	}//end getMetrics()
1193
+
1194
+
1195
+	/**
1196
+	 * Returns the absolute filename of this file.
1197
+	 *
1198
+	 * @return string
1199
+	 */
1200
+	public function getFilename()
1201
+	{
1202
+		return $this->path;
1203
+
1204
+	}//end getFilename()
1205
+
1206
+
1207
+	/**
1208
+	 * Returns the declaration names for classes, interfaces, traits, and functions.
1209
+	 *
1210
+	 * @param int $stackPtr The position of the declaration token which
1211
+	 *                      declared the class, interface, trait, or function.
1212
+	 *
1213
+	 * @return string|null The name of the class, interface, trait, or function;
1214
+	 *                     or NULL if the function or class is anonymous.
1215
+	 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token is not of type
1216
+	 *                                                      T_FUNCTION, T_CLASS, T_ANON_CLASS,
1217
+	 *                                                      T_CLOSURE, T_TRAIT, or T_INTERFACE.
1218
+	 */
1219
+	public function getDeclarationName($stackPtr)
1220
+	{
1221
+		$tokenCode = $this->tokens[$stackPtr]['code'];
1222
+
1223
+		if ($tokenCode === T_ANON_CLASS || $tokenCode === T_CLOSURE) {
1224
+			return null;
1225
+		}
1226
+
1227
+		if ($tokenCode !== T_FUNCTION
1228
+			&& $tokenCode !== T_CLASS
1229
+			&& $tokenCode !== T_INTERFACE
1230
+			&& $tokenCode !== T_TRAIT
1231
+		) {
1232
+			throw new RuntimeException('Token type "'.$this->tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT');
1233
+		}
1234
+
1235
+		if ($tokenCode === T_FUNCTION
1236
+			&& strtolower($this->tokens[$stackPtr]['content']) !== 'function'
1237
+		) {
1238
+			// This is a function declared without the "function" keyword.
1239
+			// So this token is the function name.
1240
+			return $this->tokens[$stackPtr]['content'];
1241
+		}
1242
+
1243
+		$content = null;
1244
+		for ($i = $stackPtr; $i < $this->numTokens; $i++) {
1245
+			if ($this->tokens[$i]['code'] === T_STRING) {
1246
+				$content = $this->tokens[$i]['content'];
1247
+				break;
1248
+			}
1249
+		}
1250
+
1251
+		return $content;
1252
+
1253
+	}//end getDeclarationName()
1254
+
1255
+
1256
+	/**
1257
+	 * Returns the method parameters for the specified function token.
1258
+	 *
1259
+	 * Each parameter is in the following format:
1260
+	 *
1261
+	 * <code>
1262
+	 *   0 => array(
1263
+	 *         'name'              => '$var',  // The variable name.
1264
+	 *         'token'             => integer, // The stack pointer to the variable name.
1265
+	 *         'content'           => string,  // The full content of the variable definition.
1266
+	 *         'pass_by_reference' => boolean, // Is the variable passed by reference?
1267
+	 *         'variable_length'   => boolean, // Is the param of variable length through use of `...` ?
1268
+	 *         'type_hint'         => string,  // The type hint for the variable.
1269
+	 *         'type_hint_token'   => integer, // The stack pointer to the type hint
1270
+	 *                                         // or false if there is no type hint.
1271
+	 *         'nullable_type'     => boolean, // Is the variable using a nullable type?
1272
+	 *        )
1273
+	 * </code>
1274
+	 *
1275
+	 * Parameters with default values have an additional array index of
1276
+	 * 'default' with the value of the default as a string.
1277
+	 *
1278
+	 * @param int $stackPtr The position in the stack of the function token
1279
+	 *                      to acquire the parameters for.
1280
+	 *
1281
+	 * @return array
1282
+	 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified $stackPtr is not of
1283
+	 *                                                        type T_FUNCTION or T_CLOSURE.
1284
+	 */
1285
+	public function getMethodParameters($stackPtr)
1286
+	{
1287
+		if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION
1288
+			&& $this->tokens[$stackPtr]['code'] !== T_CLOSURE
1289
+		) {
1290
+			throw new TokenizerException('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
1291
+		}
1292
+
1293
+		$opener = $this->tokens[$stackPtr]['parenthesis_opener'];
1294
+		$closer = $this->tokens[$stackPtr]['parenthesis_closer'];
1295
+
1296
+		$vars            = [];
1297
+		$currVar         = null;
1298
+		$paramStart      = ($opener + 1);
1299
+		$defaultStart    = null;
1300
+		$paramCount      = 0;
1301
+		$passByReference = false;
1302
+		$variableLength  = false;
1303
+		$typeHint        = '';
1304
+		$typeHintToken   = false;
1305
+		$nullableType    = false;
1306
+
1307
+		for ($i = $paramStart; $i <= $closer; $i++) {
1308
+			// Check to see if this token has a parenthesis or bracket opener. If it does
1309
+			// it's likely to be an array which might have arguments in it. This
1310
+			// could cause problems in our parsing below, so lets just skip to the
1311
+			// end of it.
1312
+			if (isset($this->tokens[$i]['parenthesis_opener']) === true) {
1313
+				// Don't do this if it's the close parenthesis for the method.
1314
+				if ($i !== $this->tokens[$i]['parenthesis_closer']) {
1315
+					$i = ($this->tokens[$i]['parenthesis_closer'] + 1);
1316
+				}
1317
+			}
1318
+
1319
+			if (isset($this->tokens[$i]['bracket_opener']) === true) {
1320
+				// Don't do this if it's the close parenthesis for the method.
1321
+				if ($i !== $this->tokens[$i]['bracket_closer']) {
1322
+					$i = ($this->tokens[$i]['bracket_closer'] + 1);
1323
+				}
1324
+			}
1325
+
1326
+			switch ($this->tokens[$i]['code']) {
1327
+			case T_BITWISE_AND:
1328
+				if ($defaultStart === null) {
1329
+					$passByReference = true;
1330
+				}
1331
+				break;
1332
+			case T_VARIABLE:
1333
+				$currVar = $i;
1334
+				break;
1335
+			case T_ELLIPSIS:
1336
+				$variableLength = true;
1337
+				break;
1338
+			case T_CALLABLE:
1339
+				if ($typeHintToken === false) {
1340
+					$typeHintToken = $i;
1341
+				}
1342
+
1343
+				$typeHint .= $this->tokens[$i]['content'];
1344
+				break;
1345
+			case T_SELF:
1346
+			case T_PARENT:
1347
+			case T_STATIC:
1348
+				// Self and parent are valid, static invalid, but was probably intended as type hint.
1349
+				if (isset($defaultStart) === false) {
1350
+					if ($typeHintToken === false) {
1351
+						$typeHintToken = $i;
1352
+					}
1353
+
1354
+					$typeHint .= $this->tokens[$i]['content'];
1355
+				}
1356
+				break;
1357
+			case T_STRING:
1358
+				// This is a string, so it may be a type hint, but it could
1359
+				// also be a constant used as a default value.
1360
+				$prevComma = false;
1361
+				for ($t = $i; $t >= $opener; $t--) {
1362
+					if ($this->tokens[$t]['code'] === T_COMMA) {
1363
+						$prevComma = $t;
1364
+						break;
1365
+					}
1366
+				}
1367
+
1368
+				if ($prevComma !== false) {
1369
+					$nextEquals = false;
1370
+					for ($t = $prevComma; $t < $i; $t++) {
1371
+						if ($this->tokens[$t]['code'] === T_EQUAL) {
1372
+							$nextEquals = $t;
1373
+							break;
1374
+						}
1375
+					}
1376
+
1377
+					if ($nextEquals !== false) {
1378
+						break;
1379
+					}
1380
+				}
1381
+
1382
+				if ($defaultStart === null) {
1383
+					if ($typeHintToken === false) {
1384
+						$typeHintToken = $i;
1385
+					}
1386
+
1387
+					$typeHint .= $this->tokens[$i]['content'];
1388
+				}
1389
+				break;
1390
+			case T_NS_SEPARATOR:
1391
+				// Part of a type hint or default value.
1392
+				if ($defaultStart === null) {
1393
+					if ($typeHintToken === false) {
1394
+						$typeHintToken = $i;
1395
+					}
1396
+
1397
+					$typeHint .= $this->tokens[$i]['content'];
1398
+				}
1399
+				break;
1400
+			case T_NULLABLE:
1401
+				if ($defaultStart === null) {
1402
+					$nullableType = true;
1403
+					$typeHint    .= $this->tokens[$i]['content'];
1404
+				}
1405
+				break;
1406
+			case T_CLOSE_PARENTHESIS:
1407
+			case T_COMMA:
1408
+				// If it's null, then there must be no parameters for this
1409
+				// method.
1410
+				if ($currVar === null) {
1411
+					continue 2;
1412
+				}
1413
+
1414
+				$vars[$paramCount]            = [];
1415
+				$vars[$paramCount]['token']   = $currVar;
1416
+				$vars[$paramCount]['name']    = $this->tokens[$currVar]['content'];
1417
+				$vars[$paramCount]['content'] = trim($this->getTokensAsString($paramStart, ($i - $paramStart)));
1418
+
1419
+				if ($defaultStart !== null) {
1420
+					$vars[$paramCount]['default'] = trim($this->getTokensAsString($defaultStart, ($i - $defaultStart)));
1421
+				}
1422
+
1423
+				$vars[$paramCount]['pass_by_reference'] = $passByReference;
1424
+				$vars[$paramCount]['variable_length']   = $variableLength;
1425
+				$vars[$paramCount]['type_hint']         = $typeHint;
1426
+				$vars[$paramCount]['type_hint_token']   = $typeHintToken;
1427
+				$vars[$paramCount]['nullable_type']     = $nullableType;
1428
+
1429
+				// Reset the vars, as we are about to process the next parameter.
1430
+				$defaultStart    = null;
1431
+				$paramStart      = ($i + 1);
1432
+				$passByReference = false;
1433
+				$variableLength  = false;
1434
+				$typeHint        = '';
1435
+				$typeHintToken   = false;
1436
+				$nullableType    = false;
1437
+
1438
+				$paramCount++;
1439
+				break;
1440
+			case T_EQUAL:
1441
+				$defaultStart = ($i + 1);
1442
+				break;
1443
+			}//end switch
1444
+		}//end for
1445
+
1446
+		return $vars;
1447
+
1448
+	}//end getMethodParameters()
1449
+
1450
+
1451
+	/**
1452
+	 * Returns the visibility and implementation properties of a method.
1453
+	 *
1454
+	 * The format of the array is:
1455
+	 * <code>
1456
+	 *   array(
1457
+	 *    'scope'                => 'public', // public protected or protected
1458
+	 *    'scope_specified'      => true,     // true is scope keyword was found.
1459
+	 *    'return_type'          => '',       // the return type of the method.
1460
+	 *    'return_type_token'    => integer,  // The stack pointer to the start of the return type
1461
+	 *                                        // or false if there is no return type.
1462
+	 *    'nullable_return_type' => false,    // true if the return type is nullable.
1463
+	 *    'is_abstract'          => false,    // true if the abstract keyword was found.
1464
+	 *    'is_final'             => false,    // true if the final keyword was found.
1465
+	 *    'is_static'            => false,    // true if the static keyword was found.
1466
+	 *    'has_body'             => false,    // true if the method has a body
1467
+	 *   );
1468
+	 * </code>
1469
+	 *
1470
+	 * @param int $stackPtr The position in the stack of the function token to
1471
+	 *                      acquire the properties for.
1472
+	 *
1473
+	 * @return array
1474
+	 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
1475
+	 *                                                        T_FUNCTION token.
1476
+	 */
1477
+	public function getMethodProperties($stackPtr)
1478
+	{
1479
+		if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION
1480
+			&& $this->tokens[$stackPtr]['code'] !== T_CLOSURE
1481
+		) {
1482
+			throw new TokenizerException('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
1483
+		}
1484
+
1485
+		if ($this->tokens[$stackPtr]['code'] === T_FUNCTION) {
1486
+			$valid = [
1487
+				T_PUBLIC      => T_PUBLIC,
1488
+				T_PRIVATE     => T_PRIVATE,
1489
+				T_PROTECTED   => T_PROTECTED,
1490
+				T_STATIC      => T_STATIC,
1491
+				T_FINAL       => T_FINAL,
1492
+				T_ABSTRACT    => T_ABSTRACT,
1493
+				T_WHITESPACE  => T_WHITESPACE,
1494
+				T_COMMENT     => T_COMMENT,
1495
+				T_DOC_COMMENT => T_DOC_COMMENT,
1496
+			];
1497
+		} else {
1498
+			$valid = [
1499
+				T_STATIC      => T_STATIC,
1500
+				T_WHITESPACE  => T_WHITESPACE,
1501
+				T_COMMENT     => T_COMMENT,
1502
+				T_DOC_COMMENT => T_DOC_COMMENT,
1503
+			];
1504
+		}
1505
+
1506
+		$scope          = 'public';
1507
+		$scopeSpecified = false;
1508
+		$isAbstract     = false;
1509
+		$isFinal        = false;
1510
+		$isStatic       = false;
1511
+
1512
+		for ($i = ($stackPtr - 1); $i > 0; $i--) {
1513
+			if (isset($valid[$this->tokens[$i]['code']]) === false) {
1514
+				break;
1515
+			}
1516
+
1517
+			switch ($this->tokens[$i]['code']) {
1518
+			case T_PUBLIC:
1519
+				$scope          = 'public';
1520
+				$scopeSpecified = true;
1521
+				break;
1522
+			case T_PRIVATE:
1523
+				$scope          = 'private';
1524
+				$scopeSpecified = true;
1525
+				break;
1526
+			case T_PROTECTED:
1527
+				$scope          = 'protected';
1528
+				$scopeSpecified = true;
1529
+				break;
1530
+			case T_ABSTRACT:
1531
+				$isAbstract = true;
1532
+				break;
1533
+			case T_FINAL:
1534
+				$isFinal = true;
1535
+				break;
1536
+			case T_STATIC:
1537
+				$isStatic = true;
1538
+				break;
1539
+			}//end switch
1540
+		}//end for
1541
+
1542
+		$returnType         = '';
1543
+		$returnTypeToken    = false;
1544
+		$nullableReturnType = false;
1545
+		$hasBody            = true;
1546
+
1547
+		if (isset($this->tokens[$stackPtr]['parenthesis_closer']) === true) {
1548
+			$scopeOpener = null;
1549
+			if (isset($this->tokens[$stackPtr]['scope_opener']) === true) {
1550
+				$scopeOpener = $this->tokens[$stackPtr]['scope_opener'];
1551
+			}
1552
+
1553
+			$valid = [
1554
+				T_STRING       => T_STRING,
1555
+				T_CALLABLE     => T_CALLABLE,
1556
+				T_SELF         => T_SELF,
1557
+				T_PARENT       => T_PARENT,
1558
+				T_NS_SEPARATOR => T_NS_SEPARATOR,
1559
+			];
1560
+
1561
+			for ($i = $this->tokens[$stackPtr]['parenthesis_closer']; $i < $this->numTokens; $i++) {
1562
+				if (($scopeOpener === null && $this->tokens[$i]['code'] === T_SEMICOLON)
1563
+					|| ($scopeOpener !== null && $i === $scopeOpener)
1564
+				) {
1565
+					// End of function definition.
1566
+					break;
1567
+				}
1568
+
1569
+				if ($this->tokens[$i]['code'] === T_NULLABLE) {
1570
+					$nullableReturnType = true;
1571
+				}
1572
+
1573
+				if (isset($valid[$this->tokens[$i]['code']]) === true) {
1574
+					if ($returnTypeToken === false) {
1575
+						$returnTypeToken = $i;
1576
+					}
1577
+
1578
+					$returnType .= $this->tokens[$i]['content'];
1579
+				}
1580
+			}
1581
+
1582
+			$end     = $this->findNext([T_OPEN_CURLY_BRACKET, T_SEMICOLON], $this->tokens[$stackPtr]['parenthesis_closer']);
1583
+			$hasBody = $this->tokens[$end]['code'] === T_OPEN_CURLY_BRACKET;
1584
+		}//end if
1585
+
1586
+		if ($returnType !== '' && $nullableReturnType === true) {
1587
+			$returnType = '?'.$returnType;
1588
+		}
1589
+
1590
+		return [
1591
+			'scope'                => $scope,
1592
+			'scope_specified'      => $scopeSpecified,
1593
+			'return_type'          => $returnType,
1594
+			'return_type_token'    => $returnTypeToken,
1595
+			'nullable_return_type' => $nullableReturnType,
1596
+			'is_abstract'          => $isAbstract,
1597
+			'is_final'             => $isFinal,
1598
+			'is_static'            => $isStatic,
1599
+			'has_body'             => $hasBody,
1600
+		];
1601
+
1602
+	}//end getMethodProperties()
1603
+
1604
+
1605
+	/**
1606
+	 * Returns the visibility and implementation properties of the class member
1607
+	 * variable found at the specified position in the stack.
1608
+	 *
1609
+	 * The format of the array is:
1610
+	 *
1611
+	 * <code>
1612
+	 *   array(
1613
+	 *    'scope'           => 'public', // public protected or protected.
1614
+	 *    'scope_specified' => false,    // true if the scope was explicitly specified.
1615
+	 *    'is_static'       => false,    // true if the static keyword was found.
1616
+	 *   );
1617
+	 * </code>
1618
+	 *
1619
+	 * @param int $stackPtr The position in the stack of the T_VARIABLE token to
1620
+	 *                      acquire the properties for.
1621
+	 *
1622
+	 * @return array
1623
+	 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
1624
+	 *                                                        T_VARIABLE token, or if the position is not
1625
+	 *                                                        a class member variable.
1626
+	 */
1627
+	public function getMemberProperties($stackPtr)
1628
+	{
1629
+		if ($this->tokens[$stackPtr]['code'] !== T_VARIABLE) {
1630
+			throw new TokenizerException('$stackPtr must be of type T_VARIABLE');
1631
+		}
1632
+
1633
+		$conditions = array_keys($this->tokens[$stackPtr]['conditions']);
1634
+		$ptr        = array_pop($conditions);
1635
+		if (isset($this->tokens[$ptr]) === false
1636
+			|| ($this->tokens[$ptr]['code'] !== T_CLASS
1637
+			&& $this->tokens[$ptr]['code'] !== T_ANON_CLASS
1638
+			&& $this->tokens[$ptr]['code'] !== T_TRAIT)
1639
+		) {
1640
+			if (isset($this->tokens[$ptr]) === true
1641
+				&& $this->tokens[$ptr]['code'] === T_INTERFACE
1642
+			) {
1643
+				// T_VARIABLEs in interfaces can actually be method arguments
1644
+				// but they wont be seen as being inside the method because there
1645
+				// are no scope openers and closers for abstract methods. If it is in
1646
+				// parentheses, we can be pretty sure it is a method argument.
1647
+				if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === false
1648
+					|| empty($this->tokens[$stackPtr]['nested_parenthesis']) === true
1649
+				) {
1650
+					$error = 'Possible parse error: interfaces may not include member vars';
1651
+					$this->addWarning($error, $stackPtr, 'Internal.ParseError.InterfaceHasMemberVar');
1652
+					return [];
1653
+				}
1654
+			} else {
1655
+				throw new TokenizerException('$stackPtr is not a class member var');
1656
+			}
1657
+		}
1658
+
1659
+		// Make sure it's not a method parameter.
1660
+		if (empty($this->tokens[$stackPtr]['nested_parenthesis']) === false) {
1661
+			$parenthesis = array_keys($this->tokens[$stackPtr]['nested_parenthesis']);
1662
+			$deepestOpen = array_pop($parenthesis);
1663
+			if ($deepestOpen > $ptr
1664
+				&& isset($this->tokens[$deepestOpen]['parenthesis_owner']) === true
1665
+				&& $this->tokens[$this->tokens[$deepestOpen]['parenthesis_owner']]['code'] === T_FUNCTION
1666
+			) {
1667
+				throw new TokenizerException('$stackPtr is not a class member var');
1668
+			}
1669
+		}
1670
+
1671
+		$valid = [
1672
+			T_PUBLIC    => T_PUBLIC,
1673
+			T_PRIVATE   => T_PRIVATE,
1674
+			T_PROTECTED => T_PROTECTED,
1675
+			T_STATIC    => T_STATIC,
1676
+			T_VAR       => T_VAR,
1677
+		];
1678
+
1679
+		$valid += Util\Tokens::$emptyTokens;
1680
+
1681
+		$scope          = 'public';
1682
+		$scopeSpecified = false;
1683
+		$isStatic       = false;
1684
+
1685
+		$startOfStatement = $this->findPrevious(
1686
+			[
1687
+				T_SEMICOLON,
1688
+				T_OPEN_CURLY_BRACKET,
1689
+				T_CLOSE_CURLY_BRACKET,
1690
+			],
1691
+			($stackPtr - 1)
1692
+		);
1693
+
1694
+		for ($i = ($startOfStatement + 1); $i < $stackPtr; $i++) {
1695
+			if (isset($valid[$this->tokens[$i]['code']]) === false) {
1696
+				break;
1697
+			}
1698
+
1699
+			switch ($this->tokens[$i]['code']) {
1700
+			case T_PUBLIC:
1701
+				$scope          = 'public';
1702
+				$scopeSpecified = true;
1703
+				break;
1704
+			case T_PRIVATE:
1705
+				$scope          = 'private';
1706
+				$scopeSpecified = true;
1707
+				break;
1708
+			case T_PROTECTED:
1709
+				$scope          = 'protected';
1710
+				$scopeSpecified = true;
1711
+				break;
1712
+			case T_STATIC:
1713
+				$isStatic = true;
1714
+				break;
1715
+			}
1716
+		}//end for
1717
+
1718
+		return [
1719
+			'scope'           => $scope,
1720
+			'scope_specified' => $scopeSpecified,
1721
+			'is_static'       => $isStatic,
1722
+		];
1723
+
1724
+	}//end getMemberProperties()
1725
+
1726
+
1727
+	/**
1728
+	 * Returns the visibility and implementation properties of a class.
1729
+	 *
1730
+	 * The format of the array is:
1731
+	 * <code>
1732
+	 *   array(
1733
+	 *    'is_abstract' => false, // true if the abstract keyword was found.
1734
+	 *    'is_final'    => false, // true if the final keyword was found.
1735
+	 *   );
1736
+	 * </code>
1737
+	 *
1738
+	 * @param int $stackPtr The position in the stack of the T_CLASS token to
1739
+	 *                      acquire the properties for.
1740
+	 *
1741
+	 * @return array
1742
+	 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
1743
+	 *                                                        T_CLASS token.
1744
+	 */
1745
+	public function getClassProperties($stackPtr)
1746
+	{
1747
+		if ($this->tokens[$stackPtr]['code'] !== T_CLASS) {
1748
+			throw new TokenizerException('$stackPtr must be of type T_CLASS');
1749
+		}
1750
+
1751
+		$valid = [
1752
+			T_FINAL       => T_FINAL,
1753
+			T_ABSTRACT    => T_ABSTRACT,
1754
+			T_WHITESPACE  => T_WHITESPACE,
1755
+			T_COMMENT     => T_COMMENT,
1756
+			T_DOC_COMMENT => T_DOC_COMMENT,
1757
+		];
1758
+
1759
+		$isAbstract = false;
1760
+		$isFinal    = false;
1761
+
1762
+		for ($i = ($stackPtr - 1); $i > 0; $i--) {
1763
+			if (isset($valid[$this->tokens[$i]['code']]) === false) {
1764
+				break;
1765
+			}
1766
+
1767
+			switch ($this->tokens[$i]['code']) {
1768
+			case T_ABSTRACT:
1769
+				$isAbstract = true;
1770
+				break;
1771
+
1772
+			case T_FINAL:
1773
+				$isFinal = true;
1774
+				break;
1775
+			}
1776
+		}//end for
1777
+
1778
+		return [
1779
+			'is_abstract' => $isAbstract,
1780
+			'is_final'    => $isFinal,
1781
+		];
1782
+
1783
+	}//end getClassProperties()
1784
+
1785
+
1786
+	/**
1787
+	 * Determine if the passed token is a reference operator.
1788
+	 *
1789
+	 * Returns true if the specified token position represents a reference.
1790
+	 * Returns false if the token represents a bitwise operator.
1791
+	 *
1792
+	 * @param int $stackPtr The position of the T_BITWISE_AND token.
1793
+	 *
1794
+	 * @return boolean
1795
+	 */
1796
+	public function isReference($stackPtr)
1797
+	{
1798
+		if ($this->tokens[$stackPtr]['code'] !== T_BITWISE_AND) {
1799
+			return false;
1800
+		}
1801
+
1802
+		$tokenBefore = $this->findPrevious(
1803
+			Util\Tokens::$emptyTokens,
1804
+			($stackPtr - 1),
1805
+			null,
1806
+			true
1807
+		);
1808
+
1809
+		if ($this->tokens[$tokenBefore]['code'] === T_FUNCTION) {
1810
+			// Function returns a reference.
1811
+			return true;
1812
+		}
1813
+
1814
+		if ($this->tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) {
1815
+			// Inside a foreach loop or array assignment, this is a reference.
1816
+			return true;
1817
+		}
1818
+
1819
+		if ($this->tokens[$tokenBefore]['code'] === T_AS) {
1820
+			// Inside a foreach loop, this is a reference.
1821
+			return true;
1822
+		}
1823
+
1824
+		if (isset(Util\Tokens::$assignmentTokens[$this->tokens[$tokenBefore]['code']]) === true) {
1825
+			// This is directly after an assignment. It's a reference. Even if
1826
+			// it is part of an operation, the other tests will handle it.
1827
+			return true;
1828
+		}
1829
+
1830
+		$tokenAfter = $this->findNext(
1831
+			Util\Tokens::$emptyTokens,
1832
+			($stackPtr + 1),
1833
+			null,
1834
+			true
1835
+		);
1836
+
1837
+		if ($this->tokens[$tokenAfter]['code'] === T_NEW) {
1838
+			return true;
1839
+		}
1840
+
1841
+		if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === true) {
1842
+			$brackets    = $this->tokens[$stackPtr]['nested_parenthesis'];
1843
+			$lastBracket = array_pop($brackets);
1844
+			if (isset($this->tokens[$lastBracket]['parenthesis_owner']) === true) {
1845
+				$owner = $this->tokens[$this->tokens[$lastBracket]['parenthesis_owner']];
1846
+				if ($owner['code'] === T_FUNCTION
1847
+					|| $owner['code'] === T_CLOSURE
1848
+				) {
1849
+					$params = $this->getMethodParameters($this->tokens[$lastBracket]['parenthesis_owner']);
1850
+					foreach ($params as $param) {
1851
+						$varToken = $tokenAfter;
1852
+						if ($param['variable_length'] === true) {
1853
+							$varToken = $this->findNext(
1854
+								(Util\Tokens::$emptyTokens + [T_ELLIPSIS]),
1855
+								($stackPtr + 1),
1856
+								null,
1857
+								true
1858
+							);
1859
+						}
1860
+
1861
+						if ($param['token'] === $varToken
1862
+							&& $param['pass_by_reference'] === true
1863
+						) {
1864
+							// Function parameter declared to be passed by reference.
1865
+							return true;
1866
+						}
1867
+					}
1868
+				}//end if
1869
+			} else {
1870
+				$prev = false;
1871
+				for ($t = ($this->tokens[$lastBracket]['parenthesis_opener'] - 1); $t >= 0; $t--) {
1872
+					if ($this->tokens[$t]['code'] !== T_WHITESPACE) {
1873
+						$prev = $t;
1874
+						break;
1875
+					}
1876
+				}
1877
+
1878
+				if ($prev !== false && $this->tokens[$prev]['code'] === T_USE) {
1879
+					// Closure use by reference.
1880
+					return true;
1881
+				}
1882
+			}//end if
1883
+		}//end if
1884
+
1885
+		// Pass by reference in function calls and assign by reference in arrays.
1886
+		if ($this->tokens[$tokenBefore]['code'] === T_OPEN_PARENTHESIS
1887
+			|| $this->tokens[$tokenBefore]['code'] === T_COMMA
1888
+			|| $this->tokens[$tokenBefore]['code'] === T_OPEN_SHORT_ARRAY
1889
+		) {
1890
+			if ($this->tokens[$tokenAfter]['code'] === T_VARIABLE) {
1891
+				return true;
1892
+			} else {
1893
+				$skip   = Util\Tokens::$emptyTokens;
1894
+				$skip[] = T_NS_SEPARATOR;
1895
+				$skip[] = T_SELF;
1896
+				$skip[] = T_PARENT;
1897
+				$skip[] = T_STATIC;
1898
+				$skip[] = T_STRING;
1899
+				$skip[] = T_NAMESPACE;
1900
+				$skip[] = T_DOUBLE_COLON;
1901
+
1902
+				$nextSignificantAfter = $this->findNext(
1903
+					$skip,
1904
+					($stackPtr + 1),
1905
+					null,
1906
+					true
1907
+				);
1908
+				if ($this->tokens[$nextSignificantAfter]['code'] === T_VARIABLE) {
1909
+					return true;
1910
+				}
1911
+			}//end if
1912
+		}//end if
1913
+
1914
+		return false;
1915
+
1916
+	}//end isReference()
1917
+
1918
+
1919
+	/**
1920
+	 * Returns the content of the tokens from the specified start position in
1921
+	 * the token stack for the specified length.
1922
+	 *
1923
+	 * @param int  $start       The position to start from in the token stack.
1924
+	 * @param int  $length      The length of tokens to traverse from the start pos.
1925
+	 * @param bool $origContent Whether the original content or the tab replaced
1926
+	 *                          content should be used.
1927
+	 *
1928
+	 * @return string The token contents.
1929
+	 */
1930
+	public function getTokensAsString($start, $length, $origContent=false)
1931
+	{
1932
+		if (is_int($start) === false || isset($this->tokens[$start]) === false) {
1933
+			throw new RuntimeException('The $start position for getTokensAsString() must exist in the token stack');
1934
+		}
1935
+
1936
+		if (is_int($length) === false || $length <= 0) {
1937
+			return '';
1938
+		}
1939
+
1940
+		$str = '';
1941
+		$end = ($start + $length);
1942
+		if ($end > $this->numTokens) {
1943
+			$end = $this->numTokens;
1944
+		}
1945
+
1946
+		for ($i = $start; $i < $end; $i++) {
1947
+			// If tabs are being converted to spaces by the tokeniser, the
1948
+			// original content should be used instead of the converted content.
1949
+			if ($origContent === true && isset($this->tokens[$i]['orig_content']) === true) {
1950
+				$str .= $this->tokens[$i]['orig_content'];
1951
+			} else {
1952
+				$str .= $this->tokens[$i]['content'];
1953
+			}
1954
+		}
1955
+
1956
+		return $str;
1957
+
1958
+	}//end getTokensAsString()
1959
+
1960
+
1961
+	/**
1962
+	 * Returns the position of the previous specified token(s).
1963
+	 *
1964
+	 * If a value is specified, the previous token of the specified type(s)
1965
+	 * containing the specified value will be returned.
1966
+	 *
1967
+	 * Returns false if no token can be found.
1968
+	 *
1969
+	 * @param int|string|array $types   The type(s) of tokens to search for.
1970
+	 * @param int              $start   The position to start searching from in the
1971
+	 *                                  token stack.
1972
+	 * @param int              $end     The end position to fail if no token is found.
1973
+	 *                                  if not specified or null, end will default to
1974
+	 *                                  the start of the token stack.
1975
+	 * @param bool             $exclude If true, find the previous token that is NOT of
1976
+	 *                                  the types specified in $types.
1977
+	 * @param string           $value   The value that the token(s) must be equal to.
1978
+	 *                                  If value is omitted, tokens with any value will
1979
+	 *                                  be returned.
1980
+	 * @param bool             $local   If true, tokens outside the current statement
1981
+	 *                                  will not be checked. IE. checking will stop
1982
+	 *                                  at the previous semi-colon found.
1983
+	 *
1984
+	 * @return int|bool
1985
+	 * @see    findNext()
1986
+	 */
1987
+	public function findPrevious(
1988
+		$types,
1989
+		$start,
1990
+		$end=null,
1991
+		$exclude=false,
1992
+		$value=null,
1993
+		$local=false
1994
+	) {
1995
+		$types = (array) $types;
1996
+
1997
+		if ($end === null) {
1998
+			$end = 0;
1999
+		}
2000
+
2001
+		for ($i = $start; $i >= $end; $i--) {
2002
+			$found = (bool) $exclude;
2003
+			foreach ($types as $type) {
2004
+				if ($this->tokens[$i]['code'] === $type) {
2005
+					$found = !$exclude;
2006
+					break;
2007
+				}
2008
+			}
2009
+
2010
+			if ($found === true) {
2011
+				if ($value === null) {
2012
+					return $i;
2013
+				} else if ($this->tokens[$i]['content'] === $value) {
2014
+					return $i;
2015
+				}
2016
+			}
2017
+
2018
+			if ($local === true) {
2019
+				if (isset($this->tokens[$i]['scope_opener']) === true
2020
+					&& $i === $this->tokens[$i]['scope_closer']
2021
+				) {
2022
+					$i = $this->tokens[$i]['scope_opener'];
2023
+				} else if (isset($this->tokens[$i]['bracket_opener']) === true
2024
+					&& $i === $this->tokens[$i]['bracket_closer']
2025
+				) {
2026
+					$i = $this->tokens[$i]['bracket_opener'];
2027
+				} else if (isset($this->tokens[$i]['parenthesis_opener']) === true
2028
+					&& $i === $this->tokens[$i]['parenthesis_closer']
2029
+				) {
2030
+					$i = $this->tokens[$i]['parenthesis_opener'];
2031
+				} else if ($this->tokens[$i]['code'] === T_SEMICOLON) {
2032
+					break;
2033
+				}
2034
+			}
2035
+		}//end for
2036
+
2037
+		return false;
2038
+
2039
+	}//end findPrevious()
2040
+
2041
+
2042
+	/**
2043
+	 * Returns the position of the next specified token(s).
2044
+	 *
2045
+	 * If a value is specified, the next token of the specified type(s)
2046
+	 * containing the specified value will be returned.
2047
+	 *
2048
+	 * Returns false if no token can be found.
2049
+	 *
2050
+	 * @param int|string|array $types   The type(s) of tokens to search for.
2051
+	 * @param int              $start   The position to start searching from in the
2052
+	 *                                  token stack.
2053
+	 * @param int              $end     The end position to fail if no token is found.
2054
+	 *                                  if not specified or null, end will default to
2055
+	 *                                  the end of the token stack.
2056
+	 * @param bool             $exclude If true, find the next token that is NOT of
2057
+	 *                                  a type specified in $types.
2058
+	 * @param string           $value   The value that the token(s) must be equal to.
2059
+	 *                                  If value is omitted, tokens with any value will
2060
+	 *                                  be returned.
2061
+	 * @param bool             $local   If true, tokens outside the current statement
2062
+	 *                                  will not be checked. i.e., checking will stop
2063
+	 *                                  at the next semi-colon found.
2064
+	 *
2065
+	 * @return int|bool
2066
+	 * @see    findPrevious()
2067
+	 */
2068
+	public function findNext(
2069
+		$types,
2070
+		$start,
2071
+		$end=null,
2072
+		$exclude=false,
2073
+		$value=null,
2074
+		$local=false
2075
+	) {
2076
+		$types = (array) $types;
2077
+
2078
+		if ($end === null || $end > $this->numTokens) {
2079
+			$end = $this->numTokens;
2080
+		}
2081
+
2082
+		for ($i = $start; $i < $end; $i++) {
2083
+			$found = (bool) $exclude;
2084
+			foreach ($types as $type) {
2085
+				if ($this->tokens[$i]['code'] === $type) {
2086
+					$found = !$exclude;
2087
+					break;
2088
+				}
2089
+			}
2090
+
2091
+			if ($found === true) {
2092
+				if ($value === null) {
2093
+					return $i;
2094
+				} else if ($this->tokens[$i]['content'] === $value) {
2095
+					return $i;
2096
+				}
2097
+			}
2098
+
2099
+			if ($local === true && $this->tokens[$i]['code'] === T_SEMICOLON) {
2100
+				break;
2101
+			}
2102
+		}//end for
2103
+
2104
+		return false;
2105
+
2106
+	}//end findNext()
2107
+
2108
+
2109
+	/**
2110
+	 * Returns the position of the first non-whitespace token in a statement.
2111
+	 *
2112
+	 * @param int       $start  The position to start searching from in the token stack.
2113
+	 * @param int|array $ignore Token types that should not be considered stop points.
2114
+	 *
2115
+	 * @return int
2116
+	 */
2117
+	public function findStartOfStatement($start, $ignore=null)
2118
+	{
2119
+		$endTokens = Util\Tokens::$blockOpeners;
2120
+
2121
+		$endTokens[T_COLON]            = true;
2122
+		$endTokens[T_COMMA]            = true;
2123
+		$endTokens[T_DOUBLE_ARROW]     = true;
2124
+		$endTokens[T_SEMICOLON]        = true;
2125
+		$endTokens[T_OPEN_TAG]         = true;
2126
+		$endTokens[T_CLOSE_TAG]        = true;
2127
+		$endTokens[T_OPEN_SHORT_ARRAY] = true;
2128
+
2129
+		if ($ignore !== null) {
2130
+			$ignore = (array) $ignore;
2131
+			foreach ($ignore as $code) {
2132
+				unset($endTokens[$code]);
2133
+			}
2134
+		}
2135
+
2136
+		$lastNotEmpty = $start;
2137
+
2138
+		for ($i = $start; $i >= 0; $i--) {
2139
+			if (isset($endTokens[$this->tokens[$i]['code']]) === true) {
2140
+				// Found the end of the previous statement.
2141
+				return $lastNotEmpty;
2142
+			}
2143
+
2144
+			if (isset($this->tokens[$i]['scope_opener']) === true
2145
+				&& $i === $this->tokens[$i]['scope_closer']
2146
+			) {
2147
+				// Found the end of the previous scope block.
2148
+				return $lastNotEmpty;
2149
+			}
2150
+
2151
+			// Skip nested statements.
2152
+			if (isset($this->tokens[$i]['bracket_opener']) === true
2153
+				&& $i === $this->tokens[$i]['bracket_closer']
2154
+			) {
2155
+				$i = $this->tokens[$i]['bracket_opener'];
2156
+			} else if (isset($this->tokens[$i]['parenthesis_opener']) === true
2157
+				&& $i === $this->tokens[$i]['parenthesis_closer']
2158
+			) {
2159
+				$i = $this->tokens[$i]['parenthesis_opener'];
2160
+			}
2161
+
2162
+			if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === false) {
2163
+				$lastNotEmpty = $i;
2164
+			}
2165
+		}//end for
2166
+
2167
+		return 0;
2168
+
2169
+	}//end findStartOfStatement()
2170
+
2171
+
2172
+	/**
2173
+	 * Returns the position of the last non-whitespace token in a statement.
2174
+	 *
2175
+	 * @param int       $start  The position to start searching from in the token stack.
2176
+	 * @param int|array $ignore Token types that should not be considered stop points.
2177
+	 *
2178
+	 * @return int
2179
+	 */
2180
+	public function findEndOfStatement($start, $ignore=null)
2181
+	{
2182
+		$endTokens = [
2183
+			T_COLON                => true,
2184
+			T_COMMA                => true,
2185
+			T_DOUBLE_ARROW         => true,
2186
+			T_SEMICOLON            => true,
2187
+			T_CLOSE_PARENTHESIS    => true,
2188
+			T_CLOSE_SQUARE_BRACKET => true,
2189
+			T_CLOSE_CURLY_BRACKET  => true,
2190
+			T_CLOSE_SHORT_ARRAY    => true,
2191
+			T_OPEN_TAG             => true,
2192
+			T_CLOSE_TAG            => true,
2193
+		];
2194
+
2195
+		if ($ignore !== null) {
2196
+			$ignore = (array) $ignore;
2197
+			foreach ($ignore as $code) {
2198
+				unset($endTokens[$code]);
2199
+			}
2200
+		}
2201
+
2202
+		$lastNotEmpty = $start;
2203
+
2204
+		for ($i = $start; $i < $this->numTokens; $i++) {
2205
+			if ($i !== $start && isset($endTokens[$this->tokens[$i]['code']]) === true) {
2206
+				// Found the end of the statement.
2207
+				if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
2208
+					|| $this->tokens[$i]['code'] === T_CLOSE_SQUARE_BRACKET
2209
+					|| $this->tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET
2210
+					|| $this->tokens[$i]['code'] === T_CLOSE_SHORT_ARRAY
2211
+					|| $this->tokens[$i]['code'] === T_OPEN_TAG
2212
+					|| $this->tokens[$i]['code'] === T_CLOSE_TAG
2213
+				) {
2214
+					return $lastNotEmpty;
2215
+				}
2216
+
2217
+				return $i;
2218
+			}
2219
+
2220
+			// Skip nested statements.
2221
+			if (isset($this->tokens[$i]['scope_closer']) === true
2222
+				&& ($i === $this->tokens[$i]['scope_opener']
2223
+				|| $i === $this->tokens[$i]['scope_condition'])
2224
+			) {
2225
+				if ($i === $start && isset(Util\Tokens::$scopeOpeners[$this->tokens[$i]['code']]) === true) {
2226
+					return $this->tokens[$i]['scope_closer'];
2227
+				}
2228
+
2229
+				$i = $this->tokens[$i]['scope_closer'];
2230
+			} else if (isset($this->tokens[$i]['bracket_closer']) === true
2231
+				&& $i === $this->tokens[$i]['bracket_opener']
2232
+			) {
2233
+				$i = $this->tokens[$i]['bracket_closer'];
2234
+			} else if (isset($this->tokens[$i]['parenthesis_closer']) === true
2235
+				&& $i === $this->tokens[$i]['parenthesis_opener']
2236
+			) {
2237
+				$i = $this->tokens[$i]['parenthesis_closer'];
2238
+			}
2239
+
2240
+			if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === false) {
2241
+				$lastNotEmpty = $i;
2242
+			}
2243
+		}//end for
2244
+
2245
+		return ($this->numTokens - 1);
2246
+
2247
+	}//end findEndOfStatement()
2248
+
2249
+
2250
+	/**
2251
+	 * Returns the position of the first token on a line, matching given type.
2252
+	 *
2253
+	 * Returns false if no token can be found.
2254
+	 *
2255
+	 * @param int|string|array $types   The type(s) of tokens to search for.
2256
+	 * @param int              $start   The position to start searching from in the
2257
+	 *                                  token stack. The first token matching on
2258
+	 *                                  this line before this token will be returned.
2259
+	 * @param bool             $exclude If true, find the token that is NOT of
2260
+	 *                                  the types specified in $types.
2261
+	 * @param string           $value   The value that the token must be equal to.
2262
+	 *                                  If value is omitted, tokens with any value will
2263
+	 *                                  be returned.
2264
+	 *
2265
+	 * @return int | bool
2266
+	 */
2267
+	public function findFirstOnLine($types, $start, $exclude=false, $value=null)
2268
+	{
2269
+		if (is_array($types) === false) {
2270
+			$types = [$types];
2271
+		}
2272
+
2273
+		$foundToken = false;
2274
+
2275
+		for ($i = $start; $i >= 0; $i--) {
2276
+			if ($this->tokens[$i]['line'] < $this->tokens[$start]['line']) {
2277
+				break;
2278
+			}
2279
+
2280
+			$found = $exclude;
2281
+			foreach ($types as $type) {
2282
+				if ($exclude === false) {
2283
+					if ($this->tokens[$i]['code'] === $type) {
2284
+						$found = true;
2285
+						break;
2286
+					}
2287
+				} else {
2288
+					if ($this->tokens[$i]['code'] === $type) {
2289
+						$found = false;
2290
+						break;
2291
+					}
2292
+				}
2293
+			}
2294
+
2295
+			if ($found === true) {
2296
+				if ($value === null) {
2297
+					$foundToken = $i;
2298
+				} else if ($this->tokens[$i]['content'] === $value) {
2299
+					$foundToken = $i;
2300
+				}
2301
+			}
2302
+		}//end for
2303
+
2304
+		return $foundToken;
2305
+
2306
+	}//end findFirstOnLine()
2307
+
2308
+
2309
+	/**
2310
+	 * Determine if the passed token has a condition of one of the passed types.
2311
+	 *
2312
+	 * @param int              $stackPtr The position of the token we are checking.
2313
+	 * @param int|string|array $types    The type(s) of tokens to search for.
2314
+	 *
2315
+	 * @return boolean
2316
+	 */
2317
+	public function hasCondition($stackPtr, $types)
2318
+	{
2319
+		// Check for the existence of the token.
2320
+		if (isset($this->tokens[$stackPtr]) === false) {
2321
+			return false;
2322
+		}
2323
+
2324
+		// Make sure the token has conditions.
2325
+		if (isset($this->tokens[$stackPtr]['conditions']) === false) {
2326
+			return false;
2327
+		}
2328
+
2329
+		$types      = (array) $types;
2330
+		$conditions = $this->tokens[$stackPtr]['conditions'];
2331
+
2332
+		foreach ($types as $type) {
2333
+			if (in_array($type, $conditions, true) === true) {
2334
+				// We found a token with the required type.
2335
+				return true;
2336
+			}
2337
+		}
2338
+
2339
+		return false;
2340
+
2341
+	}//end hasCondition()
2342
+
2343
+
2344
+	/**
2345
+	 * Return the position of the condition for the passed token.
2346
+	 *
2347
+	 * Returns FALSE if the token does not have the condition.
2348
+	 *
2349
+	 * @param int        $stackPtr The position of the token we are checking.
2350
+	 * @param int|string $type     The type of token to search for.
2351
+	 *
2352
+	 * @return int
2353
+	 */
2354
+	public function getCondition($stackPtr, $type)
2355
+	{
2356
+		// Check for the existence of the token.
2357
+		if (isset($this->tokens[$stackPtr]) === false) {
2358
+			return false;
2359
+		}
2360
+
2361
+		// Make sure the token has conditions.
2362
+		if (isset($this->tokens[$stackPtr]['conditions']) === false) {
2363
+			return false;
2364
+		}
2365
+
2366
+		$conditions = $this->tokens[$stackPtr]['conditions'];
2367
+		foreach ($conditions as $token => $condition) {
2368
+			if ($condition === $type) {
2369
+				return $token;
2370
+			}
2371
+		}
2372
+
2373
+		return false;
2374
+
2375
+	}//end getCondition()
2376
+
2377
+
2378
+	/**
2379
+	 * Returns the name of the class that the specified class extends.
2380
+	 * (works for classes, anonymous classes and interfaces)
2381
+	 *
2382
+	 * Returns FALSE on error or if there is no extended class name.
2383
+	 *
2384
+	 * @param int $stackPtr The stack position of the class.
2385
+	 *
2386
+	 * @return string|false
2387
+	 */
2388
+	public function findExtendedClassName($stackPtr)
2389
+	{
2390
+		// Check for the existence of the token.
2391
+		if (isset($this->tokens[$stackPtr]) === false) {
2392
+			return false;
2393
+		}
2394
+
2395
+		if ($this->tokens[$stackPtr]['code'] !== T_CLASS
2396
+			&& $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
2397
+			&& $this->tokens[$stackPtr]['code'] !== T_INTERFACE
2398
+		) {
2399
+			return false;
2400
+		}
2401
+
2402
+		if (isset($this->tokens[$stackPtr]['scope_opener']) === false) {
2403
+			return false;
2404
+		}
2405
+
2406
+		$classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
2407
+		$extendsIndex     = $this->findNext(T_EXTENDS, $stackPtr, $classOpenerIndex);
2408
+		if (false === $extendsIndex) {
2409
+			return false;
2410
+		}
2411
+
2412
+		$find = [
2413
+			T_NS_SEPARATOR,
2414
+			T_STRING,
2415
+			T_WHITESPACE,
2416
+		];
2417
+
2418
+		$end  = $this->findNext($find, ($extendsIndex + 1), ($classOpenerIndex + 1), true);
2419
+		$name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1));
2420
+		$name = trim($name);
2421
+
2422
+		if ($name === '') {
2423
+			return false;
2424
+		}
2425
+
2426
+		return $name;
2427
+
2428
+	}//end findExtendedClassName()
2429
+
2430
+
2431
+	/**
2432
+	 * Returns the names of the interfaces that the specified class implements.
2433
+	 *
2434
+	 * Returns FALSE on error or if there are no implemented interface names.
2435
+	 *
2436
+	 * @param int $stackPtr The stack position of the class.
2437
+	 *
2438
+	 * @return array|false
2439
+	 */
2440
+	public function findImplementedInterfaceNames($stackPtr)
2441
+	{
2442
+		// Check for the existence of the token.
2443
+		if (isset($this->tokens[$stackPtr]) === false) {
2444
+			return false;
2445
+		}
2446
+
2447
+		if ($this->tokens[$stackPtr]['code'] !== T_CLASS
2448
+			&& $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
2449
+		) {
2450
+			return false;
2451
+		}
2452
+
2453
+		if (isset($this->tokens[$stackPtr]['scope_closer']) === false) {
2454
+			return false;
2455
+		}
2456
+
2457
+		$classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
2458
+		$implementsIndex  = $this->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
2459
+		if ($implementsIndex === false) {
2460
+			return false;
2461
+		}
2462
+
2463
+		$find = [
2464
+			T_NS_SEPARATOR,
2465
+			T_STRING,
2466
+			T_WHITESPACE,
2467
+			T_COMMA,
2468
+		];
2469
+
2470
+		$end  = $this->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
2471
+		$name = $this->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
2472
+		$name = trim($name);
2473
+
2474
+		if ($name === '') {
2475
+			return false;
2476
+		} else {
2477
+			$names = explode(',', $name);
2478
+			$names = array_map('trim', $names);
2479
+			return $names;
2480
+		}
2481
+
2482
+	}//end findImplementedInterfaceNames()
2483 2483
 
2484 2484
 
2485 2485
 }//end class
Please login to merge, or discard this patch.
vendor/squizlabs/php_codesniffer/src/Files/FileList.php 1 patch
Indentation   +222 added lines, -222 removed lines patch added patch discarded remove patch
@@ -20,228 +20,228 @@
 block discarded – undo
20 20
 class FileList implements \Iterator, \Countable
21 21
 {
22 22
 
23
-    /**
24
-     * A list of file paths that are included in the list.
25
-     *
26
-     * @var array
27
-     */
28
-    private $files = [];
29
-
30
-    /**
31
-     * The number of files in the list.
32
-     *
33
-     * @var integer
34
-     */
35
-    private $numFiles = 0;
36
-
37
-    /**
38
-     * The config data for the run.
39
-     *
40
-     * @var \PHP_CodeSniffer\Config
41
-     */
42
-    public $config = null;
43
-
44
-    /**
45
-     * The ruleset used for the run.
46
-     *
47
-     * @var \PHP_CodeSniffer\Ruleset
48
-     */
49
-    public $ruleset = null;
50
-
51
-    /**
52
-     * An array of patterns to use for skipping files.
53
-     *
54
-     * @var array
55
-     */
56
-    protected $ignorePatterns = [];
57
-
58
-
59
-    /**
60
-     * Constructs a file list and loads in an array of file paths to process.
61
-     *
62
-     * @param \PHP_CodeSniffer\Config  $config  The config data for the run.
63
-     * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
64
-     *
65
-     * @return void
66
-     */
67
-    public function __construct(Config $config, Ruleset $ruleset)
68
-    {
69
-        $this->ruleset = $ruleset;
70
-        $this->config  = $config;
71
-
72
-        $paths = $config->files;
73
-        foreach ($paths as $path) {
74
-            $isPharFile = Util\Common::isPharFile($path);
75
-            if (is_dir($path) === true || $isPharFile === true) {
76
-                if ($isPharFile === true) {
77
-                    $path = 'phar://'.$path;
78
-                }
79
-
80
-                $filterClass = $this->getFilterClass();
81
-
82
-                $di       = new \RecursiveDirectoryIterator($path, (\RecursiveDirectoryIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS));
83
-                $filter   = new $filterClass($di, $path, $config, $ruleset);
84
-                $iterator = new \RecursiveIteratorIterator($filter);
85
-
86
-                foreach ($iterator as $file) {
87
-                    $this->files[$file->getPathname()] = null;
88
-                    $this->numFiles++;
89
-                }
90
-            } else {
91
-                $this->addFile($path);
92
-            }//end if
93
-        }//end foreach
94
-
95
-        reset($this->files);
96
-
97
-    }//end __construct()
98
-
99
-
100
-    /**
101
-     * Add a file to the list.
102
-     *
103
-     * If a file object has already been created, it can be passed here.
104
-     * If it is left NULL, it will be created when accessed.
105
-     *
106
-     * @param string                      $path The path to the file being added.
107
-     * @param \PHP_CodeSniffer\Files\File $file The file being added.
108
-     *
109
-     * @return void
110
-     */
111
-    public function addFile($path, $file=null)
112
-    {
113
-        // No filtering is done for STDIN when the filename
114
-        // has not been specified.
115
-        if ($path === 'STDIN') {
116
-            $this->files[$path] = $file;
117
-            $this->numFiles++;
118
-            return;
119
-        }
120
-
121
-        $filterClass = $this->getFilterClass();
122
-
123
-        $di       = new \RecursiveArrayIterator([$path]);
124
-        $filter   = new $filterClass($di, $path, $this->config, $this->ruleset);
125
-        $iterator = new \RecursiveIteratorIterator($filter);
126
-
127
-        foreach ($iterator as $path) {
128
-            $this->files[$path] = $file;
129
-            $this->numFiles++;
130
-        }
131
-
132
-    }//end addFile()
133
-
134
-
135
-    /**
136
-     * Get the class name of the filter being used for the run.
137
-     *
138
-     * @return string
139
-     */
140
-    private function getFilterClass()
141
-    {
142
-        $filterType = $this->config->filter;
143
-
144
-        if ($filterType === null) {
145
-            $filterClass = '\PHP_CodeSniffer\Filters\Filter';
146
-        } else {
147
-            if (strpos($filterType, '.') !== false) {
148
-                // This is a path to a custom filter class.
149
-                $filename = realpath($filterType);
150
-                if ($filename === false) {
151
-                    $error = "ERROR: Custom filter \"$filterType\" not found".PHP_EOL;
152
-                    throw new DeepExitException($error, 3);
153
-                }
154
-
155
-                $filterClass = Autoload::loadFile($filename);
156
-            } else {
157
-                $filterClass = '\PHP_CodeSniffer\Filters\\'.$filterType;
158
-            }
159
-        }
160
-
161
-        return $filterClass;
162
-
163
-    }//end getFilterClass()
164
-
165
-
166
-    /**
167
-     * Rewind the iterator to the first file.
168
-     *
169
-     * @return void
170
-     */
171
-    public function rewind()
172
-    {
173
-        reset($this->files);
174
-
175
-    }//end rewind()
176
-
177
-
178
-    /**
179
-     * Get the file that is currently being processed.
180
-     *
181
-     * @return \PHP_CodeSniffer\Files\File
182
-     */
183
-    public function current()
184
-    {
185
-        $path = key($this->files);
186
-        if ($this->files[$path] === null) {
187
-            $this->files[$path] = new LocalFile($path, $this->ruleset, $this->config);
188
-        }
189
-
190
-        return $this->files[$path];
191
-
192
-    }//end current()
193
-
194
-
195
-    /**
196
-     * Return the file path of the current file being processed.
197
-     *
198
-     * @return void
199
-     */
200
-    public function key()
201
-    {
202
-        return key($this->files);
203
-
204
-    }//end key()
205
-
206
-
207
-    /**
208
-     * Move forward to the next file.
209
-     *
210
-     * @return void
211
-     */
212
-    public function next()
213
-    {
214
-        next($this->files);
215
-
216
-    }//end next()
217
-
218
-
219
-    /**
220
-     * Checks if current position is valid.
221
-     *
222
-     * @return boolean
223
-     */
224
-    public function valid()
225
-    {
226
-        if (current($this->files) === false) {
227
-            return false;
228
-        }
229
-
230
-        return true;
231
-
232
-    }//end valid()
233
-
234
-
235
-    /**
236
-     * Return the number of files in the list.
237
-     *
238
-     * @return integer
239
-     */
240
-    public function count()
241
-    {
242
-        return $this->numFiles;
243
-
244
-    }//end count()
23
+	/**
24
+	 * A list of file paths that are included in the list.
25
+	 *
26
+	 * @var array
27
+	 */
28
+	private $files = [];
29
+
30
+	/**
31
+	 * The number of files in the list.
32
+	 *
33
+	 * @var integer
34
+	 */
35
+	private $numFiles = 0;
36
+
37
+	/**
38
+	 * The config data for the run.
39
+	 *
40
+	 * @var \PHP_CodeSniffer\Config
41
+	 */
42
+	public $config = null;
43
+
44
+	/**
45
+	 * The ruleset used for the run.
46
+	 *
47
+	 * @var \PHP_CodeSniffer\Ruleset
48
+	 */
49
+	public $ruleset = null;
50
+
51
+	/**
52
+	 * An array of patterns to use for skipping files.
53
+	 *
54
+	 * @var array
55
+	 */
56
+	protected $ignorePatterns = [];
57
+
58
+
59
+	/**
60
+	 * Constructs a file list and loads in an array of file paths to process.
61
+	 *
62
+	 * @param \PHP_CodeSniffer\Config  $config  The config data for the run.
63
+	 * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
64
+	 *
65
+	 * @return void
66
+	 */
67
+	public function __construct(Config $config, Ruleset $ruleset)
68
+	{
69
+		$this->ruleset = $ruleset;
70
+		$this->config  = $config;
71
+
72
+		$paths = $config->files;
73
+		foreach ($paths as $path) {
74
+			$isPharFile = Util\Common::isPharFile($path);
75
+			if (is_dir($path) === true || $isPharFile === true) {
76
+				if ($isPharFile === true) {
77
+					$path = 'phar://'.$path;
78
+				}
79
+
80
+				$filterClass = $this->getFilterClass();
81
+
82
+				$di       = new \RecursiveDirectoryIterator($path, (\RecursiveDirectoryIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS));
83
+				$filter   = new $filterClass($di, $path, $config, $ruleset);
84
+				$iterator = new \RecursiveIteratorIterator($filter);
85
+
86
+				foreach ($iterator as $file) {
87
+					$this->files[$file->getPathname()] = null;
88
+					$this->numFiles++;
89
+				}
90
+			} else {
91
+				$this->addFile($path);
92
+			}//end if
93
+		}//end foreach
94
+
95
+		reset($this->files);
96
+
97
+	}//end __construct()
98
+
99
+
100
+	/**
101
+	 * Add a file to the list.
102
+	 *
103
+	 * If a file object has already been created, it can be passed here.
104
+	 * If it is left NULL, it will be created when accessed.
105
+	 *
106
+	 * @param string                      $path The path to the file being added.
107
+	 * @param \PHP_CodeSniffer\Files\File $file The file being added.
108
+	 *
109
+	 * @return void
110
+	 */
111
+	public function addFile($path, $file=null)
112
+	{
113
+		// No filtering is done for STDIN when the filename
114
+		// has not been specified.
115
+		if ($path === 'STDIN') {
116
+			$this->files[$path] = $file;
117
+			$this->numFiles++;
118
+			return;
119
+		}
120
+
121
+		$filterClass = $this->getFilterClass();
122
+
123
+		$di       = new \RecursiveArrayIterator([$path]);
124
+		$filter   = new $filterClass($di, $path, $this->config, $this->ruleset);
125
+		$iterator = new \RecursiveIteratorIterator($filter);
126
+
127
+		foreach ($iterator as $path) {
128
+			$this->files[$path] = $file;
129
+			$this->numFiles++;
130
+		}
131
+
132
+	}//end addFile()
133
+
134
+
135
+	/**
136
+	 * Get the class name of the filter being used for the run.
137
+	 *
138
+	 * @return string
139
+	 */
140
+	private function getFilterClass()
141
+	{
142
+		$filterType = $this->config->filter;
143
+
144
+		if ($filterType === null) {
145
+			$filterClass = '\PHP_CodeSniffer\Filters\Filter';
146
+		} else {
147
+			if (strpos($filterType, '.') !== false) {
148
+				// This is a path to a custom filter class.
149
+				$filename = realpath($filterType);
150
+				if ($filename === false) {
151
+					$error = "ERROR: Custom filter \"$filterType\" not found".PHP_EOL;
152
+					throw new DeepExitException($error, 3);
153
+				}
154
+
155
+				$filterClass = Autoload::loadFile($filename);
156
+			} else {
157
+				$filterClass = '\PHP_CodeSniffer\Filters\\'.$filterType;
158
+			}
159
+		}
160
+
161
+		return $filterClass;
162
+
163
+	}//end getFilterClass()
164
+
165
+
166
+	/**
167
+	 * Rewind the iterator to the first file.
168
+	 *
169
+	 * @return void
170
+	 */
171
+	public function rewind()
172
+	{
173
+		reset($this->files);
174
+
175
+	}//end rewind()
176
+
177
+
178
+	/**
179
+	 * Get the file that is currently being processed.
180
+	 *
181
+	 * @return \PHP_CodeSniffer\Files\File
182
+	 */
183
+	public function current()
184
+	{
185
+		$path = key($this->files);
186
+		if ($this->files[$path] === null) {
187
+			$this->files[$path] = new LocalFile($path, $this->ruleset, $this->config);
188
+		}
189
+
190
+		return $this->files[$path];
191
+
192
+	}//end current()
193
+
194
+
195
+	/**
196
+	 * Return the file path of the current file being processed.
197
+	 *
198
+	 * @return void
199
+	 */
200
+	public function key()
201
+	{
202
+		return key($this->files);
203
+
204
+	}//end key()
205
+
206
+
207
+	/**
208
+	 * Move forward to the next file.
209
+	 *
210
+	 * @return void
211
+	 */
212
+	public function next()
213
+	{
214
+		next($this->files);
215
+
216
+	}//end next()
217
+
218
+
219
+	/**
220
+	 * Checks if current position is valid.
221
+	 *
222
+	 * @return boolean
223
+	 */
224
+	public function valid()
225
+	{
226
+		if (current($this->files) === false) {
227
+			return false;
228
+		}
229
+
230
+		return true;
231
+
232
+	}//end valid()
233
+
234
+
235
+	/**
236
+	 * Return the number of files in the list.
237
+	 *
238
+	 * @return integer
239
+	 */
240
+	public function count()
241
+	{
242
+		return $this->numFiles;
243
+
244
+	}//end count()
245 245
 
246 246
 
247 247
 }//end class
Please login to merge, or discard this patch.
vendor/squizlabs/php_codesniffer/src/Files/LocalFile.php 1 patch
Indentation   +191 added lines, -191 removed lines patch added patch discarded remove patch
@@ -17,197 +17,197 @@
 block discarded – undo
17 17
 {
18 18
 
19 19
 
20
-    /**
21
-     * Creates a LocalFile object and sets the content.
22
-     *
23
-     * @param string                   $path    The absolute path to the file.
24
-     * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
25
-     * @param \PHP_CodeSniffer\Config  $config  The config data for the run.
26
-     *
27
-     * @return void
28
-     */
29
-    public function __construct($path, Ruleset $ruleset, Config $config)
30
-    {
31
-        $this->path = trim($path);
32
-        if (is_readable($this->path) === false) {
33
-            parent::__construct($this->path, $ruleset, $config);
34
-            $error = 'Error opening file; file no longer exists or you do not have access to read the file';
35
-            $this->addMessage(true, $error, 1, 1, 'Internal.LocalFile', [], 5, false);
36
-            $this->ignored = true;
37
-            return;
38
-        }
39
-
40
-        // Before we go and spend time tokenizing this file, just check
41
-        // to see if there is a tag up top to indicate that the whole
42
-        // file should be ignored. It must be on one of the first two lines.
43
-        if ($config->annotations === true) {
44
-            $handle = fopen($this->path, 'r');
45
-            if ($handle !== false) {
46
-                $firstContent  = fgets($handle);
47
-                $firstContent .= fgets($handle);
48
-                fclose($handle);
49
-
50
-                if (strpos($firstContent, '@codingStandardsIgnoreFile') !== false
51
-                    || stripos($firstContent, 'phpcs:ignorefile') !== false
52
-                ) {
53
-                    // We are ignoring the whole file.
54
-                    $this->ignored = true;
55
-                    return;
56
-                }
57
-            }
58
-        }
59
-
60
-        $this->reloadContent();
61
-
62
-        parent::__construct($this->path, $ruleset, $config);
63
-
64
-    }//end __construct()
65
-
66
-
67
-    /**
68
-     * Loads the latest version of the file's content from the file system.
69
-     *
70
-     * @return void
71
-     */
72
-    public function reloadContent()
73
-    {
74
-        $this->setContent(file_get_contents($this->path));
75
-
76
-    }//end reloadContent()
77
-
78
-
79
-    /**
80
-     * Processes the file.
81
-     *
82
-     * @return void
83
-     */
84
-    public function process()
85
-    {
86
-        if ($this->ignored === true) {
87
-            return;
88
-        }
89
-
90
-        if ($this->configCache['cache'] === false) {
91
-            parent::process();
92
-            return;
93
-        }
94
-
95
-        $hash  = md5_file($this->path);
96
-        $cache = Cache::get($this->path);
97
-        if ($cache !== false && $cache['hash'] === $hash) {
98
-            // We can't filter metrics, so just load all of them.
99
-            $this->metrics = $cache['metrics'];
100
-
101
-            if ($this->configCache['recordErrors'] === true) {
102
-                // Replay the cached errors and warnings to filter out the ones
103
-                // we don't need for this specific run.
104
-                $this->configCache['cache'] = false;
105
-                $this->replayErrors($cache['errors'], $cache['warnings']);
106
-                $this->configCache['cache'] = true;
107
-            } else {
108
-                $this->errorCount   = $cache['errorCount'];
109
-                $this->warningCount = $cache['warningCount'];
110
-                $this->fixableCount = $cache['fixableCount'];
111
-            }
112
-
113
-            if (PHP_CODESNIFFER_VERBOSITY > 0
114
-                || (PHP_CODESNIFFER_CBF === true && empty($this->config->files) === false)
115
-            ) {
116
-                echo "[loaded from cache]... ";
117
-            }
118
-
119
-            $this->numTokens = $cache['numTokens'];
120
-            $this->fromCache = true;
121
-            return;
122
-        }//end if
123
-
124
-        if (PHP_CODESNIFFER_VERBOSITY > 1) {
125
-            echo PHP_EOL;
126
-        }
127
-
128
-        parent::process();
129
-
130
-        $cache = [
131
-            'hash'         => $hash,
132
-            'errors'       => $this->errors,
133
-            'warnings'     => $this->warnings,
134
-            'metrics'      => $this->metrics,
135
-            'errorCount'   => $this->errorCount,
136
-            'warningCount' => $this->warningCount,
137
-            'fixableCount' => $this->fixableCount,
138
-            'numTokens'    => $this->numTokens,
139
-        ];
140
-
141
-        Cache::set($this->path, $cache);
142
-
143
-        // During caching, we don't filter out errors in any way, so
144
-        // we need to do that manually now by replaying them.
145
-        if ($this->configCache['recordErrors'] === true) {
146
-            $this->configCache['cache'] = false;
147
-            $this->replayErrors($this->errors, $this->warnings);
148
-            $this->configCache['cache'] = true;
149
-        }
150
-
151
-    }//end process()
152
-
153
-
154
-    /**
155
-     * Clears and replays error and warnings for the file.
156
-     *
157
-     * Replaying errors and warnings allows for filtering rules to be changed
158
-     * and then errors and warnings to be reapplied with the new rules. This is
159
-     * particularly useful while caching.
160
-     *
161
-     * @param array $errors   The list of errors to replay.
162
-     * @param array $warnings The list of warnings to replay.
163
-     *
164
-     * @return void
165
-     */
166
-    private function replayErrors($errors, $warnings)
167
-    {
168
-        $this->errors       = [];
169
-        $this->warnings     = [];
170
-        $this->errorCount   = 0;
171
-        $this->warningCount = 0;
172
-        $this->fixableCount = 0;
173
-
174
-        foreach ($errors as $line => $lineErrors) {
175
-            foreach ($lineErrors as $column => $colErrors) {
176
-                foreach ($colErrors as $error) {
177
-                    $this->activeListener = $error['listener'];
178
-                    $this->addMessage(
179
-                        true,
180
-                        $error['message'],
181
-                        $line,
182
-                        $column,
183
-                        $error['source'],
184
-                        [],
185
-                        $error['severity'],
186
-                        $error['fixable']
187
-                    );
188
-                }
189
-            }
190
-        }
191
-
192
-        foreach ($warnings as $line => $lineErrors) {
193
-            foreach ($lineErrors as $column => $colErrors) {
194
-                foreach ($colErrors as $error) {
195
-                    $this->activeListener = $error['listener'];
196
-                    $this->addMessage(
197
-                        false,
198
-                        $error['message'],
199
-                        $line,
200
-                        $column,
201
-                        $error['source'],
202
-                        [],
203
-                        $error['severity'],
204
-                        $error['fixable']
205
-                    );
206
-                }
207
-            }
208
-        }
209
-
210
-    }//end replayErrors()
20
+	/**
21
+	 * Creates a LocalFile object and sets the content.
22
+	 *
23
+	 * @param string                   $path    The absolute path to the file.
24
+	 * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
25
+	 * @param \PHP_CodeSniffer\Config  $config  The config data for the run.
26
+	 *
27
+	 * @return void
28
+	 */
29
+	public function __construct($path, Ruleset $ruleset, Config $config)
30
+	{
31
+		$this->path = trim($path);
32
+		if (is_readable($this->path) === false) {
33
+			parent::__construct($this->path, $ruleset, $config);
34
+			$error = 'Error opening file; file no longer exists or you do not have access to read the file';
35
+			$this->addMessage(true, $error, 1, 1, 'Internal.LocalFile', [], 5, false);
36
+			$this->ignored = true;
37
+			return;
38
+		}
39
+
40
+		// Before we go and spend time tokenizing this file, just check
41
+		// to see if there is a tag up top to indicate that the whole
42
+		// file should be ignored. It must be on one of the first two lines.
43
+		if ($config->annotations === true) {
44
+			$handle = fopen($this->path, 'r');
45
+			if ($handle !== false) {
46
+				$firstContent  = fgets($handle);
47
+				$firstContent .= fgets($handle);
48
+				fclose($handle);
49
+
50
+				if (strpos($firstContent, '@codingStandardsIgnoreFile') !== false
51
+					|| stripos($firstContent, 'phpcs:ignorefile') !== false
52
+				) {
53
+					// We are ignoring the whole file.
54
+					$this->ignored = true;
55
+					return;
56
+				}
57
+			}
58
+		}
59
+
60
+		$this->reloadContent();
61
+
62
+		parent::__construct($this->path, $ruleset, $config);
63
+
64
+	}//end __construct()
65
+
66
+
67
+	/**
68
+	 * Loads the latest version of the file's content from the file system.
69
+	 *
70
+	 * @return void
71
+	 */
72
+	public function reloadContent()
73
+	{
74
+		$this->setContent(file_get_contents($this->path));
75
+
76
+	}//end reloadContent()
77
+
78
+
79
+	/**
80
+	 * Processes the file.
81
+	 *
82
+	 * @return void
83
+	 */
84
+	public function process()
85
+	{
86
+		if ($this->ignored === true) {
87
+			return;
88
+		}
89
+
90
+		if ($this->configCache['cache'] === false) {
91
+			parent::process();
92
+			return;
93
+		}
94
+
95
+		$hash  = md5_file($this->path);
96
+		$cache = Cache::get($this->path);
97
+		if ($cache !== false && $cache['hash'] === $hash) {
98
+			// We can't filter metrics, so just load all of them.
99
+			$this->metrics = $cache['metrics'];
100
+
101
+			if ($this->configCache['recordErrors'] === true) {
102
+				// Replay the cached errors and warnings to filter out the ones
103
+				// we don't need for this specific run.
104
+				$this->configCache['cache'] = false;
105
+				$this->replayErrors($cache['errors'], $cache['warnings']);
106
+				$this->configCache['cache'] = true;
107
+			} else {
108
+				$this->errorCount   = $cache['errorCount'];
109
+				$this->warningCount = $cache['warningCount'];
110
+				$this->fixableCount = $cache['fixableCount'];
111
+			}
112
+
113
+			if (PHP_CODESNIFFER_VERBOSITY > 0
114
+				|| (PHP_CODESNIFFER_CBF === true && empty($this->config->files) === false)
115
+			) {
116
+				echo "[loaded from cache]... ";
117
+			}
118
+
119
+			$this->numTokens = $cache['numTokens'];
120
+			$this->fromCache = true;
121
+			return;
122
+		}//end if
123
+
124
+		if (PHP_CODESNIFFER_VERBOSITY > 1) {
125
+			echo PHP_EOL;
126
+		}
127
+
128
+		parent::process();
129
+
130
+		$cache = [
131
+			'hash'         => $hash,
132
+			'errors'       => $this->errors,
133
+			'warnings'     => $this->warnings,
134
+			'metrics'      => $this->metrics,
135
+			'errorCount'   => $this->errorCount,
136
+			'warningCount' => $this->warningCount,
137
+			'fixableCount' => $this->fixableCount,
138
+			'numTokens'    => $this->numTokens,
139
+		];
140
+
141
+		Cache::set($this->path, $cache);
142
+
143
+		// During caching, we don't filter out errors in any way, so
144
+		// we need to do that manually now by replaying them.
145
+		if ($this->configCache['recordErrors'] === true) {
146
+			$this->configCache['cache'] = false;
147
+			$this->replayErrors($this->errors, $this->warnings);
148
+			$this->configCache['cache'] = true;
149
+		}
150
+
151
+	}//end process()
152
+
153
+
154
+	/**
155
+	 * Clears and replays error and warnings for the file.
156
+	 *
157
+	 * Replaying errors and warnings allows for filtering rules to be changed
158
+	 * and then errors and warnings to be reapplied with the new rules. This is
159
+	 * particularly useful while caching.
160
+	 *
161
+	 * @param array $errors   The list of errors to replay.
162
+	 * @param array $warnings The list of warnings to replay.
163
+	 *
164
+	 * @return void
165
+	 */
166
+	private function replayErrors($errors, $warnings)
167
+	{
168
+		$this->errors       = [];
169
+		$this->warnings     = [];
170
+		$this->errorCount   = 0;
171
+		$this->warningCount = 0;
172
+		$this->fixableCount = 0;
173
+
174
+		foreach ($errors as $line => $lineErrors) {
175
+			foreach ($lineErrors as $column => $colErrors) {
176
+				foreach ($colErrors as $error) {
177
+					$this->activeListener = $error['listener'];
178
+					$this->addMessage(
179
+						true,
180
+						$error['message'],
181
+						$line,
182
+						$column,
183
+						$error['source'],
184
+						[],
185
+						$error['severity'],
186
+						$error['fixable']
187
+					);
188
+				}
189
+			}
190
+		}
191
+
192
+		foreach ($warnings as $line => $lineErrors) {
193
+			foreach ($lineErrors as $column => $colErrors) {
194
+				foreach ($colErrors as $error) {
195
+					$this->activeListener = $error['listener'];
196
+					$this->addMessage(
197
+						false,
198
+						$error['message'],
199
+						$line,
200
+						$column,
201
+						$error['source'],
202
+						[],
203
+						$error['severity'],
204
+						$error['fixable']
205
+					);
206
+				}
207
+			}
208
+		}
209
+
210
+	}//end replayErrors()
211 211
 
212 212
 
213 213
 }//end class
Please login to merge, or discard this patch.
vendor/squizlabs/php_codesniffer/src/Files/DummyFile.php 1 patch
Indentation   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -21,62 +21,62 @@
 block discarded – undo
21 21
 {
22 22
 
23 23
 
24
-    /**
25
-     * Creates a DummyFile object and sets the content.
26
-     *
27
-     * @param string                   $content The content of the file.
28
-     * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
29
-     * @param \PHP_CodeSniffer\Config  $config  The config data for the run.
30
-     *
31
-     * @return void
32
-     */
33
-    public function __construct($content, Ruleset $ruleset, Config $config)
34
-    {
35
-        $this->setContent($content);
24
+	/**
25
+	 * Creates a DummyFile object and sets the content.
26
+	 *
27
+	 * @param string                   $content The content of the file.
28
+	 * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
29
+	 * @param \PHP_CodeSniffer\Config  $config  The config data for the run.
30
+	 *
31
+	 * @return void
32
+	 */
33
+	public function __construct($content, Ruleset $ruleset, Config $config)
34
+	{
35
+		$this->setContent($content);
36 36
 
37
-        // See if a filename was defined in the content.
38
-        // This is done by including: phpcs_input_file: [file path]
39
-        // as the first line of content.
40
-        $path = 'STDIN';
41
-        if ($content !== null) {
42
-            if (substr($content, 0, 17) === 'phpcs_input_file:') {
43
-                $eolPos   = strpos($content, $this->eolChar);
44
-                $filename = trim(substr($content, 17, ($eolPos - 17)));
45
-                $content  = substr($content, ($eolPos + strlen($this->eolChar)));
46
-                $path     = $filename;
37
+		// See if a filename was defined in the content.
38
+		// This is done by including: phpcs_input_file: [file path]
39
+		// as the first line of content.
40
+		$path = 'STDIN';
41
+		if ($content !== null) {
42
+			if (substr($content, 0, 17) === 'phpcs_input_file:') {
43
+				$eolPos   = strpos($content, $this->eolChar);
44
+				$filename = trim(substr($content, 17, ($eolPos - 17)));
45
+				$content  = substr($content, ($eolPos + strlen($this->eolChar)));
46
+				$path     = $filename;
47 47
 
48
-                $this->setContent($content);
49
-            }
50
-        }
48
+				$this->setContent($content);
49
+			}
50
+		}
51 51
 
52
-        // The CLI arg overrides anything passed in the content.
53
-        if ($config->stdinPath !== null) {
54
-            $path = $config->stdinPath;
55
-        }
52
+		// The CLI arg overrides anything passed in the content.
53
+		if ($config->stdinPath !== null) {
54
+			$path = $config->stdinPath;
55
+		}
56 56
 
57
-        parent::__construct($path, $ruleset, $config);
57
+		parent::__construct($path, $ruleset, $config);
58 58
 
59
-    }//end __construct()
59
+	}//end __construct()
60 60
 
61 61
 
62
-    /**
63
-     * Set the error, warning, and fixable counts for the file.
64
-     *
65
-     * @param int $errorCount   The number of errors found.
66
-     * @param int $warningCount The number of warnings found.
67
-     * @param int $fixableCount The number of fixable errors found.
68
-     * @param int $fixedCount   The number of errors that were fixed.
69
-     *
70
-     * @return void
71
-     */
72
-    public function setErrorCounts($errorCount, $warningCount, $fixableCount, $fixedCount)
73
-    {
74
-        $this->errorCount   = $errorCount;
75
-        $this->warningCount = $warningCount;
76
-        $this->fixableCount = $fixableCount;
77
-        $this->fixedCount   = $fixedCount;
62
+	/**
63
+	 * Set the error, warning, and fixable counts for the file.
64
+	 *
65
+	 * @param int $errorCount   The number of errors found.
66
+	 * @param int $warningCount The number of warnings found.
67
+	 * @param int $fixableCount The number of fixable errors found.
68
+	 * @param int $fixedCount   The number of errors that were fixed.
69
+	 *
70
+	 * @return void
71
+	 */
72
+	public function setErrorCounts($errorCount, $warningCount, $fixableCount, $fixedCount)
73
+	{
74
+		$this->errorCount   = $errorCount;
75
+		$this->warningCount = $warningCount;
76
+		$this->fixableCount = $fixableCount;
77
+		$this->fixedCount   = $fixedCount;
78 78
 
79
-    }//end setErrorCounts()
79
+	}//end setErrorCounts()
80 80
 
81 81
 
82 82
 }//end class
Please login to merge, or discard this patch.
vendor/squizlabs/php_codesniffer/src/Ruleset.php 1 patch
Indentation   +1348 added lines, -1348 removed lines patch added patch discarded remove patch
@@ -17,1354 +17,1354 @@
 block discarded – undo
17 17
 class Ruleset
18 18
 {
19 19
 
20
-    /**
21
-     * The name of the coding standard being used.
22
-     *
23
-     * If a top-level standard includes other standards, or sniffs
24
-     * from other standards, only the name of the top-level standard
25
-     * will be stored in here.
26
-     *
27
-     * If multiple top-level standards are being loaded into
28
-     * a single ruleset object, this will store a comma separated list
29
-     * of the top-level standard names.
30
-     *
31
-     * @var string
32
-     */
33
-    public $name = '';
34
-
35
-    /**
36
-     * A list of file paths for the ruleset files being used.
37
-     *
38
-     * @var string[]
39
-     */
40
-    public $paths = [];
41
-
42
-    /**
43
-     * A list of regular expressions used to ignore specific sniffs for files and folders.
44
-     *
45
-     * Is also used to set global exclude patterns.
46
-     * The key is the regular expression and the value is the type
47
-     * of ignore pattern (absolute or relative).
48
-     *
49
-     * @var array<string, string>
50
-     */
51
-    public $ignorePatterns = [];
52
-
53
-    /**
54
-     * A list of regular expressions used to include specific sniffs for files and folders.
55
-     *
56
-     * The key is the sniff code and the value is an array with
57
-     * the key being a regular expression and the value is the type
58
-     * of ignore pattern (absolute or relative).
59
-     *
60
-     * @var array<string, array<string, string>>
61
-     */
62
-    public $includePatterns = [];
63
-
64
-    /**
65
-     * An array of sniff objects that are being used to check files.
66
-     *
67
-     * The key is the fully qualified name of the sniff class
68
-     * and the value is the sniff object.
69
-     *
70
-     * @var array<string, \PHP_CodeSniffer\Sniffs\Sniff>
71
-     */
72
-    public $sniffs = [];
73
-
74
-    /**
75
-     * A mapping of sniff codes to fully qualified class names.
76
-     *
77
-     * The key is the sniff code and the value
78
-     * is the fully qualified name of the sniff class.
79
-     *
80
-     * @var array<string, string>
81
-     */
82
-    public $sniffCodes = [];
83
-
84
-    /**
85
-     * An array of token types and the sniffs that are listening for them.
86
-     *
87
-     * The key is the token name being listened for and the value
88
-     * is the sniff object.
89
-     *
90
-     * @var array<int, \PHP_CodeSniffer\Sniffs\Sniff>
91
-     */
92
-    public $tokenListeners = [];
93
-
94
-    /**
95
-     * An array of rules from the ruleset.xml file.
96
-     *
97
-     * It may be empty, indicating that the ruleset does not override
98
-     * any of the default sniff settings.
99
-     *
100
-     * @var array<string, mixed>
101
-     */
102
-    public $ruleset = [];
103
-
104
-    /**
105
-     * The directories that the processed rulesets are in.
106
-     *
107
-     * @var string[]
108
-     */
109
-    protected $rulesetDirs = [];
110
-
111
-    /**
112
-     * The config data for the run.
113
-     *
114
-     * @var \PHP_CodeSniffer\Config
115
-     */
116
-    private $config = null;
117
-
118
-
119
-    /**
120
-     * Initialise the ruleset that the run will use.
121
-     *
122
-     * @param \PHP_CodeSniffer\Config $config The config data for the run.
123
-     *
124
-     * @return void
125
-     */
126
-    public function __construct(Config $config)
127
-    {
128
-        // Ignore sniff restrictions if caching is on.
129
-        $restrictions = [];
130
-        $exclusions   = [];
131
-        if ($config->cache === false) {
132
-            $restrictions = $config->sniffs;
133
-            $exclusions   = $config->exclude;
134
-        }
135
-
136
-        $this->config = $config;
137
-        $sniffs       = [];
138
-
139
-        $standardPaths = [];
140
-        foreach ($config->standards as $standard) {
141
-            $installed = Util\Standards::getInstalledStandardPath($standard);
142
-            if ($installed === null) {
143
-                $standard = Util\Common::realpath($standard);
144
-                if (is_dir($standard) === true
145
-                    && is_file(Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true
146
-                ) {
147
-                    $standard = Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml');
148
-                }
149
-            } else {
150
-                $standard = $installed;
151
-            }
152
-
153
-            $standardPaths[] = $standard;
154
-        }
155
-
156
-        foreach ($standardPaths as $standard) {
157
-            $ruleset = @simplexml_load_string(file_get_contents($standard));
158
-            if ($ruleset !== false) {
159
-                $standardName = (string) $ruleset['name'];
160
-                if ($this->name !== '') {
161
-                    $this->name .= ', ';
162
-                }
163
-
164
-                $this->name .= $standardName;
165
-
166
-                // Allow autoloading of custom files inside this standard.
167
-                if (isset($ruleset['namespace']) === true) {
168
-                    $namespace = (string) $ruleset['namespace'];
169
-                } else {
170
-                    $namespace = basename(dirname($standard));
171
-                }
172
-
173
-                Autoload::addSearchPath(dirname($standard), $namespace);
174
-            }
175
-
176
-            if (defined('PHP_CODESNIFFER_IN_TESTS') === true && empty($restrictions) === false) {
177
-                // In unit tests, only register the sniffs that the test wants and not the entire standard.
178
-                try {
179
-                    foreach ($restrictions as $restriction) {
180
-                        $sniffs = array_merge($sniffs, $this->expandRulesetReference($restriction, dirname($standard)));
181
-                    }
182
-                } catch (RuntimeException $e) {
183
-                    // Sniff reference could not be expanded, which probably means this
184
-                    // is an installed standard. Let the unit test system take care of
185
-                    // setting the correct sniff for testing.
186
-                    return;
187
-                }
188
-
189
-                break;
190
-            }
191
-
192
-            if (PHP_CODESNIFFER_VERBOSITY === 1) {
193
-                echo "Registering sniffs in the $standardName standard... ";
194
-                if (count($config->standards) > 1 || PHP_CODESNIFFER_VERBOSITY > 2) {
195
-                    echo PHP_EOL;
196
-                }
197
-            }
198
-
199
-            $sniffs = array_merge($sniffs, $this->processRuleset($standard));
200
-        }//end foreach
201
-
202
-        $sniffRestrictions = [];
203
-        foreach ($restrictions as $sniffCode) {
204
-            $parts     = explode('.', strtolower($sniffCode));
205
-            $sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff';
206
-            $sniffRestrictions[$sniffName] = true;
207
-        }
208
-
209
-        $sniffExclusions = [];
210
-        foreach ($exclusions as $sniffCode) {
211
-            $parts     = explode('.', strtolower($sniffCode));
212
-            $sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff';
213
-            $sniffExclusions[$sniffName] = true;
214
-        }
215
-
216
-        $this->registerSniffs($sniffs, $sniffRestrictions, $sniffExclusions);
217
-        $this->populateTokenListeners();
218
-
219
-        $numSniffs = count($this->sniffs);
220
-        if (PHP_CODESNIFFER_VERBOSITY === 1) {
221
-            echo "DONE ($numSniffs sniffs registered)".PHP_EOL;
222
-        }
223
-
224
-        if ($numSniffs === 0) {
225
-            throw new RuntimeException('No sniffs were registered');
226
-        }
227
-
228
-    }//end __construct()
229
-
230
-
231
-    /**
232
-     * Prints a report showing the sniffs contained in a standard.
233
-     *
234
-     * @return void
235
-     */
236
-    public function explain()
237
-    {
238
-        $sniffs = array_keys($this->sniffCodes);
239
-        sort($sniffs);
240
-
241
-        ob_start();
242
-
243
-        $lastStandard = null;
244
-        $lastCount    = '';
245
-        $sniffCount   = count($sniffs);
246
-
247
-        // Add a dummy entry to the end so we loop
248
-        // one last time and clear the output buffer.
249
-        $sniffs[] = '';
250
-
251
-        echo PHP_EOL."The $this->name standard contains $sniffCount sniffs".PHP_EOL;
252
-
253
-        ob_start();
254
-
255
-        foreach ($sniffs as $i => $sniff) {
256
-            if ($i === $sniffCount) {
257
-                $currentStandard = null;
258
-            } else {
259
-                $currentStandard = substr($sniff, 0, strpos($sniff, '.'));
260
-                if ($lastStandard === null) {
261
-                    $lastStandard = $currentStandard;
262
-                }
263
-            }
264
-
265
-            if ($currentStandard !== $lastStandard) {
266
-                $sniffList = ob_get_contents();
267
-                ob_end_clean();
268
-
269
-                echo PHP_EOL.$lastStandard.' ('.$lastCount.' sniff';
270
-                if ($lastCount > 1) {
271
-                    echo 's';
272
-                }
273
-
274
-                echo ')'.PHP_EOL;
275
-                echo str_repeat('-', (strlen($lastStandard.$lastCount) + 10));
276
-                echo PHP_EOL;
277
-                echo $sniffList;
278
-
279
-                $lastStandard = $currentStandard;
280
-                $lastCount    = 0;
281
-
282
-                if ($currentStandard === null) {
283
-                    break;
284
-                }
285
-
286
-                ob_start();
287
-            }//end if
288
-
289
-            echo '  '.$sniff.PHP_EOL;
290
-            $lastCount++;
291
-        }//end foreach
292
-
293
-    }//end explain()
294
-
295
-
296
-    /**
297
-     * Processes a single ruleset and returns a list of the sniffs it represents.
298
-     *
299
-     * Rules founds within the ruleset are processed immediately, but sniff classes
300
-     * are not registered by this method.
301
-     *
302
-     * @param string $rulesetPath The path to a ruleset XML file.
303
-     * @param int    $depth       How many nested processing steps we are in. This
304
-     *                            is only used for debug output.
305
-     *
306
-     * @return string[]
307
-     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the ruleset path is invalid.
308
-     */
309
-    public function processRuleset($rulesetPath, $depth=0)
310
-    {
311
-        $rulesetPath = Util\Common::realpath($rulesetPath);
312
-        if (PHP_CODESNIFFER_VERBOSITY > 1) {
313
-            echo str_repeat("\t", $depth);
314
-            echo 'Processing ruleset '.Util\Common::stripBasepath($rulesetPath, $this->config->basepath).PHP_EOL;
315
-        }
316
-
317
-        libxml_use_internal_errors(true);
318
-        $ruleset = simplexml_load_string(file_get_contents($rulesetPath));
319
-        if ($ruleset === false) {
320
-            $errorMsg = "Ruleset $rulesetPath is not valid".PHP_EOL;
321
-            $errors   = libxml_get_errors();
322
-            foreach ($errors as $error) {
323
-                $errorMsg .= '- On line '.$error->line.', column '.$error->column.': '.$error->message;
324
-            }
325
-
326
-            libxml_clear_errors();
327
-            throw new RuntimeException($errorMsg);
328
-        }
329
-
330
-        libxml_use_internal_errors(false);
331
-
332
-        $ownSniffs      = [];
333
-        $includedSniffs = [];
334
-        $excludedSniffs = [];
335
-
336
-        $this->paths[]       = $rulesetPath;
337
-        $rulesetDir          = dirname($rulesetPath);
338
-        $this->rulesetDirs[] = $rulesetDir;
339
-
340
-        $sniffDir = $rulesetDir.DIRECTORY_SEPARATOR.'Sniffs';
341
-        if (is_dir($sniffDir) === true) {
342
-            if (PHP_CODESNIFFER_VERBOSITY > 1) {
343
-                echo str_repeat("\t", $depth);
344
-                echo "\tAdding sniff files from ".Util\Common::stripBasepath($sniffDir, $this->config->basepath).' directory'.PHP_EOL;
345
-            }
346
-
347
-            $ownSniffs = $this->expandSniffDirectory($sniffDir, $depth);
348
-        }
349
-
350
-        // Included custom autoloaders.
351
-        foreach ($ruleset->{'autoload'} as $autoload) {
352
-            if ($this->shouldProcessElement($autoload) === false) {
353
-                continue;
354
-            }
355
-
356
-            $autoloadPath = (string) $autoload;
357
-            if (is_file($autoloadPath) === false) {
358
-                $autoloadPath = Util\Common::realPath(dirname($rulesetPath).DIRECTORY_SEPARATOR.$autoloadPath);
359
-            }
360
-
361
-            if ($autoloadPath === false) {
362
-                throw new RuntimeException('The specified autoload file "'.$autoload.'" does not exist');
363
-            }
364
-
365
-            include_once $autoloadPath;
366
-
367
-            if (PHP_CODESNIFFER_VERBOSITY > 1) {
368
-                echo str_repeat("\t", $depth);
369
-                echo "\t=> included autoloader $autoloadPath".PHP_EOL;
370
-            }
371
-        }//end foreach
372
-
373
-        // Process custom sniff config settings.
374
-        foreach ($ruleset->{'config'} as $config) {
375
-            if ($this->shouldProcessElement($config) === false) {
376
-                continue;
377
-            }
378
-
379
-            Config::setConfigData((string) $config['name'], (string) $config['value'], true);
380
-            if (PHP_CODESNIFFER_VERBOSITY > 1) {
381
-                echo str_repeat("\t", $depth);
382
-                echo "\t=> set config value ".(string) $config['name'].': '.(string) $config['value'].PHP_EOL;
383
-            }
384
-        }
385
-
386
-        foreach ($ruleset->rule as $rule) {
387
-            if (isset($rule['ref']) === false
388
-                || $this->shouldProcessElement($rule) === false
389
-            ) {
390
-                continue;
391
-            }
392
-
393
-            if (PHP_CODESNIFFER_VERBOSITY > 1) {
394
-                echo str_repeat("\t", $depth);
395
-                echo "\tProcessing rule \"".$rule['ref'].'"'.PHP_EOL;
396
-            }
397
-
398
-            $expandedSniffs = $this->expandRulesetReference((string) $rule['ref'], $rulesetDir, $depth);
399
-            $newSniffs      = array_diff($expandedSniffs, $includedSniffs);
400
-            $includedSniffs = array_merge($includedSniffs, $expandedSniffs);
401
-
402
-            $parts = explode('.', $rule['ref']);
403
-            if (count($parts) === 4
404
-                && $parts[0] !== ''
405
-                && $parts[1] !== ''
406
-                && $parts[2] !== ''
407
-            ) {
408
-                $sniffCode = $parts[0].'.'.$parts[1].'.'.$parts[2];
409
-                if (isset($this->ruleset[$sniffCode]['severity']) === true
410
-                    && $this->ruleset[$sniffCode]['severity'] === 0
411
-                ) {
412
-                    // This sniff code has already been turned off, but now
413
-                    // it is being explicitly included again, so turn it back on.
414
-                    $this->ruleset[(string) $rule['ref']]['severity'] = 5;
415
-                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
416
-                        echo str_repeat("\t", $depth);
417
-                        echo "\t\t* disabling sniff exclusion for specific message code *".PHP_EOL;
418
-                        echo str_repeat("\t", $depth);
419
-                        echo "\t\t=> severity set to 5".PHP_EOL;
420
-                    }
421
-                } else if (empty($newSniffs) === false) {
422
-                    $newSniff = $newSniffs[0];
423
-                    if (in_array($newSniff, $ownSniffs, true) === false) {
424
-                        // Including a sniff that hasn't been included higher up, but
425
-                        // only including a single message from it. So turn off all messages in
426
-                        // the sniff, except this one.
427
-                        $this->ruleset[$sniffCode]['severity']            = 0;
428
-                        $this->ruleset[(string) $rule['ref']]['severity'] = 5;
429
-                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
430
-                            echo str_repeat("\t", $depth);
431
-                            echo "\t\tExcluding sniff \"".$sniffCode.'" except for "'.$parts[3].'"'.PHP_EOL;
432
-                        }
433
-                    }
434
-                }//end if
435
-            }//end if
436
-
437
-            if (isset($rule->exclude) === true) {
438
-                foreach ($rule->exclude as $exclude) {
439
-                    if (isset($exclude['name']) === false) {
440
-                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
441
-                            echo str_repeat("\t", $depth);
442
-                            echo "\t\t* ignoring empty exclude rule *".PHP_EOL;
443
-                            echo "\t\t\t=> ".$exclude->asXML().PHP_EOL;
444
-                        }
445
-
446
-                        continue;
447
-                    }
448
-
449
-                    if ($this->shouldProcessElement($exclude) === false) {
450
-                        continue;
451
-                    }
452
-
453
-                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
454
-                        echo str_repeat("\t", $depth);
455
-                        echo "\t\tExcluding rule \"".$exclude['name'].'"'.PHP_EOL;
456
-                    }
457
-
458
-                    // Check if a single code is being excluded, which is a shortcut
459
-                    // for setting the severity of the message to 0.
460
-                    $parts = explode('.', $exclude['name']);
461
-                    if (count($parts) === 4) {
462
-                        $this->ruleset[(string) $exclude['name']]['severity'] = 0;
463
-                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
464
-                            echo str_repeat("\t", $depth);
465
-                            echo "\t\t=> severity set to 0".PHP_EOL;
466
-                        }
467
-                    } else {
468
-                        $excludedSniffs = array_merge(
469
-                            $excludedSniffs,
470
-                            $this->expandRulesetReference((string) $exclude['name'], $rulesetDir, ($depth + 1))
471
-                        );
472
-                    }
473
-                }//end foreach
474
-            }//end if
475
-
476
-            $this->processRule($rule, $newSniffs, $depth);
477
-        }//end foreach
478
-
479
-        // Process custom command line arguments.
480
-        $cliArgs = [];
481
-        foreach ($ruleset->{'arg'} as $arg) {
482
-            if ($this->shouldProcessElement($arg) === false) {
483
-                continue;
484
-            }
485
-
486
-            if (isset($arg['name']) === true) {
487
-                $argString = '--'.(string) $arg['name'];
488
-                if (isset($arg['value']) === true) {
489
-                    $argString .= '='.(string) $arg['value'];
490
-                }
491
-            } else {
492
-                $argString = '-'.(string) $arg['value'];
493
-            }
494
-
495
-            $cliArgs[] = $argString;
496
-
497
-            if (PHP_CODESNIFFER_VERBOSITY > 1) {
498
-                echo str_repeat("\t", $depth);
499
-                echo "\t=> set command line value $argString".PHP_EOL;
500
-            }
501
-        }//end foreach
502
-
503
-        // Set custom php ini values as CLI args.
504
-        foreach ($ruleset->{'ini'} as $arg) {
505
-            if ($this->shouldProcessElement($arg) === false) {
506
-                continue;
507
-            }
508
-
509
-            if (isset($arg['name']) === false) {
510
-                continue;
511
-            }
512
-
513
-            $name      = (string) $arg['name'];
514
-            $argString = $name;
515
-            if (isset($arg['value']) === true) {
516
-                $value      = (string) $arg['value'];
517
-                $argString .= "=$value";
518
-            } else {
519
-                $value = 'true';
520
-            }
521
-
522
-            $cliArgs[] = '-d';
523
-            $cliArgs[] = $argString;
524
-
525
-            if (PHP_CODESNIFFER_VERBOSITY > 1) {
526
-                echo str_repeat("\t", $depth);
527
-                echo "\t=> set PHP ini value $name to $value".PHP_EOL;
528
-            }
529
-        }//end foreach
530
-
531
-        if (empty($this->config->files) === true) {
532
-            // Process hard-coded file paths.
533
-            foreach ($ruleset->{'file'} as $file) {
534
-                $file      = (string) $file;
535
-                $cliArgs[] = $file;
536
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
537
-                    echo str_repeat("\t", $depth);
538
-                    echo "\t=> added \"$file\" to the file list".PHP_EOL;
539
-                }
540
-            }
541
-        }
542
-
543
-        if (empty($cliArgs) === false) {
544
-            // Change the directory so all relative paths are worked
545
-            // out based on the location of the ruleset instead of
546
-            // the location of the user.
547
-            $inPhar = Util\Common::isPharFile($rulesetDir);
548
-            if ($inPhar === false) {
549
-                $currentDir = getcwd();
550
-                chdir($rulesetDir);
551
-            }
552
-
553
-            $this->config->setCommandLineValues($cliArgs);
554
-
555
-            if ($inPhar === false) {
556
-                chdir($currentDir);
557
-            }
558
-        }
559
-
560
-        // Process custom ignore pattern rules.
561
-        foreach ($ruleset->{'exclude-pattern'} as $pattern) {
562
-            if ($this->shouldProcessElement($pattern) === false) {
563
-                continue;
564
-            }
565
-
566
-            if (isset($pattern['type']) === false) {
567
-                $pattern['type'] = 'absolute';
568
-            }
569
-
570
-            $this->ignorePatterns[(string) $pattern] = (string) $pattern['type'];
571
-            if (PHP_CODESNIFFER_VERBOSITY > 1) {
572
-                echo str_repeat("\t", $depth);
573
-                echo "\t=> added global ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL;
574
-            }
575
-        }
576
-
577
-        $includedSniffs = array_unique(array_merge($ownSniffs, $includedSniffs));
578
-        $excludedSniffs = array_unique($excludedSniffs);
579
-
580
-        if (PHP_CODESNIFFER_VERBOSITY > 1) {
581
-            $included = count($includedSniffs);
582
-            $excluded = count($excludedSniffs);
583
-            echo str_repeat("\t", $depth);
584
-            echo "=> Ruleset processing complete; included $included sniffs and excluded $excluded".PHP_EOL;
585
-        }
586
-
587
-        // Merge our own sniff list with our externally included
588
-        // sniff list, but filter out any excluded sniffs.
589
-        $files = [];
590
-        foreach ($includedSniffs as $sniff) {
591
-            if (in_array($sniff, $excludedSniffs, true) === true) {
592
-                continue;
593
-            } else {
594
-                $files[] = Util\Common::realpath($sniff);
595
-            }
596
-        }
597
-
598
-        return $files;
599
-
600
-    }//end processRuleset()
601
-
602
-
603
-    /**
604
-     * Expands a directory into a list of sniff files within.
605
-     *
606
-     * @param string $directory The path to a directory.
607
-     * @param int    $depth     How many nested processing steps we are in. This
608
-     *                          is only used for debug output.
609
-     *
610
-     * @return array
611
-     */
612
-    private function expandSniffDirectory($directory, $depth=0)
613
-    {
614
-        $sniffs = [];
615
-
616
-        $rdi = new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
617
-        $di  = new \RecursiveIteratorIterator($rdi, 0, \RecursiveIteratorIterator::CATCH_GET_CHILD);
618
-
619
-        $dirLen = strlen($directory);
620
-
621
-        foreach ($di as $file) {
622
-            $filename = $file->getFilename();
623
-
624
-            // Skip hidden files.
625
-            if (substr($filename, 0, 1) === '.') {
626
-                continue;
627
-            }
628
-
629
-            // We are only interested in PHP and sniff files.
630
-            $fileParts = explode('.', $filename);
631
-            if (array_pop($fileParts) !== 'php') {
632
-                continue;
633
-            }
634
-
635
-            $basename = basename($filename, '.php');
636
-            if (substr($basename, -5) !== 'Sniff') {
637
-                continue;
638
-            }
639
-
640
-            $path = $file->getPathname();
641
-
642
-            // Skip files in hidden directories within the Sniffs directory of this
643
-            // standard. We use the offset with strpos() to allow hidden directories
644
-            // before, valid example:
645
-            // /home/foo/.composer/vendor/squiz/custom_tool/MyStandard/Sniffs/...
646
-            if (strpos($path, DIRECTORY_SEPARATOR.'.', $dirLen) !== false) {
647
-                continue;
648
-            }
649
-
650
-            if (PHP_CODESNIFFER_VERBOSITY > 1) {
651
-                echo str_repeat("\t", $depth);
652
-                echo "\t\t=> ".Util\Common::stripBasepath($path, $this->config->basepath).PHP_EOL;
653
-            }
654
-
655
-            $sniffs[] = $path;
656
-        }//end foreach
657
-
658
-        return $sniffs;
659
-
660
-    }//end expandSniffDirectory()
661
-
662
-
663
-    /**
664
-     * Expands a ruleset reference into a list of sniff files.
665
-     *
666
-     * @param string $ref        The reference from the ruleset XML file.
667
-     * @param string $rulesetDir The directory of the ruleset XML file, used to
668
-     *                           evaluate relative paths.
669
-     * @param int    $depth      How many nested processing steps we are in. This
670
-     *                           is only used for debug output.
671
-     *
672
-     * @return array
673
-     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the reference is invalid.
674
-     */
675
-    private function expandRulesetReference($ref, $rulesetDir, $depth=0)
676
-    {
677
-        // Ignore internal sniffs codes as they are used to only
678
-        // hide and change internal messages.
679
-        if (substr($ref, 0, 9) === 'Internal.') {
680
-            if (PHP_CODESNIFFER_VERBOSITY > 1) {
681
-                echo str_repeat("\t", $depth);
682
-                echo "\t\t* ignoring internal sniff code *".PHP_EOL;
683
-            }
684
-
685
-            return [];
686
-        }
687
-
688
-        // As sniffs can't begin with a full stop, assume references in
689
-        // this format are relative paths and attempt to convert them
690
-        // to absolute paths. If this fails, let the reference run through
691
-        // the normal checks and have it fail as normal.
692
-        if (substr($ref, 0, 1) === '.') {
693
-            $realpath = Util\Common::realpath($rulesetDir.'/'.$ref);
694
-            if ($realpath !== false) {
695
-                $ref = $realpath;
696
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
697
-                    echo str_repeat("\t", $depth);
698
-                    echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
699
-                }
700
-            }
701
-        }
702
-
703
-        // As sniffs can't begin with a tilde, assume references in
704
-        // this format are relative to the user's home directory.
705
-        if (substr($ref, 0, 2) === '~/') {
706
-            $realpath = Util\Common::realpath($ref);
707
-            if ($realpath !== false) {
708
-                $ref = $realpath;
709
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
710
-                    echo str_repeat("\t", $depth);
711
-                    echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
712
-                }
713
-            }
714
-        }
715
-
716
-        if (is_file($ref) === true) {
717
-            if (substr($ref, -9) === 'Sniff.php') {
718
-                // A single external sniff.
719
-                $this->rulesetDirs[] = dirname(dirname(dirname($ref)));
720
-                return [$ref];
721
-            }
722
-        } else {
723
-            // See if this is a whole standard being referenced.
724
-            $path = Util\Standards::getInstalledStandardPath($ref);
725
-            if (Util\Common::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) {
726
-                // If the ruleset exists inside the phar file, use it.
727
-                if (file_exists($path.DIRECTORY_SEPARATOR.'ruleset.xml') === true) {
728
-                    $path .= DIRECTORY_SEPARATOR.'ruleset.xml';
729
-                } else {
730
-                    $path = null;
731
-                }
732
-            }
733
-
734
-            if ($path !== null) {
735
-                $ref = $path;
736
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
737
-                    echo str_repeat("\t", $depth);
738
-                    echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
739
-                }
740
-            } else if (is_dir($ref) === false) {
741
-                // Work out the sniff path.
742
-                $sepPos = strpos($ref, DIRECTORY_SEPARATOR);
743
-                if ($sepPos !== false) {
744
-                    $stdName = substr($ref, 0, $sepPos);
745
-                    $path    = substr($ref, $sepPos);
746
-                } else {
747
-                    $parts   = explode('.', $ref);
748
-                    $stdName = $parts[0];
749
-                    if (count($parts) === 1) {
750
-                        // A whole standard?
751
-                        $path = '';
752
-                    } else if (count($parts) === 2) {
753
-                        // A directory of sniffs?
754
-                        $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1];
755
-                    } else {
756
-                        // A single sniff?
757
-                        $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1].DIRECTORY_SEPARATOR.$parts[2].'Sniff.php';
758
-                    }
759
-                }
760
-
761
-                $newRef  = false;
762
-                $stdPath = Util\Standards::getInstalledStandardPath($stdName);
763
-                if ($stdPath !== null && $path !== '') {
764
-                    if (Util\Common::isPharFile($stdPath) === true
765
-                        && strpos($stdPath, 'ruleset.xml') === false
766
-                    ) {
767
-                        // Phar files can only return the directory,
768
-                        // since ruleset can be omitted if building one standard.
769
-                        $newRef = Util\Common::realpath($stdPath.$path);
770
-                    } else {
771
-                        $newRef = Util\Common::realpath(dirname($stdPath).$path);
772
-                    }
773
-                }
774
-
775
-                if ($newRef === false) {
776
-                    // The sniff is not locally installed, so check if it is being
777
-                    // referenced as a remote sniff outside the install. We do this
778
-                    // by looking through all directories where we have found ruleset
779
-                    // files before, looking for ones for this particular standard,
780
-                    // and seeing if it is in there.
781
-                    foreach ($this->rulesetDirs as $dir) {
782
-                        if (strtolower(basename($dir)) !== strtolower($stdName)) {
783
-                            continue;
784
-                        }
785
-
786
-                        $newRef = Util\Common::realpath($dir.$path);
787
-
788
-                        if ($newRef !== false) {
789
-                            $ref = $newRef;
790
-                        }
791
-                    }
792
-                } else {
793
-                    $ref = $newRef;
794
-                }
795
-
796
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
797
-                    echo str_repeat("\t", $depth);
798
-                    echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
799
-                }
800
-            }//end if
801
-        }//end if
802
-
803
-        if (is_dir($ref) === true) {
804
-            if (is_file($ref.DIRECTORY_SEPARATOR.'ruleset.xml') === true) {
805
-                // We are referencing an external coding standard.
806
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
807
-                    echo str_repeat("\t", $depth);
808
-                    echo "\t\t* rule is referencing a standard using directory name; processing *".PHP_EOL;
809
-                }
810
-
811
-                return $this->processRuleset($ref.DIRECTORY_SEPARATOR.'ruleset.xml', ($depth + 2));
812
-            } else {
813
-                // We are referencing a whole directory of sniffs.
814
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
815
-                    echo str_repeat("\t", $depth);
816
-                    echo "\t\t* rule is referencing a directory of sniffs *".PHP_EOL;
817
-                    echo str_repeat("\t", $depth);
818
-                    echo "\t\tAdding sniff files from directory".PHP_EOL;
819
-                }
820
-
821
-                return $this->expandSniffDirectory($ref, ($depth + 1));
822
-            }
823
-        } else {
824
-            if (is_file($ref) === false) {
825
-                $error = "Referenced sniff \"$ref\" does not exist";
826
-                throw new RuntimeException($error);
827
-            }
828
-
829
-            if (substr($ref, -9) === 'Sniff.php') {
830
-                // A single sniff.
831
-                return [$ref];
832
-            } else {
833
-                // Assume an external ruleset.xml file.
834
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
835
-                    echo str_repeat("\t", $depth);
836
-                    echo "\t\t* rule is referencing a standard using ruleset path; processing *".PHP_EOL;
837
-                }
838
-
839
-                return $this->processRuleset($ref, ($depth + 2));
840
-            }
841
-        }//end if
842
-
843
-    }//end expandRulesetReference()
844
-
845
-
846
-    /**
847
-     * Processes a rule from a ruleset XML file, overriding built-in defaults.
848
-     *
849
-     * @param \SimpleXMLElement $rule      The rule object from a ruleset XML file.
850
-     * @param string[]          $newSniffs An array of sniffs that got included by this rule.
851
-     * @param int               $depth     How many nested processing steps we are in.
852
-     *                                     This is only used for debug output.
853
-     *
854
-     * @return void
855
-     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If rule settings are invalid.
856
-     */
857
-    private function processRule($rule, $newSniffs, $depth=0)
858
-    {
859
-        $ref  = (string) $rule['ref'];
860
-        $todo = [$ref];
861
-
862
-        $parts = explode('.', $ref);
863
-        if (count($parts) <= 2) {
864
-            // We are processing a standard or a category of sniffs.
865
-            foreach ($newSniffs as $sniffFile) {
866
-                $parts         = explode(DIRECTORY_SEPARATOR, $sniffFile);
867
-                $sniffName     = array_pop($parts);
868
-                $sniffCategory = array_pop($parts);
869
-                array_pop($parts);
870
-                $sniffStandard = array_pop($parts);
871
-                $todo[]        = $sniffStandard.'.'.$sniffCategory.'.'.substr($sniffName, 0, -9);
872
-            }
873
-        }
874
-
875
-        foreach ($todo as $code) {
876
-            // Custom severity.
877
-            if (isset($rule->severity) === true
878
-                && $this->shouldProcessElement($rule->severity) === true
879
-            ) {
880
-                if (isset($this->ruleset[$code]) === false) {
881
-                    $this->ruleset[$code] = [];
882
-                }
883
-
884
-                $this->ruleset[$code]['severity'] = (int) $rule->severity;
885
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
886
-                    echo str_repeat("\t", $depth);
887
-                    echo "\t\t=> severity set to ".(int) $rule->severity;
888
-                    if ($code !== $ref) {
889
-                        echo " for $code";
890
-                    }
891
-
892
-                    echo PHP_EOL;
893
-                }
894
-            }
895
-
896
-            // Custom message type.
897
-            if (isset($rule->type) === true
898
-                && $this->shouldProcessElement($rule->type) === true
899
-            ) {
900
-                if (isset($this->ruleset[$code]) === false) {
901
-                    $this->ruleset[$code] = [];
902
-                }
903
-
904
-                $type = strtolower((string) $rule->type);
905
-                if ($type !== 'error' && $type !== 'warning') {
906
-                    throw new RuntimeException("Message type \"$type\" is invalid; must be \"error\" or \"warning\"");
907
-                }
908
-
909
-                $this->ruleset[$code]['type'] = $type;
910
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
911
-                    echo str_repeat("\t", $depth);
912
-                    echo "\t\t=> message type set to ".(string) $rule->type;
913
-                    if ($code !== $ref) {
914
-                        echo " for $code";
915
-                    }
916
-
917
-                    echo PHP_EOL;
918
-                }
919
-            }//end if
920
-
921
-            // Custom message.
922
-            if (isset($rule->message) === true
923
-                && $this->shouldProcessElement($rule->message) === true
924
-            ) {
925
-                if (isset($this->ruleset[$code]) === false) {
926
-                    $this->ruleset[$code] = [];
927
-                }
928
-
929
-                $this->ruleset[$code]['message'] = (string) $rule->message;
930
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
931
-                    echo str_repeat("\t", $depth);
932
-                    echo "\t\t=> message set to ".(string) $rule->message;
933
-                    if ($code !== $ref) {
934
-                        echo " for $code";
935
-                    }
936
-
937
-                    echo PHP_EOL;
938
-                }
939
-            }
940
-
941
-            // Custom properties.
942
-            if (isset($rule->properties) === true
943
-                && $this->shouldProcessElement($rule->properties) === true
944
-            ) {
945
-                foreach ($rule->properties->property as $prop) {
946
-                    if ($this->shouldProcessElement($prop) === false) {
947
-                        continue;
948
-                    }
949
-
950
-                    if (isset($this->ruleset[$code]) === false) {
951
-                        $this->ruleset[$code] = [
952
-                            'properties' => [],
953
-                        ];
954
-                    } else if (isset($this->ruleset[$code]['properties']) === false) {
955
-                        $this->ruleset[$code]['properties'] = [];
956
-                    }
957
-
958
-                    $name = (string) $prop['name'];
959
-                    if (isset($prop['type']) === true
960
-                        && (string) $prop['type'] === 'array'
961
-                    ) {
962
-                        $values = [];
963
-                        if (isset($prop['extend']) === true
964
-                            && (string) $prop['extend'] === 'true'
965
-                            && isset($this->ruleset[$code]['properties'][$name]) === true
966
-                        ) {
967
-                            $values = $this->ruleset[$code]['properties'][$name];
968
-                        }
969
-
970
-                        if (isset($prop->element) === true) {
971
-                            $printValue = '';
972
-                            foreach ($prop->element as $element) {
973
-                                if ($this->shouldProcessElement($element) === false) {
974
-                                    continue;
975
-                                }
976
-
977
-                                $value = (string) $element['value'];
978
-                                if (isset($element['key']) === true) {
979
-                                    $key          = (string) $element['key'];
980
-                                    $values[$key] = $value;
981
-                                    $printValue  .= $key.'=>'.$value.',';
982
-                                } else {
983
-                                    $values[]    = $value;
984
-                                    $printValue .= $value.',';
985
-                                }
986
-                            }
987
-
988
-                            $printValue = rtrim($printValue, ',');
989
-                        } else {
990
-                            $value      = (string) $prop['value'];
991
-                            $printValue = $value;
992
-                            foreach (explode(',', $value) as $val) {
993
-                                list($k, $v) = explode('=>', $val.'=>');
994
-                                if ($v !== '') {
995
-                                    $values[trim($k)] = trim($v);
996
-                                } else {
997
-                                    $values[] = trim($k);
998
-                                }
999
-                            }
1000
-                        }//end if
1001
-
1002
-                        $this->ruleset[$code]['properties'][$name] = $values;
1003
-                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1004
-                            echo str_repeat("\t", $depth);
1005
-                            echo "\t\t=> array property \"$name\" set to \"$printValue\"";
1006
-                            if ($code !== $ref) {
1007
-                                echo " for $code";
1008
-                            }
1009
-
1010
-                            echo PHP_EOL;
1011
-                        }
1012
-                    } else {
1013
-                        $this->ruleset[$code]['properties'][$name] = (string) $prop['value'];
1014
-                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1015
-                            echo str_repeat("\t", $depth);
1016
-                            echo "\t\t=> property \"$name\" set to \"".(string) $prop['value'].'"';
1017
-                            if ($code !== $ref) {
1018
-                                echo " for $code";
1019
-                            }
1020
-
1021
-                            echo PHP_EOL;
1022
-                        }
1023
-                    }//end if
1024
-                }//end foreach
1025
-            }//end if
1026
-
1027
-            // Ignore patterns.
1028
-            foreach ($rule->{'exclude-pattern'} as $pattern) {
1029
-                if ($this->shouldProcessElement($pattern) === false) {
1030
-                    continue;
1031
-                }
1032
-
1033
-                if (isset($this->ignorePatterns[$code]) === false) {
1034
-                    $this->ignorePatterns[$code] = [];
1035
-                }
1036
-
1037
-                if (isset($pattern['type']) === false) {
1038
-                    $pattern['type'] = 'absolute';
1039
-                }
1040
-
1041
-                $this->ignorePatterns[$code][(string) $pattern] = (string) $pattern['type'];
1042
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1043
-                    echo str_repeat("\t", $depth);
1044
-                    echo "\t\t=> added rule-specific ".(string) $pattern['type'].' ignore pattern';
1045
-                    if ($code !== $ref) {
1046
-                        echo " for $code";
1047
-                    }
1048
-
1049
-                    echo ': '.(string) $pattern.PHP_EOL;
1050
-                }
1051
-            }//end foreach
1052
-
1053
-            // Include patterns.
1054
-            foreach ($rule->{'include-pattern'} as $pattern) {
1055
-                if ($this->shouldProcessElement($pattern) === false) {
1056
-                    continue;
1057
-                }
1058
-
1059
-                if (isset($this->includePatterns[$code]) === false) {
1060
-                    $this->includePatterns[$code] = [];
1061
-                }
1062
-
1063
-                if (isset($pattern['type']) === false) {
1064
-                    $pattern['type'] = 'absolute';
1065
-                }
1066
-
1067
-                $this->includePatterns[$code][(string) $pattern] = (string) $pattern['type'];
1068
-                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1069
-                    echo str_repeat("\t", $depth);
1070
-                    echo "\t\t=> added rule-specific ".(string) $pattern['type'].' include pattern';
1071
-                    if ($code !== $ref) {
1072
-                        echo " for $code";
1073
-                    }
1074
-
1075
-                    echo ': '.(string) $pattern.PHP_EOL;
1076
-                }
1077
-            }//end foreach
1078
-        }//end foreach
1079
-
1080
-    }//end processRule()
1081
-
1082
-
1083
-    /**
1084
-     * Determine if an element should be processed or ignored.
1085
-     *
1086
-     * @param \SimpleXMLElement $element An object from a ruleset XML file.
1087
-     *
1088
-     * @return bool
1089
-     */
1090
-    private function shouldProcessElement($element)
1091
-    {
1092
-        if (isset($element['phpcbf-only']) === false
1093
-            && isset($element['phpcs-only']) === false
1094
-        ) {
1095
-            // No exceptions are being made.
1096
-            return true;
1097
-        }
1098
-
1099
-        if (PHP_CODESNIFFER_CBF === true
1100
-            && isset($element['phpcbf-only']) === true
1101
-            && (string) $element['phpcbf-only'] === 'true'
1102
-        ) {
1103
-            return true;
1104
-        }
1105
-
1106
-        if (PHP_CODESNIFFER_CBF === false
1107
-            && isset($element['phpcs-only']) === true
1108
-            && (string) $element['phpcs-only'] === 'true'
1109
-        ) {
1110
-            return true;
1111
-        }
1112
-
1113
-        return false;
1114
-
1115
-    }//end shouldProcessElement()
1116
-
1117
-
1118
-    /**
1119
-     * Loads and stores sniffs objects used for sniffing files.
1120
-     *
1121
-     * @param array $files        Paths to the sniff files to register.
1122
-     * @param array $restrictions The sniff class names to restrict the allowed
1123
-     *                            listeners to.
1124
-     * @param array $exclusions   The sniff class names to exclude from the
1125
-     *                            listeners list.
1126
-     *
1127
-     * @return void
1128
-     */
1129
-    public function registerSniffs($files, $restrictions, $exclusions)
1130
-    {
1131
-        $listeners = [];
1132
-
1133
-        foreach ($files as $file) {
1134
-            // Work out where the position of /StandardName/Sniffs/... is
1135
-            // so we can determine what the class will be called.
1136
-            $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR);
1137
-            if ($sniffPos === false) {
1138
-                continue;
1139
-            }
1140
-
1141
-            $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR);
1142
-            if ($slashPos === false) {
1143
-                continue;
1144
-            }
1145
-
1146
-            $className   = Autoload::loadFile($file);
1147
-            $compareName = Util\Common::cleanSniffClass($className);
1148
-
1149
-            // If they have specified a list of sniffs to restrict to, check
1150
-            // to see if this sniff is allowed.
1151
-            if (empty($restrictions) === false
1152
-                && isset($restrictions[$compareName]) === false
1153
-            ) {
1154
-                continue;
1155
-            }
1156
-
1157
-            // If they have specified a list of sniffs to exclude, check
1158
-            // to see if this sniff is allowed.
1159
-            if (empty($exclusions) === false
1160
-                && isset($exclusions[$compareName]) === true
1161
-            ) {
1162
-                continue;
1163
-            }
1164
-
1165
-            // Skip abstract classes.
1166
-            $reflection = new \ReflectionClass($className);
1167
-            if ($reflection->isAbstract() === true) {
1168
-                continue;
1169
-            }
1170
-
1171
-            $listeners[$className] = $className;
1172
-
1173
-            if (PHP_CODESNIFFER_VERBOSITY > 2) {
1174
-                echo "Registered $className".PHP_EOL;
1175
-            }
1176
-        }//end foreach
1177
-
1178
-        $this->sniffs = $listeners;
1179
-
1180
-    }//end registerSniffs()
1181
-
1182
-
1183
-    /**
1184
-     * Populates the array of PHP_CodeSniffer_Sniff's for this file.
1185
-     *
1186
-     * @return void
1187
-     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If sniff registration fails.
1188
-     */
1189
-    public function populateTokenListeners()
1190
-    {
1191
-        // Construct a list of listeners indexed by token being listened for.
1192
-        $this->tokenListeners = [];
1193
-
1194
-        foreach ($this->sniffs as $sniffClass => $sniffObject) {
1195
-            $this->sniffs[$sniffClass] = null;
1196
-            $this->sniffs[$sniffClass] = new $sniffClass();
1197
-
1198
-            $sniffCode = Util\Common::getSniffCode($sniffClass);
1199
-            $this->sniffCodes[$sniffCode] = $sniffClass;
1200
-
1201
-            // Set custom properties.
1202
-            if (isset($this->ruleset[$sniffCode]['properties']) === true) {
1203
-                foreach ($this->ruleset[$sniffCode]['properties'] as $name => $value) {
1204
-                    $this->setSniffProperty($sniffClass, $name, $value);
1205
-                }
1206
-            }
1207
-
1208
-            $tokenizers = [];
1209
-            $vars       = get_class_vars($sniffClass);
1210
-            if (isset($vars['supportedTokenizers']) === true) {
1211
-                foreach ($vars['supportedTokenizers'] as $tokenizer) {
1212
-                    $tokenizers[$tokenizer] = $tokenizer;
1213
-                }
1214
-            } else {
1215
-                $tokenizers = ['PHP' => 'PHP'];
1216
-            }
1217
-
1218
-            $tokens = $this->sniffs[$sniffClass]->register();
1219
-            if (is_array($tokens) === false) {
1220
-                $msg = "Sniff $sniffClass register() method must return an array";
1221
-                throw new RuntimeException($msg);
1222
-            }
1223
-
1224
-            $ignorePatterns = [];
1225
-            $patterns       = $this->getIgnorePatterns($sniffCode);
1226
-            foreach ($patterns as $pattern => $type) {
1227
-                $replacements = [
1228
-                    '\\,' => ',',
1229
-                    '*'   => '.*',
1230
-                ];
1231
-
1232
-                $ignorePatterns[] = strtr($pattern, $replacements);
1233
-            }
1234
-
1235
-            $includePatterns = [];
1236
-            $patterns        = $this->getIncludePatterns($sniffCode);
1237
-            foreach ($patterns as $pattern => $type) {
1238
-                $replacements = [
1239
-                    '\\,' => ',',
1240
-                    '*'   => '.*',
1241
-                ];
1242
-
1243
-                $includePatterns[] = strtr($pattern, $replacements);
1244
-            }
1245
-
1246
-            foreach ($tokens as $token) {
1247
-                if (isset($this->tokenListeners[$token]) === false) {
1248
-                    $this->tokenListeners[$token] = [];
1249
-                }
1250
-
1251
-                if (isset($this->tokenListeners[$token][$sniffClass]) === false) {
1252
-                    $this->tokenListeners[$token][$sniffClass] = [
1253
-                        'class'      => $sniffClass,
1254
-                        'source'     => $sniffCode,
1255
-                        'tokenizers' => $tokenizers,
1256
-                        'ignore'     => $ignorePatterns,
1257
-                        'include'    => $includePatterns,
1258
-                    ];
1259
-                }
1260
-            }
1261
-        }//end foreach
1262
-
1263
-    }//end populateTokenListeners()
1264
-
1265
-
1266
-    /**
1267
-     * Set a single property for a sniff.
1268
-     *
1269
-     * @param string $sniffClass The class name of the sniff.
1270
-     * @param string $name       The name of the property to change.
1271
-     * @param string $value      The new value of the property.
1272
-     *
1273
-     * @return void
1274
-     */
1275
-    public function setSniffProperty($sniffClass, $name, $value)
1276
-    {
1277
-        // Setting a property for a sniff we are not using.
1278
-        if (isset($this->sniffs[$sniffClass]) === false) {
1279
-            return;
1280
-        }
1281
-
1282
-        $name = trim($name);
1283
-        if (is_string($value) === true) {
1284
-            $value = trim($value);
1285
-        }
1286
-
1287
-        if ($value === '') {
1288
-            $value = null;
1289
-        }
1290
-
1291
-        // Special case for booleans.
1292
-        if ($value === 'true') {
1293
-            $value = true;
1294
-        } else if ($value === 'false') {
1295
-            $value = false;
1296
-        } else if (substr($name, -2) === '[]') {
1297
-            $name   = substr($name, 0, -2);
1298
-            $values = [];
1299
-            if ($value !== null) {
1300
-                foreach (explode(',', $value) as $val) {
1301
-                    list($k, $v) = explode('=>', $val.'=>');
1302
-                    if ($v !== '') {
1303
-                        $values[trim($k)] = trim($v);
1304
-                    } else {
1305
-                        $values[] = trim($k);
1306
-                    }
1307
-                }
1308
-            }
1309
-
1310
-            $value = $values;
1311
-        }
1312
-
1313
-        $this->sniffs[$sniffClass]->$name = $value;
1314
-
1315
-    }//end setSniffProperty()
1316
-
1317
-
1318
-    /**
1319
-     * Gets the array of ignore patterns.
1320
-     *
1321
-     * Optionally takes a listener to get ignore patterns specified
1322
-     * for that sniff only.
1323
-     *
1324
-     * @param string $listener The listener to get patterns for. If NULL, all
1325
-     *                         patterns are returned.
1326
-     *
1327
-     * @return array
1328
-     */
1329
-    public function getIgnorePatterns($listener=null)
1330
-    {
1331
-        if ($listener === null) {
1332
-            return $this->ignorePatterns;
1333
-        }
1334
-
1335
-        if (isset($this->ignorePatterns[$listener]) === true) {
1336
-            return $this->ignorePatterns[$listener];
1337
-        }
1338
-
1339
-        return [];
1340
-
1341
-    }//end getIgnorePatterns()
1342
-
1343
-
1344
-    /**
1345
-     * Gets the array of include patterns.
1346
-     *
1347
-     * Optionally takes a listener to get include patterns specified
1348
-     * for that sniff only.
1349
-     *
1350
-     * @param string $listener The listener to get patterns for. If NULL, all
1351
-     *                         patterns are returned.
1352
-     *
1353
-     * @return array
1354
-     */
1355
-    public function getIncludePatterns($listener=null)
1356
-    {
1357
-        if ($listener === null) {
1358
-            return $this->includePatterns;
1359
-        }
1360
-
1361
-        if (isset($this->includePatterns[$listener]) === true) {
1362
-            return $this->includePatterns[$listener];
1363
-        }
1364
-
1365
-        return [];
1366
-
1367
-    }//end getIncludePatterns()
20
+	/**
21
+	 * The name of the coding standard being used.
22
+	 *
23
+	 * If a top-level standard includes other standards, or sniffs
24
+	 * from other standards, only the name of the top-level standard
25
+	 * will be stored in here.
26
+	 *
27
+	 * If multiple top-level standards are being loaded into
28
+	 * a single ruleset object, this will store a comma separated list
29
+	 * of the top-level standard names.
30
+	 *
31
+	 * @var string
32
+	 */
33
+	public $name = '';
34
+
35
+	/**
36
+	 * A list of file paths for the ruleset files being used.
37
+	 *
38
+	 * @var string[]
39
+	 */
40
+	public $paths = [];
41
+
42
+	/**
43
+	 * A list of regular expressions used to ignore specific sniffs for files and folders.
44
+	 *
45
+	 * Is also used to set global exclude patterns.
46
+	 * The key is the regular expression and the value is the type
47
+	 * of ignore pattern (absolute or relative).
48
+	 *
49
+	 * @var array<string, string>
50
+	 */
51
+	public $ignorePatterns = [];
52
+
53
+	/**
54
+	 * A list of regular expressions used to include specific sniffs for files and folders.
55
+	 *
56
+	 * The key is the sniff code and the value is an array with
57
+	 * the key being a regular expression and the value is the type
58
+	 * of ignore pattern (absolute or relative).
59
+	 *
60
+	 * @var array<string, array<string, string>>
61
+	 */
62
+	public $includePatterns = [];
63
+
64
+	/**
65
+	 * An array of sniff objects that are being used to check files.
66
+	 *
67
+	 * The key is the fully qualified name of the sniff class
68
+	 * and the value is the sniff object.
69
+	 *
70
+	 * @var array<string, \PHP_CodeSniffer\Sniffs\Sniff>
71
+	 */
72
+	public $sniffs = [];
73
+
74
+	/**
75
+	 * A mapping of sniff codes to fully qualified class names.
76
+	 *
77
+	 * The key is the sniff code and the value
78
+	 * is the fully qualified name of the sniff class.
79
+	 *
80
+	 * @var array<string, string>
81
+	 */
82
+	public $sniffCodes = [];
83
+
84
+	/**
85
+	 * An array of token types and the sniffs that are listening for them.
86
+	 *
87
+	 * The key is the token name being listened for and the value
88
+	 * is the sniff object.
89
+	 *
90
+	 * @var array<int, \PHP_CodeSniffer\Sniffs\Sniff>
91
+	 */
92
+	public $tokenListeners = [];
93
+
94
+	/**
95
+	 * An array of rules from the ruleset.xml file.
96
+	 *
97
+	 * It may be empty, indicating that the ruleset does not override
98
+	 * any of the default sniff settings.
99
+	 *
100
+	 * @var array<string, mixed>
101
+	 */
102
+	public $ruleset = [];
103
+
104
+	/**
105
+	 * The directories that the processed rulesets are in.
106
+	 *
107
+	 * @var string[]
108
+	 */
109
+	protected $rulesetDirs = [];
110
+
111
+	/**
112
+	 * The config data for the run.
113
+	 *
114
+	 * @var \PHP_CodeSniffer\Config
115
+	 */
116
+	private $config = null;
117
+
118
+
119
+	/**
120
+	 * Initialise the ruleset that the run will use.
121
+	 *
122
+	 * @param \PHP_CodeSniffer\Config $config The config data for the run.
123
+	 *
124
+	 * @return void
125
+	 */
126
+	public function __construct(Config $config)
127
+	{
128
+		// Ignore sniff restrictions if caching is on.
129
+		$restrictions = [];
130
+		$exclusions   = [];
131
+		if ($config->cache === false) {
132
+			$restrictions = $config->sniffs;
133
+			$exclusions   = $config->exclude;
134
+		}
135
+
136
+		$this->config = $config;
137
+		$sniffs       = [];
138
+
139
+		$standardPaths = [];
140
+		foreach ($config->standards as $standard) {
141
+			$installed = Util\Standards::getInstalledStandardPath($standard);
142
+			if ($installed === null) {
143
+				$standard = Util\Common::realpath($standard);
144
+				if (is_dir($standard) === true
145
+					&& is_file(Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true
146
+				) {
147
+					$standard = Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml');
148
+				}
149
+			} else {
150
+				$standard = $installed;
151
+			}
152
+
153
+			$standardPaths[] = $standard;
154
+		}
155
+
156
+		foreach ($standardPaths as $standard) {
157
+			$ruleset = @simplexml_load_string(file_get_contents($standard));
158
+			if ($ruleset !== false) {
159
+				$standardName = (string) $ruleset['name'];
160
+				if ($this->name !== '') {
161
+					$this->name .= ', ';
162
+				}
163
+
164
+				$this->name .= $standardName;
165
+
166
+				// Allow autoloading of custom files inside this standard.
167
+				if (isset($ruleset['namespace']) === true) {
168
+					$namespace = (string) $ruleset['namespace'];
169
+				} else {
170
+					$namespace = basename(dirname($standard));
171
+				}
172
+
173
+				Autoload::addSearchPath(dirname($standard), $namespace);
174
+			}
175
+
176
+			if (defined('PHP_CODESNIFFER_IN_TESTS') === true && empty($restrictions) === false) {
177
+				// In unit tests, only register the sniffs that the test wants and not the entire standard.
178
+				try {
179
+					foreach ($restrictions as $restriction) {
180
+						$sniffs = array_merge($sniffs, $this->expandRulesetReference($restriction, dirname($standard)));
181
+					}
182
+				} catch (RuntimeException $e) {
183
+					// Sniff reference could not be expanded, which probably means this
184
+					// is an installed standard. Let the unit test system take care of
185
+					// setting the correct sniff for testing.
186
+					return;
187
+				}
188
+
189
+				break;
190
+			}
191
+
192
+			if (PHP_CODESNIFFER_VERBOSITY === 1) {
193
+				echo "Registering sniffs in the $standardName standard... ";
194
+				if (count($config->standards) > 1 || PHP_CODESNIFFER_VERBOSITY > 2) {
195
+					echo PHP_EOL;
196
+				}
197
+			}
198
+
199
+			$sniffs = array_merge($sniffs, $this->processRuleset($standard));
200
+		}//end foreach
201
+
202
+		$sniffRestrictions = [];
203
+		foreach ($restrictions as $sniffCode) {
204
+			$parts     = explode('.', strtolower($sniffCode));
205
+			$sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff';
206
+			$sniffRestrictions[$sniffName] = true;
207
+		}
208
+
209
+		$sniffExclusions = [];
210
+		foreach ($exclusions as $sniffCode) {
211
+			$parts     = explode('.', strtolower($sniffCode));
212
+			$sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff';
213
+			$sniffExclusions[$sniffName] = true;
214
+		}
215
+
216
+		$this->registerSniffs($sniffs, $sniffRestrictions, $sniffExclusions);
217
+		$this->populateTokenListeners();
218
+
219
+		$numSniffs = count($this->sniffs);
220
+		if (PHP_CODESNIFFER_VERBOSITY === 1) {
221
+			echo "DONE ($numSniffs sniffs registered)".PHP_EOL;
222
+		}
223
+
224
+		if ($numSniffs === 0) {
225
+			throw new RuntimeException('No sniffs were registered');
226
+		}
227
+
228
+	}//end __construct()
229
+
230
+
231
+	/**
232
+	 * Prints a report showing the sniffs contained in a standard.
233
+	 *
234
+	 * @return void
235
+	 */
236
+	public function explain()
237
+	{
238
+		$sniffs = array_keys($this->sniffCodes);
239
+		sort($sniffs);
240
+
241
+		ob_start();
242
+
243
+		$lastStandard = null;
244
+		$lastCount    = '';
245
+		$sniffCount   = count($sniffs);
246
+
247
+		// Add a dummy entry to the end so we loop
248
+		// one last time and clear the output buffer.
249
+		$sniffs[] = '';
250
+
251
+		echo PHP_EOL."The $this->name standard contains $sniffCount sniffs".PHP_EOL;
252
+
253
+		ob_start();
254
+
255
+		foreach ($sniffs as $i => $sniff) {
256
+			if ($i === $sniffCount) {
257
+				$currentStandard = null;
258
+			} else {
259
+				$currentStandard = substr($sniff, 0, strpos($sniff, '.'));
260
+				if ($lastStandard === null) {
261
+					$lastStandard = $currentStandard;
262
+				}
263
+			}
264
+
265
+			if ($currentStandard !== $lastStandard) {
266
+				$sniffList = ob_get_contents();
267
+				ob_end_clean();
268
+
269
+				echo PHP_EOL.$lastStandard.' ('.$lastCount.' sniff';
270
+				if ($lastCount > 1) {
271
+					echo 's';
272
+				}
273
+
274
+				echo ')'.PHP_EOL;
275
+				echo str_repeat('-', (strlen($lastStandard.$lastCount) + 10));
276
+				echo PHP_EOL;
277
+				echo $sniffList;
278
+
279
+				$lastStandard = $currentStandard;
280
+				$lastCount    = 0;
281
+
282
+				if ($currentStandard === null) {
283
+					break;
284
+				}
285
+
286
+				ob_start();
287
+			}//end if
288
+
289
+			echo '  '.$sniff.PHP_EOL;
290
+			$lastCount++;
291
+		}//end foreach
292
+
293
+	}//end explain()
294
+
295
+
296
+	/**
297
+	 * Processes a single ruleset and returns a list of the sniffs it represents.
298
+	 *
299
+	 * Rules founds within the ruleset are processed immediately, but sniff classes
300
+	 * are not registered by this method.
301
+	 *
302
+	 * @param string $rulesetPath The path to a ruleset XML file.
303
+	 * @param int    $depth       How many nested processing steps we are in. This
304
+	 *                            is only used for debug output.
305
+	 *
306
+	 * @return string[]
307
+	 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the ruleset path is invalid.
308
+	 */
309
+	public function processRuleset($rulesetPath, $depth=0)
310
+	{
311
+		$rulesetPath = Util\Common::realpath($rulesetPath);
312
+		if (PHP_CODESNIFFER_VERBOSITY > 1) {
313
+			echo str_repeat("\t", $depth);
314
+			echo 'Processing ruleset '.Util\Common::stripBasepath($rulesetPath, $this->config->basepath).PHP_EOL;
315
+		}
316
+
317
+		libxml_use_internal_errors(true);
318
+		$ruleset = simplexml_load_string(file_get_contents($rulesetPath));
319
+		if ($ruleset === false) {
320
+			$errorMsg = "Ruleset $rulesetPath is not valid".PHP_EOL;
321
+			$errors   = libxml_get_errors();
322
+			foreach ($errors as $error) {
323
+				$errorMsg .= '- On line '.$error->line.', column '.$error->column.': '.$error->message;
324
+			}
325
+
326
+			libxml_clear_errors();
327
+			throw new RuntimeException($errorMsg);
328
+		}
329
+
330
+		libxml_use_internal_errors(false);
331
+
332
+		$ownSniffs      = [];
333
+		$includedSniffs = [];
334
+		$excludedSniffs = [];
335
+
336
+		$this->paths[]       = $rulesetPath;
337
+		$rulesetDir          = dirname($rulesetPath);
338
+		$this->rulesetDirs[] = $rulesetDir;
339
+
340
+		$sniffDir = $rulesetDir.DIRECTORY_SEPARATOR.'Sniffs';
341
+		if (is_dir($sniffDir) === true) {
342
+			if (PHP_CODESNIFFER_VERBOSITY > 1) {
343
+				echo str_repeat("\t", $depth);
344
+				echo "\tAdding sniff files from ".Util\Common::stripBasepath($sniffDir, $this->config->basepath).' directory'.PHP_EOL;
345
+			}
346
+
347
+			$ownSniffs = $this->expandSniffDirectory($sniffDir, $depth);
348
+		}
349
+
350
+		// Included custom autoloaders.
351
+		foreach ($ruleset->{'autoload'} as $autoload) {
352
+			if ($this->shouldProcessElement($autoload) === false) {
353
+				continue;
354
+			}
355
+
356
+			$autoloadPath = (string) $autoload;
357
+			if (is_file($autoloadPath) === false) {
358
+				$autoloadPath = Util\Common::realPath(dirname($rulesetPath).DIRECTORY_SEPARATOR.$autoloadPath);
359
+			}
360
+
361
+			if ($autoloadPath === false) {
362
+				throw new RuntimeException('The specified autoload file "'.$autoload.'" does not exist');
363
+			}
364
+
365
+			include_once $autoloadPath;
366
+
367
+			if (PHP_CODESNIFFER_VERBOSITY > 1) {
368
+				echo str_repeat("\t", $depth);
369
+				echo "\t=> included autoloader $autoloadPath".PHP_EOL;
370
+			}
371
+		}//end foreach
372
+
373
+		// Process custom sniff config settings.
374
+		foreach ($ruleset->{'config'} as $config) {
375
+			if ($this->shouldProcessElement($config) === false) {
376
+				continue;
377
+			}
378
+
379
+			Config::setConfigData((string) $config['name'], (string) $config['value'], true);
380
+			if (PHP_CODESNIFFER_VERBOSITY > 1) {
381
+				echo str_repeat("\t", $depth);
382
+				echo "\t=> set config value ".(string) $config['name'].': '.(string) $config['value'].PHP_EOL;
383
+			}
384
+		}
385
+
386
+		foreach ($ruleset->rule as $rule) {
387
+			if (isset($rule['ref']) === false
388
+				|| $this->shouldProcessElement($rule) === false
389
+			) {
390
+				continue;
391
+			}
392
+
393
+			if (PHP_CODESNIFFER_VERBOSITY > 1) {
394
+				echo str_repeat("\t", $depth);
395
+				echo "\tProcessing rule \"".$rule['ref'].'"'.PHP_EOL;
396
+			}
397
+
398
+			$expandedSniffs = $this->expandRulesetReference((string) $rule['ref'], $rulesetDir, $depth);
399
+			$newSniffs      = array_diff($expandedSniffs, $includedSniffs);
400
+			$includedSniffs = array_merge($includedSniffs, $expandedSniffs);
401
+
402
+			$parts = explode('.', $rule['ref']);
403
+			if (count($parts) === 4
404
+				&& $parts[0] !== ''
405
+				&& $parts[1] !== ''
406
+				&& $parts[2] !== ''
407
+			) {
408
+				$sniffCode = $parts[0].'.'.$parts[1].'.'.$parts[2];
409
+				if (isset($this->ruleset[$sniffCode]['severity']) === true
410
+					&& $this->ruleset[$sniffCode]['severity'] === 0
411
+				) {
412
+					// This sniff code has already been turned off, but now
413
+					// it is being explicitly included again, so turn it back on.
414
+					$this->ruleset[(string) $rule['ref']]['severity'] = 5;
415
+					if (PHP_CODESNIFFER_VERBOSITY > 1) {
416
+						echo str_repeat("\t", $depth);
417
+						echo "\t\t* disabling sniff exclusion for specific message code *".PHP_EOL;
418
+						echo str_repeat("\t", $depth);
419
+						echo "\t\t=> severity set to 5".PHP_EOL;
420
+					}
421
+				} else if (empty($newSniffs) === false) {
422
+					$newSniff = $newSniffs[0];
423
+					if (in_array($newSniff, $ownSniffs, true) === false) {
424
+						// Including a sniff that hasn't been included higher up, but
425
+						// only including a single message from it. So turn off all messages in
426
+						// the sniff, except this one.
427
+						$this->ruleset[$sniffCode]['severity']            = 0;
428
+						$this->ruleset[(string) $rule['ref']]['severity'] = 5;
429
+						if (PHP_CODESNIFFER_VERBOSITY > 1) {
430
+							echo str_repeat("\t", $depth);
431
+							echo "\t\tExcluding sniff \"".$sniffCode.'" except for "'.$parts[3].'"'.PHP_EOL;
432
+						}
433
+					}
434
+				}//end if
435
+			}//end if
436
+
437
+			if (isset($rule->exclude) === true) {
438
+				foreach ($rule->exclude as $exclude) {
439
+					if (isset($exclude['name']) === false) {
440
+						if (PHP_CODESNIFFER_VERBOSITY > 1) {
441
+							echo str_repeat("\t", $depth);
442
+							echo "\t\t* ignoring empty exclude rule *".PHP_EOL;
443
+							echo "\t\t\t=> ".$exclude->asXML().PHP_EOL;
444
+						}
445
+
446
+						continue;
447
+					}
448
+
449
+					if ($this->shouldProcessElement($exclude) === false) {
450
+						continue;
451
+					}
452
+
453
+					if (PHP_CODESNIFFER_VERBOSITY > 1) {
454
+						echo str_repeat("\t", $depth);
455
+						echo "\t\tExcluding rule \"".$exclude['name'].'"'.PHP_EOL;
456
+					}
457
+
458
+					// Check if a single code is being excluded, which is a shortcut
459
+					// for setting the severity of the message to 0.
460
+					$parts = explode('.', $exclude['name']);
461
+					if (count($parts) === 4) {
462
+						$this->ruleset[(string) $exclude['name']]['severity'] = 0;
463
+						if (PHP_CODESNIFFER_VERBOSITY > 1) {
464
+							echo str_repeat("\t", $depth);
465
+							echo "\t\t=> severity set to 0".PHP_EOL;
466
+						}
467
+					} else {
468
+						$excludedSniffs = array_merge(
469
+							$excludedSniffs,
470
+							$this->expandRulesetReference((string) $exclude['name'], $rulesetDir, ($depth + 1))
471
+						);
472
+					}
473
+				}//end foreach
474
+			}//end if
475
+
476
+			$this->processRule($rule, $newSniffs, $depth);
477
+		}//end foreach
478
+
479
+		// Process custom command line arguments.
480
+		$cliArgs = [];
481
+		foreach ($ruleset->{'arg'} as $arg) {
482
+			if ($this->shouldProcessElement($arg) === false) {
483
+				continue;
484
+			}
485
+
486
+			if (isset($arg['name']) === true) {
487
+				$argString = '--'.(string) $arg['name'];
488
+				if (isset($arg['value']) === true) {
489
+					$argString .= '='.(string) $arg['value'];
490
+				}
491
+			} else {
492
+				$argString = '-'.(string) $arg['value'];
493
+			}
494
+
495
+			$cliArgs[] = $argString;
496
+
497
+			if (PHP_CODESNIFFER_VERBOSITY > 1) {
498
+				echo str_repeat("\t", $depth);
499
+				echo "\t=> set command line value $argString".PHP_EOL;
500
+			}
501
+		}//end foreach
502
+
503
+		// Set custom php ini values as CLI args.
504
+		foreach ($ruleset->{'ini'} as $arg) {
505
+			if ($this->shouldProcessElement($arg) === false) {
506
+				continue;
507
+			}
508
+
509
+			if (isset($arg['name']) === false) {
510
+				continue;
511
+			}
512
+
513
+			$name      = (string) $arg['name'];
514
+			$argString = $name;
515
+			if (isset($arg['value']) === true) {
516
+				$value      = (string) $arg['value'];
517
+				$argString .= "=$value";
518
+			} else {
519
+				$value = 'true';
520
+			}
521
+
522
+			$cliArgs[] = '-d';
523
+			$cliArgs[] = $argString;
524
+
525
+			if (PHP_CODESNIFFER_VERBOSITY > 1) {
526
+				echo str_repeat("\t", $depth);
527
+				echo "\t=> set PHP ini value $name to $value".PHP_EOL;
528
+			}
529
+		}//end foreach
530
+
531
+		if (empty($this->config->files) === true) {
532
+			// Process hard-coded file paths.
533
+			foreach ($ruleset->{'file'} as $file) {
534
+				$file      = (string) $file;
535
+				$cliArgs[] = $file;
536
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
537
+					echo str_repeat("\t", $depth);
538
+					echo "\t=> added \"$file\" to the file list".PHP_EOL;
539
+				}
540
+			}
541
+		}
542
+
543
+		if (empty($cliArgs) === false) {
544
+			// Change the directory so all relative paths are worked
545
+			// out based on the location of the ruleset instead of
546
+			// the location of the user.
547
+			$inPhar = Util\Common::isPharFile($rulesetDir);
548
+			if ($inPhar === false) {
549
+				$currentDir = getcwd();
550
+				chdir($rulesetDir);
551
+			}
552
+
553
+			$this->config->setCommandLineValues($cliArgs);
554
+
555
+			if ($inPhar === false) {
556
+				chdir($currentDir);
557
+			}
558
+		}
559
+
560
+		// Process custom ignore pattern rules.
561
+		foreach ($ruleset->{'exclude-pattern'} as $pattern) {
562
+			if ($this->shouldProcessElement($pattern) === false) {
563
+				continue;
564
+			}
565
+
566
+			if (isset($pattern['type']) === false) {
567
+				$pattern['type'] = 'absolute';
568
+			}
569
+
570
+			$this->ignorePatterns[(string) $pattern] = (string) $pattern['type'];
571
+			if (PHP_CODESNIFFER_VERBOSITY > 1) {
572
+				echo str_repeat("\t", $depth);
573
+				echo "\t=> added global ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL;
574
+			}
575
+		}
576
+
577
+		$includedSniffs = array_unique(array_merge($ownSniffs, $includedSniffs));
578
+		$excludedSniffs = array_unique($excludedSniffs);
579
+
580
+		if (PHP_CODESNIFFER_VERBOSITY > 1) {
581
+			$included = count($includedSniffs);
582
+			$excluded = count($excludedSniffs);
583
+			echo str_repeat("\t", $depth);
584
+			echo "=> Ruleset processing complete; included $included sniffs and excluded $excluded".PHP_EOL;
585
+		}
586
+
587
+		// Merge our own sniff list with our externally included
588
+		// sniff list, but filter out any excluded sniffs.
589
+		$files = [];
590
+		foreach ($includedSniffs as $sniff) {
591
+			if (in_array($sniff, $excludedSniffs, true) === true) {
592
+				continue;
593
+			} else {
594
+				$files[] = Util\Common::realpath($sniff);
595
+			}
596
+		}
597
+
598
+		return $files;
599
+
600
+	}//end processRuleset()
601
+
602
+
603
+	/**
604
+	 * Expands a directory into a list of sniff files within.
605
+	 *
606
+	 * @param string $directory The path to a directory.
607
+	 * @param int    $depth     How many nested processing steps we are in. This
608
+	 *                          is only used for debug output.
609
+	 *
610
+	 * @return array
611
+	 */
612
+	private function expandSniffDirectory($directory, $depth=0)
613
+	{
614
+		$sniffs = [];
615
+
616
+		$rdi = new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
617
+		$di  = new \RecursiveIteratorIterator($rdi, 0, \RecursiveIteratorIterator::CATCH_GET_CHILD);
618
+
619
+		$dirLen = strlen($directory);
620
+
621
+		foreach ($di as $file) {
622
+			$filename = $file->getFilename();
623
+
624
+			// Skip hidden files.
625
+			if (substr($filename, 0, 1) === '.') {
626
+				continue;
627
+			}
628
+
629
+			// We are only interested in PHP and sniff files.
630
+			$fileParts = explode('.', $filename);
631
+			if (array_pop($fileParts) !== 'php') {
632
+				continue;
633
+			}
634
+
635
+			$basename = basename($filename, '.php');
636
+			if (substr($basename, -5) !== 'Sniff') {
637
+				continue;
638
+			}
639
+
640
+			$path = $file->getPathname();
641
+
642
+			// Skip files in hidden directories within the Sniffs directory of this
643
+			// standard. We use the offset with strpos() to allow hidden directories
644
+			// before, valid example:
645
+			// /home/foo/.composer/vendor/squiz/custom_tool/MyStandard/Sniffs/...
646
+			if (strpos($path, DIRECTORY_SEPARATOR.'.', $dirLen) !== false) {
647
+				continue;
648
+			}
649
+
650
+			if (PHP_CODESNIFFER_VERBOSITY > 1) {
651
+				echo str_repeat("\t", $depth);
652
+				echo "\t\t=> ".Util\Common::stripBasepath($path, $this->config->basepath).PHP_EOL;
653
+			}
654
+
655
+			$sniffs[] = $path;
656
+		}//end foreach
657
+
658
+		return $sniffs;
659
+
660
+	}//end expandSniffDirectory()
661
+
662
+
663
+	/**
664
+	 * Expands a ruleset reference into a list of sniff files.
665
+	 *
666
+	 * @param string $ref        The reference from the ruleset XML file.
667
+	 * @param string $rulesetDir The directory of the ruleset XML file, used to
668
+	 *                           evaluate relative paths.
669
+	 * @param int    $depth      How many nested processing steps we are in. This
670
+	 *                           is only used for debug output.
671
+	 *
672
+	 * @return array
673
+	 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the reference is invalid.
674
+	 */
675
+	private function expandRulesetReference($ref, $rulesetDir, $depth=0)
676
+	{
677
+		// Ignore internal sniffs codes as they are used to only
678
+		// hide and change internal messages.
679
+		if (substr($ref, 0, 9) === 'Internal.') {
680
+			if (PHP_CODESNIFFER_VERBOSITY > 1) {
681
+				echo str_repeat("\t", $depth);
682
+				echo "\t\t* ignoring internal sniff code *".PHP_EOL;
683
+			}
684
+
685
+			return [];
686
+		}
687
+
688
+		// As sniffs can't begin with a full stop, assume references in
689
+		// this format are relative paths and attempt to convert them
690
+		// to absolute paths. If this fails, let the reference run through
691
+		// the normal checks and have it fail as normal.
692
+		if (substr($ref, 0, 1) === '.') {
693
+			$realpath = Util\Common::realpath($rulesetDir.'/'.$ref);
694
+			if ($realpath !== false) {
695
+				$ref = $realpath;
696
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
697
+					echo str_repeat("\t", $depth);
698
+					echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
699
+				}
700
+			}
701
+		}
702
+
703
+		// As sniffs can't begin with a tilde, assume references in
704
+		// this format are relative to the user's home directory.
705
+		if (substr($ref, 0, 2) === '~/') {
706
+			$realpath = Util\Common::realpath($ref);
707
+			if ($realpath !== false) {
708
+				$ref = $realpath;
709
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
710
+					echo str_repeat("\t", $depth);
711
+					echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
712
+				}
713
+			}
714
+		}
715
+
716
+		if (is_file($ref) === true) {
717
+			if (substr($ref, -9) === 'Sniff.php') {
718
+				// A single external sniff.
719
+				$this->rulesetDirs[] = dirname(dirname(dirname($ref)));
720
+				return [$ref];
721
+			}
722
+		} else {
723
+			// See if this is a whole standard being referenced.
724
+			$path = Util\Standards::getInstalledStandardPath($ref);
725
+			if (Util\Common::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) {
726
+				// If the ruleset exists inside the phar file, use it.
727
+				if (file_exists($path.DIRECTORY_SEPARATOR.'ruleset.xml') === true) {
728
+					$path .= DIRECTORY_SEPARATOR.'ruleset.xml';
729
+				} else {
730
+					$path = null;
731
+				}
732
+			}
733
+
734
+			if ($path !== null) {
735
+				$ref = $path;
736
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
737
+					echo str_repeat("\t", $depth);
738
+					echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
739
+				}
740
+			} else if (is_dir($ref) === false) {
741
+				// Work out the sniff path.
742
+				$sepPos = strpos($ref, DIRECTORY_SEPARATOR);
743
+				if ($sepPos !== false) {
744
+					$stdName = substr($ref, 0, $sepPos);
745
+					$path    = substr($ref, $sepPos);
746
+				} else {
747
+					$parts   = explode('.', $ref);
748
+					$stdName = $parts[0];
749
+					if (count($parts) === 1) {
750
+						// A whole standard?
751
+						$path = '';
752
+					} else if (count($parts) === 2) {
753
+						// A directory of sniffs?
754
+						$path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1];
755
+					} else {
756
+						// A single sniff?
757
+						$path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1].DIRECTORY_SEPARATOR.$parts[2].'Sniff.php';
758
+					}
759
+				}
760
+
761
+				$newRef  = false;
762
+				$stdPath = Util\Standards::getInstalledStandardPath($stdName);
763
+				if ($stdPath !== null && $path !== '') {
764
+					if (Util\Common::isPharFile($stdPath) === true
765
+						&& strpos($stdPath, 'ruleset.xml') === false
766
+					) {
767
+						// Phar files can only return the directory,
768
+						// since ruleset can be omitted if building one standard.
769
+						$newRef = Util\Common::realpath($stdPath.$path);
770
+					} else {
771
+						$newRef = Util\Common::realpath(dirname($stdPath).$path);
772
+					}
773
+				}
774
+
775
+				if ($newRef === false) {
776
+					// The sniff is not locally installed, so check if it is being
777
+					// referenced as a remote sniff outside the install. We do this
778
+					// by looking through all directories where we have found ruleset
779
+					// files before, looking for ones for this particular standard,
780
+					// and seeing if it is in there.
781
+					foreach ($this->rulesetDirs as $dir) {
782
+						if (strtolower(basename($dir)) !== strtolower($stdName)) {
783
+							continue;
784
+						}
785
+
786
+						$newRef = Util\Common::realpath($dir.$path);
787
+
788
+						if ($newRef !== false) {
789
+							$ref = $newRef;
790
+						}
791
+					}
792
+				} else {
793
+					$ref = $newRef;
794
+				}
795
+
796
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
797
+					echo str_repeat("\t", $depth);
798
+					echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
799
+				}
800
+			}//end if
801
+		}//end if
802
+
803
+		if (is_dir($ref) === true) {
804
+			if (is_file($ref.DIRECTORY_SEPARATOR.'ruleset.xml') === true) {
805
+				// We are referencing an external coding standard.
806
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
807
+					echo str_repeat("\t", $depth);
808
+					echo "\t\t* rule is referencing a standard using directory name; processing *".PHP_EOL;
809
+				}
810
+
811
+				return $this->processRuleset($ref.DIRECTORY_SEPARATOR.'ruleset.xml', ($depth + 2));
812
+			} else {
813
+				// We are referencing a whole directory of sniffs.
814
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
815
+					echo str_repeat("\t", $depth);
816
+					echo "\t\t* rule is referencing a directory of sniffs *".PHP_EOL;
817
+					echo str_repeat("\t", $depth);
818
+					echo "\t\tAdding sniff files from directory".PHP_EOL;
819
+				}
820
+
821
+				return $this->expandSniffDirectory($ref, ($depth + 1));
822
+			}
823
+		} else {
824
+			if (is_file($ref) === false) {
825
+				$error = "Referenced sniff \"$ref\" does not exist";
826
+				throw new RuntimeException($error);
827
+			}
828
+
829
+			if (substr($ref, -9) === 'Sniff.php') {
830
+				// A single sniff.
831
+				return [$ref];
832
+			} else {
833
+				// Assume an external ruleset.xml file.
834
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
835
+					echo str_repeat("\t", $depth);
836
+					echo "\t\t* rule is referencing a standard using ruleset path; processing *".PHP_EOL;
837
+				}
838
+
839
+				return $this->processRuleset($ref, ($depth + 2));
840
+			}
841
+		}//end if
842
+
843
+	}//end expandRulesetReference()
844
+
845
+
846
+	/**
847
+	 * Processes a rule from a ruleset XML file, overriding built-in defaults.
848
+	 *
849
+	 * @param \SimpleXMLElement $rule      The rule object from a ruleset XML file.
850
+	 * @param string[]          $newSniffs An array of sniffs that got included by this rule.
851
+	 * @param int               $depth     How many nested processing steps we are in.
852
+	 *                                     This is only used for debug output.
853
+	 *
854
+	 * @return void
855
+	 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If rule settings are invalid.
856
+	 */
857
+	private function processRule($rule, $newSniffs, $depth=0)
858
+	{
859
+		$ref  = (string) $rule['ref'];
860
+		$todo = [$ref];
861
+
862
+		$parts = explode('.', $ref);
863
+		if (count($parts) <= 2) {
864
+			// We are processing a standard or a category of sniffs.
865
+			foreach ($newSniffs as $sniffFile) {
866
+				$parts         = explode(DIRECTORY_SEPARATOR, $sniffFile);
867
+				$sniffName     = array_pop($parts);
868
+				$sniffCategory = array_pop($parts);
869
+				array_pop($parts);
870
+				$sniffStandard = array_pop($parts);
871
+				$todo[]        = $sniffStandard.'.'.$sniffCategory.'.'.substr($sniffName, 0, -9);
872
+			}
873
+		}
874
+
875
+		foreach ($todo as $code) {
876
+			// Custom severity.
877
+			if (isset($rule->severity) === true
878
+				&& $this->shouldProcessElement($rule->severity) === true
879
+			) {
880
+				if (isset($this->ruleset[$code]) === false) {
881
+					$this->ruleset[$code] = [];
882
+				}
883
+
884
+				$this->ruleset[$code]['severity'] = (int) $rule->severity;
885
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
886
+					echo str_repeat("\t", $depth);
887
+					echo "\t\t=> severity set to ".(int) $rule->severity;
888
+					if ($code !== $ref) {
889
+						echo " for $code";
890
+					}
891
+
892
+					echo PHP_EOL;
893
+				}
894
+			}
895
+
896
+			// Custom message type.
897
+			if (isset($rule->type) === true
898
+				&& $this->shouldProcessElement($rule->type) === true
899
+			) {
900
+				if (isset($this->ruleset[$code]) === false) {
901
+					$this->ruleset[$code] = [];
902
+				}
903
+
904
+				$type = strtolower((string) $rule->type);
905
+				if ($type !== 'error' && $type !== 'warning') {
906
+					throw new RuntimeException("Message type \"$type\" is invalid; must be \"error\" or \"warning\"");
907
+				}
908
+
909
+				$this->ruleset[$code]['type'] = $type;
910
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
911
+					echo str_repeat("\t", $depth);
912
+					echo "\t\t=> message type set to ".(string) $rule->type;
913
+					if ($code !== $ref) {
914
+						echo " for $code";
915
+					}
916
+
917
+					echo PHP_EOL;
918
+				}
919
+			}//end if
920
+
921
+			// Custom message.
922
+			if (isset($rule->message) === true
923
+				&& $this->shouldProcessElement($rule->message) === true
924
+			) {
925
+				if (isset($this->ruleset[$code]) === false) {
926
+					$this->ruleset[$code] = [];
927
+				}
928
+
929
+				$this->ruleset[$code]['message'] = (string) $rule->message;
930
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
931
+					echo str_repeat("\t", $depth);
932
+					echo "\t\t=> message set to ".(string) $rule->message;
933
+					if ($code !== $ref) {
934
+						echo " for $code";
935
+					}
936
+
937
+					echo PHP_EOL;
938
+				}
939
+			}
940
+
941
+			// Custom properties.
942
+			if (isset($rule->properties) === true
943
+				&& $this->shouldProcessElement($rule->properties) === true
944
+			) {
945
+				foreach ($rule->properties->property as $prop) {
946
+					if ($this->shouldProcessElement($prop) === false) {
947
+						continue;
948
+					}
949
+
950
+					if (isset($this->ruleset[$code]) === false) {
951
+						$this->ruleset[$code] = [
952
+							'properties' => [],
953
+						];
954
+					} else if (isset($this->ruleset[$code]['properties']) === false) {
955
+						$this->ruleset[$code]['properties'] = [];
956
+					}
957
+
958
+					$name = (string) $prop['name'];
959
+					if (isset($prop['type']) === true
960
+						&& (string) $prop['type'] === 'array'
961
+					) {
962
+						$values = [];
963
+						if (isset($prop['extend']) === true
964
+							&& (string) $prop['extend'] === 'true'
965
+							&& isset($this->ruleset[$code]['properties'][$name]) === true
966
+						) {
967
+							$values = $this->ruleset[$code]['properties'][$name];
968
+						}
969
+
970
+						if (isset($prop->element) === true) {
971
+							$printValue = '';
972
+							foreach ($prop->element as $element) {
973
+								if ($this->shouldProcessElement($element) === false) {
974
+									continue;
975
+								}
976
+
977
+								$value = (string) $element['value'];
978
+								if (isset($element['key']) === true) {
979
+									$key          = (string) $element['key'];
980
+									$values[$key] = $value;
981
+									$printValue  .= $key.'=>'.$value.',';
982
+								} else {
983
+									$values[]    = $value;
984
+									$printValue .= $value.',';
985
+								}
986
+							}
987
+
988
+							$printValue = rtrim($printValue, ',');
989
+						} else {
990
+							$value      = (string) $prop['value'];
991
+							$printValue = $value;
992
+							foreach (explode(',', $value) as $val) {
993
+								list($k, $v) = explode('=>', $val.'=>');
994
+								if ($v !== '') {
995
+									$values[trim($k)] = trim($v);
996
+								} else {
997
+									$values[] = trim($k);
998
+								}
999
+							}
1000
+						}//end if
1001
+
1002
+						$this->ruleset[$code]['properties'][$name] = $values;
1003
+						if (PHP_CODESNIFFER_VERBOSITY > 1) {
1004
+							echo str_repeat("\t", $depth);
1005
+							echo "\t\t=> array property \"$name\" set to \"$printValue\"";
1006
+							if ($code !== $ref) {
1007
+								echo " for $code";
1008
+							}
1009
+
1010
+							echo PHP_EOL;
1011
+						}
1012
+					} else {
1013
+						$this->ruleset[$code]['properties'][$name] = (string) $prop['value'];
1014
+						if (PHP_CODESNIFFER_VERBOSITY > 1) {
1015
+							echo str_repeat("\t", $depth);
1016
+							echo "\t\t=> property \"$name\" set to \"".(string) $prop['value'].'"';
1017
+							if ($code !== $ref) {
1018
+								echo " for $code";
1019
+							}
1020
+
1021
+							echo PHP_EOL;
1022
+						}
1023
+					}//end if
1024
+				}//end foreach
1025
+			}//end if
1026
+
1027
+			// Ignore patterns.
1028
+			foreach ($rule->{'exclude-pattern'} as $pattern) {
1029
+				if ($this->shouldProcessElement($pattern) === false) {
1030
+					continue;
1031
+				}
1032
+
1033
+				if (isset($this->ignorePatterns[$code]) === false) {
1034
+					$this->ignorePatterns[$code] = [];
1035
+				}
1036
+
1037
+				if (isset($pattern['type']) === false) {
1038
+					$pattern['type'] = 'absolute';
1039
+				}
1040
+
1041
+				$this->ignorePatterns[$code][(string) $pattern] = (string) $pattern['type'];
1042
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
1043
+					echo str_repeat("\t", $depth);
1044
+					echo "\t\t=> added rule-specific ".(string) $pattern['type'].' ignore pattern';
1045
+					if ($code !== $ref) {
1046
+						echo " for $code";
1047
+					}
1048
+
1049
+					echo ': '.(string) $pattern.PHP_EOL;
1050
+				}
1051
+			}//end foreach
1052
+
1053
+			// Include patterns.
1054
+			foreach ($rule->{'include-pattern'} as $pattern) {
1055
+				if ($this->shouldProcessElement($pattern) === false) {
1056
+					continue;
1057
+				}
1058
+
1059
+				if (isset($this->includePatterns[$code]) === false) {
1060
+					$this->includePatterns[$code] = [];
1061
+				}
1062
+
1063
+				if (isset($pattern['type']) === false) {
1064
+					$pattern['type'] = 'absolute';
1065
+				}
1066
+
1067
+				$this->includePatterns[$code][(string) $pattern] = (string) $pattern['type'];
1068
+				if (PHP_CODESNIFFER_VERBOSITY > 1) {
1069
+					echo str_repeat("\t", $depth);
1070
+					echo "\t\t=> added rule-specific ".(string) $pattern['type'].' include pattern';
1071
+					if ($code !== $ref) {
1072
+						echo " for $code";
1073
+					}
1074
+
1075
+					echo ': '.(string) $pattern.PHP_EOL;
1076
+				}
1077
+			}//end foreach
1078
+		}//end foreach
1079
+
1080
+	}//end processRule()
1081
+
1082
+
1083
+	/**
1084
+	 * Determine if an element should be processed or ignored.
1085
+	 *
1086
+	 * @param \SimpleXMLElement $element An object from a ruleset XML file.
1087
+	 *
1088
+	 * @return bool
1089
+	 */
1090
+	private function shouldProcessElement($element)
1091
+	{
1092
+		if (isset($element['phpcbf-only']) === false
1093
+			&& isset($element['phpcs-only']) === false
1094
+		) {
1095
+			// No exceptions are being made.
1096
+			return true;
1097
+		}
1098
+
1099
+		if (PHP_CODESNIFFER_CBF === true
1100
+			&& isset($element['phpcbf-only']) === true
1101
+			&& (string) $element['phpcbf-only'] === 'true'
1102
+		) {
1103
+			return true;
1104
+		}
1105
+
1106
+		if (PHP_CODESNIFFER_CBF === false
1107
+			&& isset($element['phpcs-only']) === true
1108
+			&& (string) $element['phpcs-only'] === 'true'
1109
+		) {
1110
+			return true;
1111
+		}
1112
+
1113
+		return false;
1114
+
1115
+	}//end shouldProcessElement()
1116
+
1117
+
1118
+	/**
1119
+	 * Loads and stores sniffs objects used for sniffing files.
1120
+	 *
1121
+	 * @param array $files        Paths to the sniff files to register.
1122
+	 * @param array $restrictions The sniff class names to restrict the allowed
1123
+	 *                            listeners to.
1124
+	 * @param array $exclusions   The sniff class names to exclude from the
1125
+	 *                            listeners list.
1126
+	 *
1127
+	 * @return void
1128
+	 */
1129
+	public function registerSniffs($files, $restrictions, $exclusions)
1130
+	{
1131
+		$listeners = [];
1132
+
1133
+		foreach ($files as $file) {
1134
+			// Work out where the position of /StandardName/Sniffs/... is
1135
+			// so we can determine what the class will be called.
1136
+			$sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR);
1137
+			if ($sniffPos === false) {
1138
+				continue;
1139
+			}
1140
+
1141
+			$slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR);
1142
+			if ($slashPos === false) {
1143
+				continue;
1144
+			}
1145
+
1146
+			$className   = Autoload::loadFile($file);
1147
+			$compareName = Util\Common::cleanSniffClass($className);
1148
+
1149
+			// If they have specified a list of sniffs to restrict to, check
1150
+			// to see if this sniff is allowed.
1151
+			if (empty($restrictions) === false
1152
+				&& isset($restrictions[$compareName]) === false
1153
+			) {
1154
+				continue;
1155
+			}
1156
+
1157
+			// If they have specified a list of sniffs to exclude, check
1158
+			// to see if this sniff is allowed.
1159
+			if (empty($exclusions) === false
1160
+				&& isset($exclusions[$compareName]) === true
1161
+			) {
1162
+				continue;
1163
+			}
1164
+
1165
+			// Skip abstract classes.
1166
+			$reflection = new \ReflectionClass($className);
1167
+			if ($reflection->isAbstract() === true) {
1168
+				continue;
1169
+			}
1170
+
1171
+			$listeners[$className] = $className;
1172
+
1173
+			if (PHP_CODESNIFFER_VERBOSITY > 2) {
1174
+				echo "Registered $className".PHP_EOL;
1175
+			}
1176
+		}//end foreach
1177
+
1178
+		$this->sniffs = $listeners;
1179
+
1180
+	}//end registerSniffs()
1181
+
1182
+
1183
+	/**
1184
+	 * Populates the array of PHP_CodeSniffer_Sniff's for this file.
1185
+	 *
1186
+	 * @return void
1187
+	 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If sniff registration fails.
1188
+	 */
1189
+	public function populateTokenListeners()
1190
+	{
1191
+		// Construct a list of listeners indexed by token being listened for.
1192
+		$this->tokenListeners = [];
1193
+
1194
+		foreach ($this->sniffs as $sniffClass => $sniffObject) {
1195
+			$this->sniffs[$sniffClass] = null;
1196
+			$this->sniffs[$sniffClass] = new $sniffClass();
1197
+
1198
+			$sniffCode = Util\Common::getSniffCode($sniffClass);
1199
+			$this->sniffCodes[$sniffCode] = $sniffClass;
1200
+
1201
+			// Set custom properties.
1202
+			if (isset($this->ruleset[$sniffCode]['properties']) === true) {
1203
+				foreach ($this->ruleset[$sniffCode]['properties'] as $name => $value) {
1204
+					$this->setSniffProperty($sniffClass, $name, $value);
1205
+				}
1206
+			}
1207
+
1208
+			$tokenizers = [];
1209
+			$vars       = get_class_vars($sniffClass);
1210
+			if (isset($vars['supportedTokenizers']) === true) {
1211
+				foreach ($vars['supportedTokenizers'] as $tokenizer) {
1212
+					$tokenizers[$tokenizer] = $tokenizer;
1213
+				}
1214
+			} else {
1215
+				$tokenizers = ['PHP' => 'PHP'];
1216
+			}
1217
+
1218
+			$tokens = $this->sniffs[$sniffClass]->register();
1219
+			if (is_array($tokens) === false) {
1220
+				$msg = "Sniff $sniffClass register() method must return an array";
1221
+				throw new RuntimeException($msg);
1222
+			}
1223
+
1224
+			$ignorePatterns = [];
1225
+			$patterns       = $this->getIgnorePatterns($sniffCode);
1226
+			foreach ($patterns as $pattern => $type) {
1227
+				$replacements = [
1228
+					'\\,' => ',',
1229
+					'*'   => '.*',
1230
+				];
1231
+
1232
+				$ignorePatterns[] = strtr($pattern, $replacements);
1233
+			}
1234
+
1235
+			$includePatterns = [];
1236
+			$patterns        = $this->getIncludePatterns($sniffCode);
1237
+			foreach ($patterns as $pattern => $type) {
1238
+				$replacements = [
1239
+					'\\,' => ',',
1240
+					'*'   => '.*',
1241
+				];
1242
+
1243
+				$includePatterns[] = strtr($pattern, $replacements);
1244
+			}
1245
+
1246
+			foreach ($tokens as $token) {
1247
+				if (isset($this->tokenListeners[$token]) === false) {
1248
+					$this->tokenListeners[$token] = [];
1249
+				}
1250
+
1251
+				if (isset($this->tokenListeners[$token][$sniffClass]) === false) {
1252
+					$this->tokenListeners[$token][$sniffClass] = [
1253
+						'class'      => $sniffClass,
1254
+						'source'     => $sniffCode,
1255
+						'tokenizers' => $tokenizers,
1256
+						'ignore'     => $ignorePatterns,
1257
+						'include'    => $includePatterns,
1258
+					];
1259
+				}
1260
+			}
1261
+		}//end foreach
1262
+
1263
+	}//end populateTokenListeners()
1264
+
1265
+
1266
+	/**
1267
+	 * Set a single property for a sniff.
1268
+	 *
1269
+	 * @param string $sniffClass The class name of the sniff.
1270
+	 * @param string $name       The name of the property to change.
1271
+	 * @param string $value      The new value of the property.
1272
+	 *
1273
+	 * @return void
1274
+	 */
1275
+	public function setSniffProperty($sniffClass, $name, $value)
1276
+	{
1277
+		// Setting a property for a sniff we are not using.
1278
+		if (isset($this->sniffs[$sniffClass]) === false) {
1279
+			return;
1280
+		}
1281
+
1282
+		$name = trim($name);
1283
+		if (is_string($value) === true) {
1284
+			$value = trim($value);
1285
+		}
1286
+
1287
+		if ($value === '') {
1288
+			$value = null;
1289
+		}
1290
+
1291
+		// Special case for booleans.
1292
+		if ($value === 'true') {
1293
+			$value = true;
1294
+		} else if ($value === 'false') {
1295
+			$value = false;
1296
+		} else if (substr($name, -2) === '[]') {
1297
+			$name   = substr($name, 0, -2);
1298
+			$values = [];
1299
+			if ($value !== null) {
1300
+				foreach (explode(',', $value) as $val) {
1301
+					list($k, $v) = explode('=>', $val.'=>');
1302
+					if ($v !== '') {
1303
+						$values[trim($k)] = trim($v);
1304
+					} else {
1305
+						$values[] = trim($k);
1306
+					}
1307
+				}
1308
+			}
1309
+
1310
+			$value = $values;
1311
+		}
1312
+
1313
+		$this->sniffs[$sniffClass]->$name = $value;
1314
+
1315
+	}//end setSniffProperty()
1316
+
1317
+
1318
+	/**
1319
+	 * Gets the array of ignore patterns.
1320
+	 *
1321
+	 * Optionally takes a listener to get ignore patterns specified
1322
+	 * for that sniff only.
1323
+	 *
1324
+	 * @param string $listener The listener to get patterns for. If NULL, all
1325
+	 *                         patterns are returned.
1326
+	 *
1327
+	 * @return array
1328
+	 */
1329
+	public function getIgnorePatterns($listener=null)
1330
+	{
1331
+		if ($listener === null) {
1332
+			return $this->ignorePatterns;
1333
+		}
1334
+
1335
+		if (isset($this->ignorePatterns[$listener]) === true) {
1336
+			return $this->ignorePatterns[$listener];
1337
+		}
1338
+
1339
+		return [];
1340
+
1341
+	}//end getIgnorePatterns()
1342
+
1343
+
1344
+	/**
1345
+	 * Gets the array of include patterns.
1346
+	 *
1347
+	 * Optionally takes a listener to get include patterns specified
1348
+	 * for that sniff only.
1349
+	 *
1350
+	 * @param string $listener The listener to get patterns for. If NULL, all
1351
+	 *                         patterns are returned.
1352
+	 *
1353
+	 * @return array
1354
+	 */
1355
+	public function getIncludePatterns($listener=null)
1356
+	{
1357
+		if ($listener === null) {
1358
+			return $this->includePatterns;
1359
+		}
1360
+
1361
+		if (isset($this->includePatterns[$listener]) === true) {
1362
+			return $this->includePatterns[$listener];
1363
+		}
1364
+
1365
+		return [];
1366
+
1367
+	}//end getIncludePatterns()
1368 1368
 
1369 1369
 
1370 1370
 }//end class
Please login to merge, or discard this patch.
vendor/squizlabs/php_codesniffer/src/Config.php 1 patch
Indentation   +1691 added lines, -1691 removed lines patch added patch discarded remove patch
@@ -18,1697 +18,1697 @@
 block discarded – undo
18 18
 class Config
19 19
 {
20 20
 
21
-    /**
22
-     * The current version.
23
-     *
24
-     * @var string
25
-     */
26
-    const VERSION = '3.4.2';
27
-
28
-    /**
29
-     * Package stability; either stable, beta or alpha.
30
-     *
31
-     * @var string
32
-     */
33
-    const STABILITY = 'stable';
34
-
35
-    /**
36
-     * An array of settings that PHPCS and PHPCBF accept.
37
-     *
38
-     * This array is not meant to be accessed directly. Instead, use the settings
39
-     * as if they are class member vars so the __get() and __set() magic methods
40
-     * can be used to validate the values. For example, to set the verbosity level to
41
-     * level 2, use $this->verbosity = 2; instead of accessing this property directly.
42
-     *
43
-     * The list of settings are:
44
-     *
45
-     * string[] files           The files and directories to check.
46
-     * string[] standards       The standards being used for checking.
47
-     * int      verbosity       How verbose the output should be.
48
-     *                          0: no unnecessary output
49
-     *                          1: basic output for files being checked
50
-     *                          2: ruleset and file parsing output
51
-     *                          3: sniff execution output
52
-     * bool     interactive     Enable interactive checking mode.
53
-     * bool     parallel        Check files in parallel.
54
-     * bool     cache           Enable the use of the file cache.
55
-     * bool     cacheFile       A file where the cache data should be written
56
-     * bool     colors          Display colours in output.
57
-     * bool     explain         Explain the coding standards.
58
-     * bool     local           Process local files in directories only (no recursion).
59
-     * bool     showSources     Show sniff source codes in report output.
60
-     * bool     showProgress    Show basic progress information while running.
61
-     * bool     quiet           Quiet mode; disables progress and verbose output.
62
-     * bool     annotations     Process phpcs: annotations.
63
-     * int      tabWidth        How many spaces each tab is worth.
64
-     * string   encoding        The encoding of the files being checked.
65
-     * string[] sniffs          The sniffs that should be used for checking.
66
-     *                          If empty, all sniffs in the supplied standards will be used.
67
-     * string[] exclude         The sniffs that should be excluded from checking.
68
-     *                          If empty, all sniffs in the supplied standards will be used.
69
-     * string[] ignored         Regular expressions used to ignore files and folders during checking.
70
-     * string   reportFile      A file where the report output should be written.
71
-     * string   generator       The documentation generator to use.
72
-     * string   filter          The filter to use for the run.
73
-     * string[] bootstrap       One of more files to include before the run begins.
74
-     * int      reportWidth     The maximum number of columns that reports should use for output.
75
-     *                          Set to "auto" for have this value changed to the width of the terminal.
76
-     * int      errorSeverity   The minimum severity an error must have to be displayed.
77
-     * int      warningSeverity The minimum severity a warning must have to be displayed.
78
-     * bool     recordErrors    Record the content of error messages as well as error counts.
79
-     * string   suffix          A suffix to add to fixed files.
80
-     * string   basepath        A file system location to strip from the paths of files shown in reports.
81
-     * bool     stdin           Read content from STDIN instead of supplied files.
82
-     * string   stdinContent    Content passed directly to PHPCS on STDIN.
83
-     * string   stdinPath       The path to use for content passed on STDIN.
84
-     *
85
-     * array<string, string>      extensions File extensions that should be checked, and what tokenizer to use.
86
-     *                                       E.g., array('inc' => 'PHP');
87
-     * array<string, string|null> reports    The reports to use for printing output after the run.
88
-     *                                       The format of the array is:
89
-     *                                           array(
90
-     *                                            'reportName1' => 'outputFile',
91
-     *                                            'reportName2' => null,
92
-     *                                           );
93
-     *                                       If the array value is NULL, the report will be written to the screen.
94
-     *
95
-     * string[] unknown Any arguments gathered on the command line that are unknown to us.
96
-     *                  E.g., using `phpcs -c` will give array('c');
97
-     *
98
-     * @var array<string, mixed>
99
-     */
100
-    private $settings = [
101
-        'files'           => null,
102
-        'standards'       => null,
103
-        'verbosity'       => null,
104
-        'interactive'     => null,
105
-        'parallel'        => null,
106
-        'cache'           => null,
107
-        'cacheFile'       => null,
108
-        'colors'          => null,
109
-        'explain'         => null,
110
-        'local'           => null,
111
-        'showSources'     => null,
112
-        'showProgress'    => null,
113
-        'quiet'           => null,
114
-        'annotations'     => null,
115
-        'tabWidth'        => null,
116
-        'encoding'        => null,
117
-        'extensions'      => null,
118
-        'sniffs'          => null,
119
-        'exclude'         => null,
120
-        'ignored'         => null,
121
-        'reportFile'      => null,
122
-        'generator'       => null,
123
-        'filter'          => null,
124
-        'bootstrap'       => null,
125
-        'reports'         => null,
126
-        'basepath'        => null,
127
-        'reportWidth'     => null,
128
-        'errorSeverity'   => null,
129
-        'warningSeverity' => null,
130
-        'recordErrors'    => null,
131
-        'suffix'          => null,
132
-        'stdin'           => null,
133
-        'stdinContent'    => null,
134
-        'stdinPath'       => null,
135
-        'unknown'         => null,
136
-    ];
137
-
138
-    /**
139
-     * Whether or not to kill the process when an unknown command line arg is found.
140
-     *
141
-     * If FALSE, arguments that are not command line options or file/directory paths
142
-     * will be ignored and execution will continue. These values will be stored in
143
-     * $this->unknown.
144
-     *
145
-     * @var boolean
146
-     */
147
-    public $dieOnUnknownArg;
148
-
149
-    /**
150
-     * The current command line arguments we are processing.
151
-     *
152
-     * @var string[]
153
-     */
154
-    private $cliArgs = [];
155
-
156
-    /**
157
-     * Command line values that the user has supplied directly.
158
-     *
159
-     * @var array<string, TRUE>
160
-     */
161
-    private static $overriddenDefaults = [];
162
-
163
-    /**
164
-     * Config file data that has been loaded for the run.
165
-     *
166
-     * @var array<string, string>
167
-     */
168
-    private static $configData = null;
169
-
170
-    /**
171
-     * The full path to the config data file that has been loaded.
172
-     *
173
-     * @var string
174
-     */
175
-    private static $configDataFile = null;
176
-
177
-    /**
178
-     * Automatically discovered executable utility paths.
179
-     *
180
-     * @var array<string, string>
181
-     */
182
-    private static $executablePaths = [];
183
-
184
-
185
-    /**
186
-     * Get the value of an inaccessible property.
187
-     *
188
-     * @param string $name The name of the property.
189
-     *
190
-     * @return mixed
191
-     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
192
-     */
193
-    public function __get($name)
194
-    {
195
-        if (array_key_exists($name, $this->settings) === false) {
196
-            throw new RuntimeException("ERROR: unable to get value of property \"$name\"");
197
-        }
198
-
199
-        return $this->settings[$name];
200
-
201
-    }//end __get()
202
-
203
-
204
-    /**
205
-     * Set the value of an inaccessible property.
206
-     *
207
-     * @param string $name  The name of the property.
208
-     * @param mixed  $value The value of the property.
209
-     *
210
-     * @return void
211
-     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
212
-     */
213
-    public function __set($name, $value)
214
-    {
215
-        if (array_key_exists($name, $this->settings) === false) {
216
-            throw new RuntimeException("Can't __set() $name; setting doesn't exist");
217
-        }
218
-
219
-        switch ($name) {
220
-        case 'reportWidth' :
221
-            // Support auto terminal width.
222
-            if ($value === 'auto' && preg_match('|\d+ (\d+)|', shell_exec('stty size 2>&1'), $matches) === 1) {
223
-                $value = (int) $matches[1];
224
-            } else {
225
-                $value = (int) $value;
226
-            }
227
-            break;
228
-        case 'standards' :
229
-            $cleaned = [];
230
-
231
-            // Check if the standard name is valid, or if the case is invalid.
232
-            $installedStandards = Util\Standards::getInstalledStandards();
233
-            foreach ($value as $standard) {
234
-                foreach ($installedStandards as $validStandard) {
235
-                    if (strtolower($standard) === strtolower($validStandard)) {
236
-                        $standard = $validStandard;
237
-                        break;
238
-                    }
239
-                }
240
-
241
-                $cleaned[] = $standard;
242
-            }
243
-
244
-            $value = $cleaned;
245
-            break;
246
-        default :
247
-            // No validation required.
248
-            break;
249
-        }//end switch
250
-
251
-        $this->settings[$name] = $value;
252
-
253
-    }//end __set()
254
-
255
-
256
-    /**
257
-     * Check if the value of an inaccessible property is set.
258
-     *
259
-     * @param string $name The name of the property.
260
-     *
261
-     * @return bool
262
-     */
263
-    public function __isset($name)
264
-    {
265
-        return isset($this->settings[$name]);
266
-
267
-    }//end __isset()
268
-
269
-
270
-    /**
271
-     * Unset the value of an inaccessible property.
272
-     *
273
-     * @param string $name The name of the property.
274
-     *
275
-     * @return void
276
-     */
277
-    public function __unset($name)
278
-    {
279
-        $this->settings[$name] = null;
280
-
281
-    }//end __unset()
282
-
283
-
284
-    /**
285
-     * Get the array of all config settings.
286
-     *
287
-     * @return array<string, mixed>
288
-     */
289
-    public function getSettings()
290
-    {
291
-        return $this->settings;
292
-
293
-    }//end getSettings()
294
-
295
-
296
-    /**
297
-     * Set the array of all config settings.
298
-     *
299
-     * @param array<string, mixed> $settings The array of config settings.
300
-     *
301
-     * @return void
302
-     */
303
-    public function setSettings($settings)
304
-    {
305
-        return $this->settings = $settings;
306
-
307
-    }//end setSettings()
308
-
309
-
310
-    /**
311
-     * Creates a Config object and populates it with command line values.
312
-     *
313
-     * @param array $cliArgs         An array of values gathered from CLI args.
314
-     * @param bool  $dieOnUnknownArg Whether or not to kill the process when an
315
-     *                               unknown command line arg is found.
316
-     *
317
-     * @return void
318
-     */
319
-    public function __construct(array $cliArgs=[], $dieOnUnknownArg=true)
320
-    {
321
-        if (defined('PHP_CODESNIFFER_IN_TESTS') === true) {
322
-            // Let everything through during testing so that we can
323
-            // make use of PHPUnit command line arguments as well.
324
-            $this->dieOnUnknownArg = false;
325
-        } else {
326
-            $this->dieOnUnknownArg = $dieOnUnknownArg;
327
-        }
328
-
329
-        if (empty($cliArgs) === true) {
330
-            $cliArgs = $_SERVER['argv'];
331
-            array_shift($cliArgs);
332
-        }
333
-
334
-        $this->restoreDefaults();
335
-        $this->setCommandLineValues($cliArgs);
336
-
337
-        if (isset(self::$overriddenDefaults['standards']) === false) {
338
-            // They did not supply a standard to use.
339
-            // Look for a default ruleset in the current directory or higher.
340
-            $currentDir = getcwd();
341
-
342
-            $defaultFiles = [
343
-                '.phpcs.xml',
344
-                'phpcs.xml',
345
-                '.phpcs.xml.dist',
346
-                'phpcs.xml.dist',
347
-            ];
348
-
349
-            do {
350
-                foreach ($defaultFiles as $defaultFilename) {
351
-                    $default = $currentDir.DIRECTORY_SEPARATOR.$defaultFilename;
352
-                    if (is_file($default) === true) {
353
-                        $this->standards = [$default];
354
-                        break(2);
355
-                    }
356
-                }
357
-
358
-                $lastDir    = $currentDir;
359
-                $currentDir = dirname($currentDir);
360
-            } while ($currentDir !== '.' && $currentDir !== $lastDir);
361
-        }//end if
362
-
363
-        if (defined('STDIN') === false
364
-            || strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'
365
-        ) {
366
-            return;
367
-        }
368
-
369
-        $handle = fopen('php://stdin', 'r');
370
-
371
-        // Check for content on STDIN.
372
-        if ($this->stdin === true
373
-            || (Util\Common::isStdinATTY() === false
374
-            && feof($handle) === false)
375
-        ) {
376
-            $readStreams = [$handle];
377
-            $writeSteams = null;
378
-
379
-            $fileContents = '';
380
-            while (is_resource($handle) === true && feof($handle) === false) {
381
-                // Set a timeout of 200ms.
382
-                if (stream_select($readStreams, $writeSteams, $writeSteams, 0, 200000) === 0) {
383
-                    break;
384
-                }
385
-
386
-                $fileContents .= fgets($handle);
387
-            }
388
-
389
-            if (trim($fileContents) !== '') {
390
-                $this->stdin        = true;
391
-                $this->stdinContent = $fileContents;
392
-                self::$overriddenDefaults['stdin']        = true;
393
-                self::$overriddenDefaults['stdinContent'] = true;
394
-            }
395
-        }//end if
396
-
397
-        fclose($handle);
398
-
399
-    }//end __construct()
400
-
401
-
402
-    /**
403
-     * Set the command line values.
404
-     *
405
-     * @param array $args An array of command line arguments to set.
406
-     *
407
-     * @return void
408
-     */
409
-    public function setCommandLineValues($args)
410
-    {
411
-        $this->cliArgs = $args;
412
-        $numArgs       = count($args);
413
-
414
-        for ($i = 0; $i < $numArgs; $i++) {
415
-            $arg = $this->cliArgs[$i];
416
-            if ($arg === '') {
417
-                continue;
418
-            }
419
-
420
-            if ($arg{0} === '-') {
421
-                if ($arg === '-') {
422
-                    // Asking to read from STDIN.
423
-                    $this->stdin = true;
424
-                    self::$overriddenDefaults['stdin'] = true;
425
-                    continue;
426
-                }
427
-
428
-                if ($arg === '--') {
429
-                    // Empty argument, ignore it.
430
-                    continue;
431
-                }
432
-
433
-                if ($arg{1} === '-') {
434
-                    $this->processLongArgument(substr($arg, 2), $i);
435
-                } else {
436
-                    $switches = str_split($arg);
437
-                    foreach ($switches as $switch) {
438
-                        if ($switch === '-') {
439
-                            continue;
440
-                        }
441
-
442
-                        $this->processShortArgument($switch, $i);
443
-                    }
444
-                }
445
-            } else {
446
-                $this->processUnknownArgument($arg, $i);
447
-            }//end if
448
-        }//end for
449
-
450
-    }//end setCommandLineValues()
451
-
452
-
453
-    /**
454
-     * Restore default values for all possible command line arguments.
455
-     *
456
-     * @return array
457
-     */
458
-    public function restoreDefaults()
459
-    {
460
-        $this->files           = [];
461
-        $this->standards       = ['PEAR'];
462
-        $this->verbosity       = 0;
463
-        $this->interactive     = false;
464
-        $this->cache           = false;
465
-        $this->cacheFile       = null;
466
-        $this->colors          = false;
467
-        $this->explain         = false;
468
-        $this->local           = false;
469
-        $this->showSources     = false;
470
-        $this->showProgress    = false;
471
-        $this->quiet           = false;
472
-        $this->annotations     = true;
473
-        $this->parallel        = 1;
474
-        $this->tabWidth        = 0;
475
-        $this->encoding        = 'utf-8';
476
-        $this->extensions      = [
477
-            'php' => 'PHP',
478
-            'inc' => 'PHP',
479
-            'js'  => 'JS',
480
-            'css' => 'CSS',
481
-        ];
482
-        $this->sniffs          = [];
483
-        $this->exclude         = [];
484
-        $this->ignored         = [];
485
-        $this->reportFile      = null;
486
-        $this->generator       = null;
487
-        $this->filter          = null;
488
-        $this->bootstrap       = [];
489
-        $this->basepath        = null;
490
-        $this->reports         = ['full' => null];
491
-        $this->reportWidth     = 'auto';
492
-        $this->errorSeverity   = 5;
493
-        $this->warningSeverity = 5;
494
-        $this->recordErrors    = true;
495
-        $this->suffix          = '';
496
-        $this->stdin           = false;
497
-        $this->stdinContent    = null;
498
-        $this->stdinPath       = null;
499
-        $this->unknown         = [];
500
-
501
-        $standard = self::getConfigData('default_standard');
502
-        if ($standard !== null) {
503
-            $this->standards = explode(',', $standard);
504
-        }
505
-
506
-        $reportFormat = self::getConfigData('report_format');
507
-        if ($reportFormat !== null) {
508
-            $this->reports = [$reportFormat => null];
509
-        }
510
-
511
-        $tabWidth = self::getConfigData('tab_width');
512
-        if ($tabWidth !== null) {
513
-            $this->tabWidth = (int) $tabWidth;
514
-        }
515
-
516
-        $encoding = self::getConfigData('encoding');
517
-        if ($encoding !== null) {
518
-            $this->encoding = strtolower($encoding);
519
-        }
520
-
521
-        $severity = self::getConfigData('severity');
522
-        if ($severity !== null) {
523
-            $this->errorSeverity   = (int) $severity;
524
-            $this->warningSeverity = (int) $severity;
525
-        }
526
-
527
-        $severity = self::getConfigData('error_severity');
528
-        if ($severity !== null) {
529
-            $this->errorSeverity = (int) $severity;
530
-        }
531
-
532
-        $severity = self::getConfigData('warning_severity');
533
-        if ($severity !== null) {
534
-            $this->warningSeverity = (int) $severity;
535
-        }
536
-
537
-        $showWarnings = self::getConfigData('show_warnings');
538
-        if ($showWarnings !== null) {
539
-            $showWarnings = (bool) $showWarnings;
540
-            if ($showWarnings === false) {
541
-                $this->warningSeverity = 0;
542
-            }
543
-        }
544
-
545
-        $reportWidth = self::getConfigData('report_width');
546
-        if ($reportWidth !== null) {
547
-            $this->reportWidth = $reportWidth;
548
-        }
549
-
550
-        $showProgress = self::getConfigData('show_progress');
551
-        if ($showProgress !== null) {
552
-            $this->showProgress = (bool) $showProgress;
553
-        }
554
-
555
-        $quiet = self::getConfigData('quiet');
556
-        if ($quiet !== null) {
557
-            $this->quiet = (bool) $quiet;
558
-        }
559
-
560
-        $colors = self::getConfigData('colors');
561
-        if ($colors !== null) {
562
-            $this->colors = (bool) $colors;
563
-        }
564
-
565
-        if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
566
-            $cache = self::getConfigData('cache');
567
-            if ($cache !== null) {
568
-                $this->cache = (bool) $cache;
569
-            }
570
-
571
-            $parallel = self::getConfigData('parallel');
572
-            if ($parallel !== null) {
573
-                $this->parallel = max((int) $parallel, 1);
574
-            }
575
-        }
576
-
577
-    }//end restoreDefaults()
578
-
579
-
580
-    /**
581
-     * Processes a short (-e) command line argument.
582
-     *
583
-     * @param string $arg The command line argument.
584
-     * @param int    $pos The position of the argument on the command line.
585
-     *
586
-     * @return void
587
-     */
588
-    public function processShortArgument($arg, $pos)
589
-    {
590
-        switch ($arg) {
591
-        case 'h':
592
-        case '?':
593
-            ob_start();
594
-            $this->printUsage();
595
-            $output = ob_get_contents();
596
-            ob_end_clean();
597
-            throw new DeepExitException($output, 0);
598
-        case 'i' :
599
-            ob_start();
600
-            Util\Standards::printInstalledStandards();
601
-            $output = ob_get_contents();
602
-            ob_end_clean();
603
-            throw new DeepExitException($output, 0);
604
-        case 'v' :
605
-            if ($this->quiet === true) {
606
-                // Ignore when quiet mode is enabled.
607
-                break;
608
-            }
609
-
610
-            $this->verbosity++;
611
-            self::$overriddenDefaults['verbosity'] = true;
612
-            break;
613
-        case 'l' :
614
-            $this->local = true;
615
-            self::$overriddenDefaults['local'] = true;
616
-            break;
617
-        case 's' :
618
-            $this->showSources = true;
619
-            self::$overriddenDefaults['showSources'] = true;
620
-            break;
621
-        case 'a' :
622
-            $this->interactive = true;
623
-            self::$overriddenDefaults['interactive'] = true;
624
-            break;
625
-        case 'e':
626
-            $this->explain = true;
627
-            self::$overriddenDefaults['explain'] = true;
628
-            break;
629
-        case 'p' :
630
-            if ($this->quiet === true) {
631
-                // Ignore when quiet mode is enabled.
632
-                break;
633
-            }
634
-
635
-            $this->showProgress = true;
636
-            self::$overriddenDefaults['showProgress'] = true;
637
-            break;
638
-        case 'q' :
639
-            // Quiet mode disables a few other settings as well.
640
-            $this->quiet        = true;
641
-            $this->showProgress = false;
642
-            $this->verbosity    = 0;
643
-
644
-            self::$overriddenDefaults['quiet'] = true;
645
-            break;
646
-        case 'm' :
647
-            $this->recordErrors = false;
648
-            self::$overriddenDefaults['recordErrors'] = true;
649
-            break;
650
-        case 'd' :
651
-            $ini = explode('=', $this->cliArgs[($pos + 1)]);
652
-            $this->cliArgs[($pos + 1)] = '';
653
-            if (isset($ini[1]) === true) {
654
-                ini_set($ini[0], $ini[1]);
655
-            } else {
656
-                ini_set($ini[0], true);
657
-            }
658
-            break;
659
-        case 'n' :
660
-            if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
661
-                $this->warningSeverity = 0;
662
-                self::$overriddenDefaults['warningSeverity'] = true;
663
-            }
664
-            break;
665
-        case 'w' :
666
-            if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
667
-                $this->warningSeverity = $this->errorSeverity;
668
-                self::$overriddenDefaults['warningSeverity'] = true;
669
-            }
670
-            break;
671
-        default:
672
-            if ($this->dieOnUnknownArg === false) {
673
-                $unknown       = $this->unknown;
674
-                $unknown[]     = $arg;
675
-                $this->unknown = $unknown;
676
-            } else {
677
-                $this->processUnknownArgument('-'.$arg, $pos);
678
-            }
679
-        }//end switch
680
-
681
-    }//end processShortArgument()
682
-
683
-
684
-    /**
685
-     * Processes a long (--example) command line argument.
686
-     *
687
-     * @param string $arg The command line argument.
688
-     * @param int    $pos The position of the argument on the command line.
689
-     *
690
-     * @return void
691
-     */
692
-    public function processLongArgument($arg, $pos)
693
-    {
694
-        switch ($arg) {
695
-        case 'help':
696
-            ob_start();
697
-            $this->printUsage();
698
-            $output = ob_get_contents();
699
-            ob_end_clean();
700
-            throw new DeepExitException($output, 0);
701
-        case 'version':
702
-            $output  = 'PHP_CodeSniffer version '.self::VERSION.' ('.self::STABILITY.') ';
703
-            $output .= 'by Squiz (http://www.squiz.net)'.PHP_EOL;
704
-            throw new DeepExitException($output, 0);
705
-        case 'colors':
706
-            if (isset(self::$overriddenDefaults['colors']) === true) {
707
-                break;
708
-            }
709
-
710
-            $this->colors = true;
711
-            self::$overriddenDefaults['colors'] = true;
712
-            break;
713
-        case 'no-colors':
714
-            if (isset(self::$overriddenDefaults['colors']) === true) {
715
-                break;
716
-            }
717
-
718
-            $this->colors = false;
719
-            self::$overriddenDefaults['colors'] = true;
720
-            break;
721
-        case 'cache':
722
-            if (isset(self::$overriddenDefaults['cache']) === true) {
723
-                break;
724
-            }
725
-
726
-            if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
727
-                $this->cache = true;
728
-                self::$overriddenDefaults['cache'] = true;
729
-            }
730
-            break;
731
-        case 'no-cache':
732
-            if (isset(self::$overriddenDefaults['cache']) === true) {
733
-                break;
734
-            }
735
-
736
-            $this->cache = false;
737
-            self::$overriddenDefaults['cache'] = true;
738
-            break;
739
-        case 'ignore-annotations':
740
-            if (isset(self::$overriddenDefaults['annotations']) === true) {
741
-                break;
742
-            }
743
-
744
-            $this->annotations = false;
745
-            self::$overriddenDefaults['annotations'] = true;
746
-            break;
747
-        case 'config-set':
748
-            if (isset($this->cliArgs[($pos + 1)]) === false
749
-                || isset($this->cliArgs[($pos + 2)]) === false
750
-            ) {
751
-                $error  = 'ERROR: Setting a config option requires a name and value'.PHP_EOL.PHP_EOL;
752
-                $error .= $this->printShortUsage(true);
753
-                throw new DeepExitException($error, 3);
754
-            }
755
-
756
-            $key     = $this->cliArgs[($pos + 1)];
757
-            $value   = $this->cliArgs[($pos + 2)];
758
-            $current = self::getConfigData($key);
759
-
760
-            try {
761
-                $this->setConfigData($key, $value);
762
-            } catch (\Exception $e) {
763
-                throw new DeepExitException($e->getMessage().PHP_EOL, 3);
764
-            }
765
-
766
-            $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
767
-
768
-            if ($current === null) {
769
-                $output .= "Config value \"$key\" added successfully".PHP_EOL;
770
-            } else {
771
-                $output .= "Config value \"$key\" updated successfully; old value was \"$current\"".PHP_EOL;
772
-            }
773
-            throw new DeepExitException($output, 0);
774
-        case 'config-delete':
775
-            if (isset($this->cliArgs[($pos + 1)]) === false) {
776
-                $error  = 'ERROR: Deleting a config option requires the name of the option'.PHP_EOL.PHP_EOL;
777
-                $error .= $this->printShortUsage(true);
778
-                throw new DeepExitException($error, 3);
779
-            }
780
-
781
-            $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
782
-
783
-            $key     = $this->cliArgs[($pos + 1)];
784
-            $current = self::getConfigData($key);
785
-            if ($current === null) {
786
-                $output .= "Config value \"$key\" has not been set".PHP_EOL;
787
-            } else {
788
-                try {
789
-                    $this->setConfigData($key, null);
790
-                } catch (\Exception $e) {
791
-                    throw new DeepExitException($e->getMessage().PHP_EOL, 3);
792
-                }
793
-
794
-                $output .= "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL;
795
-            }
796
-            throw new DeepExitException($output, 0);
797
-        case 'config-show':
798
-            ob_start();
799
-            $data = self::getAllConfigData();
800
-            echo 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
801
-            $this->printConfigData($data);
802
-            $output = ob_get_contents();
803
-            ob_end_clean();
804
-            throw new DeepExitException($output, 0);
805
-        case 'runtime-set':
806
-            if (isset($this->cliArgs[($pos + 1)]) === false
807
-                || isset($this->cliArgs[($pos + 2)]) === false
808
-            ) {
809
-                $error  = 'ERROR: Setting a runtime config option requires a name and value'.PHP_EOL.PHP_EOL;
810
-                $error .= $this->printShortUsage(true);
811
-                throw new DeepExitException($error, 3);
812
-            }
813
-
814
-            $key   = $this->cliArgs[($pos + 1)];
815
-            $value = $this->cliArgs[($pos + 2)];
816
-            $this->cliArgs[($pos + 1)] = '';
817
-            $this->cliArgs[($pos + 2)] = '';
818
-            self::setConfigData($key, $value, true);
819
-            if (isset(self::$overriddenDefaults['runtime-set']) === false) {
820
-                self::$overriddenDefaults['runtime-set'] = [];
821
-            }
822
-
823
-            self::$overriddenDefaults['runtime-set'][$key] = true;
824
-            break;
825
-        default:
826
-            if (substr($arg, 0, 7) === 'sniffs=') {
827
-                if (isset(self::$overriddenDefaults['sniffs']) === true) {
828
-                    break;
829
-                }
830
-
831
-                $sniffs = explode(',', substr($arg, 7));
832
-                foreach ($sniffs as $sniff) {
833
-                    if (substr_count($sniff, '.') !== 2) {
834
-                        $error  = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL;
835
-                        $error .= $this->printShortUsage(true);
836
-                        throw new DeepExitException($error, 3);
837
-                    }
838
-                }
839
-
840
-                $this->sniffs = $sniffs;
841
-                self::$overriddenDefaults['sniffs'] = true;
842
-            } else if (substr($arg, 0, 8) === 'exclude=') {
843
-                if (isset(self::$overriddenDefaults['exclude']) === true) {
844
-                    break;
845
-                }
846
-
847
-                $sniffs = explode(',', substr($arg, 8));
848
-                foreach ($sniffs as $sniff) {
849
-                    if (substr_count($sniff, '.') !== 2) {
850
-                        $error  = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL;
851
-                        $error .= $this->printShortUsage(true);
852
-                        throw new DeepExitException($error, 3);
853
-                    }
854
-                }
855
-
856
-                $this->exclude = $sniffs;
857
-                self::$overriddenDefaults['exclude'] = true;
858
-            } else if (defined('PHP_CODESNIFFER_IN_TESTS') === false
859
-                && substr($arg, 0, 6) === 'cache='
860
-            ) {
861
-                if ((isset(self::$overriddenDefaults['cache']) === true
862
-                    && $this->cache === false)
863
-                    || isset(self::$overriddenDefaults['cacheFile']) === true
864
-                ) {
865
-                    break;
866
-                }
867
-
868
-                // Turn caching on.
869
-                $this->cache = true;
870
-                self::$overriddenDefaults['cache'] = true;
871
-
872
-                $this->cacheFile = Util\Common::realpath(substr($arg, 6));
873
-
874
-                // It may not exist and return false instead.
875
-                if ($this->cacheFile === false) {
876
-                    $this->cacheFile = substr($arg, 6);
877
-
878
-                    $dir = dirname($this->cacheFile);
879
-                    if (is_dir($dir) === false) {
880
-                        $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
881
-                        $error .= $this->printShortUsage(true);
882
-                        throw new DeepExitException($error, 3);
883
-                    }
884
-
885
-                    if ($dir === '.') {
886
-                        // Passed cache file is a file in the current directory.
887
-                        $this->cacheFile = getcwd().'/'.basename($this->cacheFile);
888
-                    } else {
889
-                        if ($dir{0} === '/') {
890
-                            // An absolute path.
891
-                            $dir = Util\Common::realpath($dir);
892
-                        } else {
893
-                            $dir = Util\Common::realpath(getcwd().'/'.$dir);
894
-                        }
895
-
896
-                        if ($dir !== false) {
897
-                            // Cache file path is relative.
898
-                            $this->cacheFile = $dir.'/'.basename($this->cacheFile);
899
-                        }
900
-                    }
901
-                }//end if
902
-
903
-                self::$overriddenDefaults['cacheFile'] = true;
904
-
905
-                if (is_dir($this->cacheFile) === true) {
906
-                    $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" is a directory'.PHP_EOL.PHP_EOL;
907
-                    $error .= $this->printShortUsage(true);
908
-                    throw new DeepExitException($error, 3);
909
-                }
910
-            } else if (substr($arg, 0, 10) === 'bootstrap=') {
911
-                $files     = explode(',', substr($arg, 10));
912
-                $bootstrap = [];
913
-                foreach ($files as $file) {
914
-                    $path = Util\Common::realpath($file);
915
-                    if ($path === false) {
916
-                        $error  = 'ERROR: The specified bootstrap file "'.$file.'" does not exist'.PHP_EOL.PHP_EOL;
917
-                        $error .= $this->printShortUsage(true);
918
-                        throw new DeepExitException($error, 3);
919
-                    }
920
-
921
-                    $bootstrap[] = $path;
922
-                }
923
-
924
-                $this->bootstrap = array_merge($this->bootstrap, $bootstrap);
925
-                self::$overriddenDefaults['bootstrap'] = true;
926
-            } else if (substr($arg, 0, 10) === 'file-list=') {
927
-                $fileList = substr($arg, 10);
928
-                $path     = Util\Common::realpath($fileList);
929
-                if ($path === false) {
930
-                    $error  = 'ERROR: The specified file list "'.$fileList.'" does not exist'.PHP_EOL.PHP_EOL;
931
-                    $error .= $this->printShortUsage(true);
932
-                    throw new DeepExitException($error, 3);
933
-                }
934
-
935
-                $files = file($path);
936
-                foreach ($files as $inputFile) {
937
-                    $inputFile = trim($inputFile);
938
-
939
-                    // Skip empty lines.
940
-                    if ($inputFile === '') {
941
-                        continue;
942
-                    }
943
-
944
-                    $this->processFilePath($inputFile);
945
-                }
946
-            } else if (substr($arg, 0, 11) === 'stdin-path=') {
947
-                if (isset(self::$overriddenDefaults['stdinPath']) === true) {
948
-                    break;
949
-                }
950
-
951
-                $this->stdinPath = Util\Common::realpath(substr($arg, 11));
952
-
953
-                // It may not exist and return false instead, so use whatever they gave us.
954
-                if ($this->stdinPath === false) {
955
-                    $this->stdinPath = trim(substr($arg, 11));
956
-                }
957
-
958
-                self::$overriddenDefaults['stdinPath'] = true;
959
-            } else if (PHP_CODESNIFFER_CBF === false && substr($arg, 0, 12) === 'report-file=') {
960
-                if (isset(self::$overriddenDefaults['reportFile']) === true) {
961
-                    break;
962
-                }
963
-
964
-                $this->reportFile = Util\Common::realpath(substr($arg, 12));
965
-
966
-                // It may not exist and return false instead.
967
-                if ($this->reportFile === false) {
968
-                    $this->reportFile = substr($arg, 12);
969
-
970
-                    $dir = dirname($this->reportFile);
971
-                    if (is_dir($dir) === false) {
972
-                        $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
973
-                        $error .= $this->printShortUsage(true);
974
-                        throw new DeepExitException($error, 3);
975
-                    }
976
-
977
-                    if ($dir === '.') {
978
-                        // Passed report file is a file in the current directory.
979
-                        $this->reportFile = getcwd().'/'.basename($this->reportFile);
980
-                    } else {
981
-                        if ($dir{0} === '/') {
982
-                            // An absolute path.
983
-                            $dir = Util\Common::realpath($dir);
984
-                        } else {
985
-                            $dir = Util\Common::realpath(getcwd().'/'.$dir);
986
-                        }
987
-
988
-                        if ($dir !== false) {
989
-                            // Report file path is relative.
990
-                            $this->reportFile = $dir.'/'.basename($this->reportFile);
991
-                        }
992
-                    }
993
-                }//end if
994
-
995
-                self::$overriddenDefaults['reportFile'] = true;
996
-
997
-                if (is_dir($this->reportFile) === true) {
998
-                    $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" is a directory'.PHP_EOL.PHP_EOL;
999
-                    $error .= $this->printShortUsage(true);
1000
-                    throw new DeepExitException($error, 3);
1001
-                }
1002
-            } else if (substr($arg, 0, 13) === 'report-width=') {
1003
-                if (isset(self::$overriddenDefaults['reportWidth']) === true) {
1004
-                    break;
1005
-                }
1006
-
1007
-                $this->reportWidth = substr($arg, 13);
1008
-                self::$overriddenDefaults['reportWidth'] = true;
1009
-            } else if (substr($arg, 0, 9) === 'basepath=') {
1010
-                if (isset(self::$overriddenDefaults['basepath']) === true) {
1011
-                    break;
1012
-                }
1013
-
1014
-                self::$overriddenDefaults['basepath'] = true;
1015
-
1016
-                if (substr($arg, 9) === '') {
1017
-                    $this->basepath = null;
1018
-                    break;
1019
-                }
1020
-
1021
-                $this->basepath = Util\Common::realpath(substr($arg, 9));
1022
-
1023
-                // It may not exist and return false instead.
1024
-                if ($this->basepath === false) {
1025
-                    $this->basepath = substr($arg, 9);
1026
-                }
1027
-
1028
-                if (is_dir($this->basepath) === false) {
1029
-                    $error  = 'ERROR: The specified basepath "'.$this->basepath.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
1030
-                    $error .= $this->printShortUsage(true);
1031
-                    throw new DeepExitException($error, 3);
1032
-                }
1033
-            } else if ((substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-')) {
1034
-                $reports = [];
1035
-
1036
-                if ($arg[6] === '-') {
1037
-                    // This is a report with file output.
1038
-                    $split = strpos($arg, '=');
1039
-                    if ($split === false) {
1040
-                        $report = substr($arg, 7);
1041
-                        $output = null;
1042
-                    } else {
1043
-                        $report = substr($arg, 7, ($split - 7));
1044
-                        $output = substr($arg, ($split + 1));
1045
-                        if ($output === false) {
1046
-                            $output = null;
1047
-                        } else {
1048
-                            $dir = dirname($output);
1049
-                            if (is_dir($dir) === false) {
1050
-                                $error  = 'ERROR: The specified '.$report.' report file path "'.$output.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
1051
-                                $error .= $this->printShortUsage(true);
1052
-                                throw new DeepExitException($error, 3);
1053
-                            }
1054
-
1055
-                            if ($dir === '.') {
1056
-                                // Passed report file is a filename in the current directory.
1057
-                                $output = getcwd().'/'.basename($output);
1058
-                            } else {
1059
-                                if ($dir{0} === '/') {
1060
-                                    // An absolute path.
1061
-                                    $dir = Util\Common::realpath($dir);
1062
-                                } else {
1063
-                                    $dir = Util\Common::realpath(getcwd().'/'.$dir);
1064
-                                }
1065
-
1066
-                                if ($dir !== false) {
1067
-                                    // Report file path is relative.
1068
-                                    $output = $dir.'/'.basename($output);
1069
-                                }
1070
-                            }
1071
-                        }//end if
1072
-                    }//end if
1073
-
1074
-                    $reports[$report] = $output;
1075
-                } else {
1076
-                    // This is a single report.
1077
-                    if (isset(self::$overriddenDefaults['reports']) === true) {
1078
-                        break;
1079
-                    }
1080
-
1081
-                    $reportNames = explode(',', substr($arg, 7));
1082
-                    foreach ($reportNames as $report) {
1083
-                        $reports[$report] = null;
1084
-                    }
1085
-                }//end if
1086
-
1087
-                // Remove the default value so the CLI value overrides it.
1088
-                if (isset(self::$overriddenDefaults['reports']) === false) {
1089
-                    $this->reports = $reports;
1090
-                } else {
1091
-                    $this->reports = array_merge($this->reports, $reports);
1092
-                }
1093
-
1094
-                self::$overriddenDefaults['reports'] = true;
1095
-            } else if (substr($arg, 0, 7) === 'filter=') {
1096
-                if (isset(self::$overriddenDefaults['filter']) === true) {
1097
-                    break;
1098
-                }
1099
-
1100
-                $this->filter = substr($arg, 7);
1101
-                self::$overriddenDefaults['filter'] = true;
1102
-            } else if (substr($arg, 0, 9) === 'standard=') {
1103
-                $standards = trim(substr($arg, 9));
1104
-                if ($standards !== '') {
1105
-                    $this->standards = explode(',', $standards);
1106
-                }
1107
-
1108
-                self::$overriddenDefaults['standards'] = true;
1109
-            } else if (substr($arg, 0, 11) === 'extensions=') {
1110
-                if (isset(self::$overriddenDefaults['extensions']) === true) {
1111
-                    break;
1112
-                }
1113
-
1114
-                $extensions    = explode(',', substr($arg, 11));
1115
-                $newExtensions = [];
1116
-                foreach ($extensions as $ext) {
1117
-                    $slash = strpos($ext, '/');
1118
-                    if ($slash !== false) {
1119
-                        // They specified the tokenizer too.
1120
-                        list($ext, $tokenizer) = explode('/', $ext);
1121
-                        $newExtensions[$ext]   = strtoupper($tokenizer);
1122
-                        continue;
1123
-                    }
1124
-
1125
-                    if (isset($this->extensions[$ext]) === true) {
1126
-                        $newExtensions[$ext] = $this->extensions[$ext];
1127
-                    } else {
1128
-                        $newExtensions[$ext] = 'PHP';
1129
-                    }
1130
-                }
1131
-
1132
-                $this->extensions = $newExtensions;
1133
-                self::$overriddenDefaults['extensions'] = true;
1134
-            } else if (substr($arg, 0, 7) === 'suffix=') {
1135
-                if (isset(self::$overriddenDefaults['suffix']) === true) {
1136
-                    break;
1137
-                }
1138
-
1139
-                $this->suffix = substr($arg, 7);
1140
-                self::$overriddenDefaults['suffix'] = true;
1141
-            } else if (substr($arg, 0, 9) === 'parallel=') {
1142
-                if (isset(self::$overriddenDefaults['parallel']) === true) {
1143
-                    break;
1144
-                }
1145
-
1146
-                $this->parallel = max((int) substr($arg, 9), 1);
1147
-                self::$overriddenDefaults['parallel'] = true;
1148
-            } else if (substr($arg, 0, 9) === 'severity=') {
1149
-                $this->errorSeverity   = (int) substr($arg, 9);
1150
-                $this->warningSeverity = $this->errorSeverity;
1151
-                if (isset(self::$overriddenDefaults['errorSeverity']) === false) {
1152
-                    self::$overriddenDefaults['errorSeverity'] = true;
1153
-                }
1154
-
1155
-                if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
1156
-                    self::$overriddenDefaults['warningSeverity'] = true;
1157
-                }
1158
-            } else if (substr($arg, 0, 15) === 'error-severity=') {
1159
-                if (isset(self::$overriddenDefaults['errorSeverity']) === true) {
1160
-                    break;
1161
-                }
1162
-
1163
-                $this->errorSeverity = (int) substr($arg, 15);
1164
-                self::$overriddenDefaults['errorSeverity'] = true;
1165
-            } else if (substr($arg, 0, 17) === 'warning-severity=') {
1166
-                if (isset(self::$overriddenDefaults['warningSeverity']) === true) {
1167
-                    break;
1168
-                }
1169
-
1170
-                $this->warningSeverity = (int) substr($arg, 17);
1171
-                self::$overriddenDefaults['warningSeverity'] = true;
1172
-            } else if (substr($arg, 0, 7) === 'ignore=') {
1173
-                if (isset(self::$overriddenDefaults['ignored']) === true) {
1174
-                    break;
1175
-                }
1176
-
1177
-                // Split the ignore string on commas, unless the comma is escaped
1178
-                // using 1 or 3 slashes (\, or \\\,).
1179
-                $patterns = preg_split(
1180
-                    '/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/',
1181
-                    substr($arg, 7)
1182
-                );
1183
-
1184
-                $ignored = [];
1185
-                foreach ($patterns as $pattern) {
1186
-                    $pattern = trim($pattern);
1187
-                    if ($pattern === '') {
1188
-                        continue;
1189
-                    }
1190
-
1191
-                    $ignored[$pattern] = 'absolute';
1192
-                }
1193
-
1194
-                $this->ignored = $ignored;
1195
-                self::$overriddenDefaults['ignored'] = true;
1196
-            } else if (substr($arg, 0, 10) === 'generator='
1197
-                && PHP_CODESNIFFER_CBF === false
1198
-            ) {
1199
-                if (isset(self::$overriddenDefaults['generator']) === true) {
1200
-                    break;
1201
-                }
1202
-
1203
-                $this->generator = substr($arg, 10);
1204
-                self::$overriddenDefaults['generator'] = true;
1205
-            } else if (substr($arg, 0, 9) === 'encoding=') {
1206
-                if (isset(self::$overriddenDefaults['encoding']) === true) {
1207
-                    break;
1208
-                }
1209
-
1210
-                $this->encoding = strtolower(substr($arg, 9));
1211
-                self::$overriddenDefaults['encoding'] = true;
1212
-            } else if (substr($arg, 0, 10) === 'tab-width=') {
1213
-                if (isset(self::$overriddenDefaults['tabWidth']) === true) {
1214
-                    break;
1215
-                }
1216
-
1217
-                $this->tabWidth = (int) substr($arg, 10);
1218
-                self::$overriddenDefaults['tabWidth'] = true;
1219
-            } else {
1220
-                if ($this->dieOnUnknownArg === false) {
1221
-                    $eqPos = strpos($arg, '=');
1222
-                    try {
1223
-                        if ($eqPos === false) {
1224
-                            $this->values[$arg] = $arg;
1225
-                        } else {
1226
-                            $value = substr($arg, ($eqPos + 1));
1227
-                            $arg   = substr($arg, 0, $eqPos);
1228
-                            $this->values[$arg] = $value;
1229
-                        }
1230
-                    } catch (RuntimeException $e) {
1231
-                        // Value is not valid, so just ignore it.
1232
-                    }
1233
-                } else {
1234
-                    $this->processUnknownArgument('--'.$arg, $pos);
1235
-                }
1236
-            }//end if
1237
-            break;
1238
-        }//end switch
1239
-
1240
-    }//end processLongArgument()
1241
-
1242
-
1243
-    /**
1244
-     * Processes an unknown command line argument.
1245
-     *
1246
-     * Assumes all unknown arguments are files and folders to check.
1247
-     *
1248
-     * @param string $arg The command line argument.
1249
-     * @param int    $pos The position of the argument on the command line.
1250
-     *
1251
-     * @return void
1252
-     */
1253
-    public function processUnknownArgument($arg, $pos)
1254
-    {
1255
-        // We don't know about any additional switches; just files.
1256
-        if ($arg{0} === '-') {
1257
-            if ($this->dieOnUnknownArg === false) {
1258
-                return;
1259
-            }
1260
-
1261
-            $error  = "ERROR: option \"$arg\" not known".PHP_EOL.PHP_EOL;
1262
-            $error .= $this->printShortUsage(true);
1263
-            throw new DeepExitException($error, 3);
1264
-        }
1265
-
1266
-        $this->processFilePath($arg);
1267
-
1268
-    }//end processUnknownArgument()
1269
-
1270
-
1271
-    /**
1272
-     * Processes a file path and add it to the file list.
1273
-     *
1274
-     * @param string $path The path to the file to add.
1275
-     *
1276
-     * @return void
1277
-     */
1278
-    public function processFilePath($path)
1279
-    {
1280
-        // If we are processing STDIN, don't record any files to check.
1281
-        if ($this->stdin === true) {
1282
-            return;
1283
-        }
1284
-
1285
-        $file = Util\Common::realpath($path);
1286
-        if (file_exists($file) === false) {
1287
-            if ($this->dieOnUnknownArg === false) {
1288
-                return;
1289
-            }
1290
-
1291
-            $error  = 'ERROR: The file "'.$path.'" does not exist.'.PHP_EOL.PHP_EOL;
1292
-            $error .= $this->printShortUsage(true);
1293
-            throw new DeepExitException($error, 3);
1294
-        } else {
1295
-            // Can't modify the files array directly because it's not a real
1296
-            // class member, so need to use this little get/modify/set trick.
1297
-            $files       = $this->files;
1298
-            $files[]     = $file;
1299
-            $this->files = $files;
1300
-            self::$overriddenDefaults['files'] = true;
1301
-        }
1302
-
1303
-    }//end processFilePath()
1304
-
1305
-
1306
-    /**
1307
-     * Prints out the usage information for this script.
1308
-     *
1309
-     * @return void
1310
-     */
1311
-    public function printUsage()
1312
-    {
1313
-        echo PHP_EOL;
1314
-
1315
-        if (PHP_CODESNIFFER_CBF === true) {
1316
-            $this->printPHPCBFUsage();
1317
-        } else {
1318
-            $this->printPHPCSUsage();
1319
-        }
1320
-
1321
-        echo PHP_EOL;
1322
-
1323
-    }//end printUsage()
1324
-
1325
-
1326
-    /**
1327
-     * Prints out the short usage information for this script.
1328
-     *
1329
-     * @param bool $return If TRUE, the usage string is returned
1330
-     *                     instead of output to screen.
1331
-     *
1332
-     * @return string|void
1333
-     */
1334
-    public function printShortUsage($return=false)
1335
-    {
1336
-        if (PHP_CODESNIFFER_CBF === true) {
1337
-            $usage = 'Run "phpcbf --help" for usage information';
1338
-        } else {
1339
-            $usage = 'Run "phpcs --help" for usage information';
1340
-        }
1341
-
1342
-        $usage .= PHP_EOL.PHP_EOL;
1343
-
1344
-        if ($return === true) {
1345
-            return $usage;
1346
-        }
1347
-
1348
-        echo $usage;
1349
-
1350
-    }//end printShortUsage()
1351
-
1352
-
1353
-    /**
1354
-     * Prints out the usage information for PHPCS.
1355
-     *
1356
-     * @return void
1357
-     */
1358
-    public function printPHPCSUsage()
1359
-    {
1360
-        echo 'Usage: phpcs [-nwlsaepqvi] [-d key[=value]] [--colors] [--no-colors]'.PHP_EOL;
1361
-        echo '  [--cache[=<cacheFile>]] [--no-cache] [--tab-width=<tabWidth>]'.PHP_EOL;
1362
-        echo '  [--report=<report>] [--report-file=<reportFile>] [--report-<report>=<reportFile>]'.PHP_EOL;
1363
-        echo '  [--report-width=<reportWidth>] [--basepath=<basepath>] [--bootstrap=<bootstrap>]'.PHP_EOL;
1364
-        echo '  [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL;
1365
-        echo '  [--runtime-set key value] [--config-set key value] [--config-delete key] [--config-show]'.PHP_EOL;
1366
-        echo '  [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>]'.PHP_EOL;
1367
-        echo '  [--encoding=<encoding>] [--parallel=<processes>] [--generator=<generator>]'.PHP_EOL;
1368
-        echo '  [--extensions=<extensions>] [--ignore=<patterns>] [--ignore-annotations]'.PHP_EOL;
1369
-        echo '  [--stdin-path=<stdinPath>] [--file-list=<fileList>] [--filter=<filter>] <file> - ...'.PHP_EOL;
1370
-        echo PHP_EOL;
1371
-        echo ' -     Check STDIN instead of local files and directories'.PHP_EOL;
1372
-        echo ' -n    Do not print warnings (shortcut for --warning-severity=0)'.PHP_EOL;
1373
-        echo ' -w    Print both warnings and errors (this is the default)'.PHP_EOL;
1374
-        echo ' -l    Local directory only, no recursion'.PHP_EOL;
1375
-        echo ' -s    Show sniff codes in all reports'.PHP_EOL;
1376
-        echo ' -a    Run interactively'.PHP_EOL;
1377
-        echo ' -e    Explain a standard by showing the sniffs it includes'.PHP_EOL;
1378
-        echo ' -p    Show progress of the run'.PHP_EOL;
1379
-        echo ' -q    Quiet mode; disables progress and verbose output'.PHP_EOL;
1380
-        echo ' -m    Stop error messages from being recorded'.PHP_EOL;
1381
-        echo '       (saves a lot of memory, but stops many reports from being used)'.PHP_EOL;
1382
-        echo ' -v    Print processed files'.PHP_EOL;
1383
-        echo ' -vv   Print ruleset and token output'.PHP_EOL;
1384
-        echo ' -vvv  Print sniff processing information'.PHP_EOL;
1385
-        echo ' -i    Show a list of installed coding standards'.PHP_EOL;
1386
-        echo ' -d    Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL;
1387
-        echo PHP_EOL;
1388
-        echo ' --help                Print this help message'.PHP_EOL;
1389
-        echo ' --version             Print version information'.PHP_EOL;
1390
-        echo ' --colors              Use colors in output'.PHP_EOL;
1391
-        echo ' --no-colors           Do not use colors in output (this is the default)'.PHP_EOL;
1392
-        echo ' --cache               Cache results between runs'.PHP_EOL;
1393
-        echo ' --no-cache            Do not cache results between runs (this is the default)'.PHP_EOL;
1394
-        echo ' --ignore-annotations  Ignore all phpcs: annotations in code comments'.PHP_EOL;
1395
-        echo PHP_EOL;
1396
-        echo ' <cacheFile>    Use a specific file for caching (uses a temporary file by default)'.PHP_EOL;
1397
-        echo ' <basepath>     A path to strip from the front of file paths inside reports'.PHP_EOL;
1398
-        echo ' <bootstrap>    A comma separated list of files to run before processing begins'.PHP_EOL;
1399
-        echo ' <encoding>     The encoding of the files being checked (default is utf-8)'.PHP_EOL;
1400
-        echo ' <extensions>   A comma separated list of file extensions to check'.PHP_EOL;
1401
-        echo '                The type of the file can be specified using: ext/type'.PHP_EOL;
1402
-        echo '                e.g., module/php,es/js'.PHP_EOL;
1403
-        echo ' <file>         One or more files and/or directories to check'.PHP_EOL;
1404
-        echo ' <fileList>     A file containing a list of files and/or directories to check (one per line)'.PHP_EOL;
1405
-        echo ' <filter>       Use the "gitmodified" filter, or specify the path to a custom filter class'.PHP_EOL;
1406
-        echo ' <generator>    Uses either the "HTML", "Markdown" or "Text" generator'.PHP_EOL;
1407
-        echo '                (forces documentation generation instead of checking)'.PHP_EOL;
1408
-        echo ' <patterns>     A comma separated list of patterns to ignore files and directories'.PHP_EOL;
1409
-        echo ' <processes>    How many files should be checked simultaneously (default is 1)'.PHP_EOL;
1410
-        echo ' <report>       Print either the "full", "xml", "checkstyle", "csv"'.PHP_EOL;
1411
-        echo '                "json", "junit", "emacs", "source", "summary", "diff"'.PHP_EOL;
1412
-        echo '                "svnblame", "gitblame", "hgblame" or "notifysend" report,'.PHP_EOL;
1413
-        echo '                or specify the path to a custom report class'.PHP_EOL;
1414
-        echo '                (the "full" report is printed by default)'.PHP_EOL;
1415
-        echo ' <reportFile>   Write the report to the specified file path'.PHP_EOL;
1416
-        echo ' <reportWidth>  How many columns wide screen reports should be printed'.PHP_EOL;
1417
-        echo '                or set to "auto" to use current screen width, where supported'.PHP_EOL;
1418
-        echo ' <severity>     The minimum severity required to display an error or warning'.PHP_EOL;
1419
-        echo ' <sniffs>       A comma separated list of sniff codes to include or exclude from checking'.PHP_EOL;
1420
-        echo '                (all sniffs must be part of the specified standard)'.PHP_EOL;
1421
-        echo ' <standard>     The name or path of the coding standard to use'.PHP_EOL;
1422
-        echo ' <stdinPath>    If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL;
1423
-        echo ' <tabWidth>     The number of spaces each tab represents'.PHP_EOL;
1424
-
1425
-    }//end printPHPCSUsage()
1426
-
1427
-
1428
-    /**
1429
-     * Prints out the usage information for PHPCBF.
1430
-     *
1431
-     * @return void
1432
-     */
1433
-    public function printPHPCBFUsage()
1434
-    {
1435
-        echo 'Usage: phpcbf [-nwli] [-d key[=value]] [--ignore-annotations] [--bootstrap=<bootstrap>]'.PHP_EOL;
1436
-        echo '  [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>] [--suffix=<suffix>]'.PHP_EOL;
1437
-        echo '  [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL;
1438
-        echo '  [--tab-width=<tabWidth>] [--encoding=<encoding>] [--parallel=<processes>]'.PHP_EOL;
1439
-        echo '  [--basepath=<basepath>] [--extensions=<extensions>] [--ignore=<patterns>]'.PHP_EOL;
1440
-        echo '  [--stdin-path=<stdinPath>] [--file-list=<fileList>] [--filter=<filter>] <file> - ...'.PHP_EOL;
1441
-        echo PHP_EOL;
1442
-        echo ' -     Fix STDIN instead of local files and directories'.PHP_EOL;
1443
-        echo ' -n    Do not fix warnings (shortcut for --warning-severity=0)'.PHP_EOL;
1444
-        echo ' -w    Fix both warnings and errors (on by default)'.PHP_EOL;
1445
-        echo ' -l    Local directory only, no recursion'.PHP_EOL;
1446
-        echo ' -p    Show progress of the run'.PHP_EOL;
1447
-        echo ' -q    Quiet mode; disables progress and verbose output'.PHP_EOL;
1448
-        echo ' -v    Print processed files'.PHP_EOL;
1449
-        echo ' -vv   Print ruleset and token output'.PHP_EOL;
1450
-        echo ' -vvv  Print sniff processing information'.PHP_EOL;
1451
-        echo ' -i    Show a list of installed coding standards'.PHP_EOL;
1452
-        echo ' -d    Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL;
1453
-        echo PHP_EOL;
1454
-        echo ' --help                Print this help message'.PHP_EOL;
1455
-        echo ' --version             Print version information'.PHP_EOL;
1456
-        echo ' --ignore-annotations  Ignore all phpcs: annotations in code comments'.PHP_EOL;
1457
-        echo PHP_EOL;
1458
-        echo ' <basepath>    A path to strip from the front of file paths inside reports'.PHP_EOL;
1459
-        echo ' <bootstrap>   A comma separated list of files to run before processing begins'.PHP_EOL;
1460
-        echo ' <encoding>    The encoding of the files being fixed (default is utf-8)'.PHP_EOL;
1461
-        echo ' <extensions>  A comma separated list of file extensions to fix'.PHP_EOL;
1462
-        echo '               The type of the file can be specified using: ext/type'.PHP_EOL;
1463
-        echo '               e.g., module/php,es/js'.PHP_EOL;
1464
-        echo ' <file>        One or more files and/or directories to fix'.PHP_EOL;
1465
-        echo ' <fileList>    A file containing a list of files and/or directories to fix (one per line)'.PHP_EOL;
1466
-        echo ' <filter>      Use the "gitmodified" filter, or specify the path to a custom filter class'.PHP_EOL;
1467
-        echo ' <patterns>    A comma separated list of patterns to ignore files and directories'.PHP_EOL;
1468
-        echo ' <processes>   How many files should be fixed simultaneously (default is 1)'.PHP_EOL;
1469
-        echo ' <severity>    The minimum severity required to fix an error or warning'.PHP_EOL;
1470
-        echo ' <sniffs>      A comma separated list of sniff codes to include or exclude from fixing'.PHP_EOL;
1471
-        echo '               (all sniffs must be part of the specified standard)'.PHP_EOL;
1472
-        echo ' <standard>    The name or path of the coding standard to use'.PHP_EOL;
1473
-        echo ' <stdinPath>   If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL;
1474
-        echo ' <suffix>      Write modified files to a filename using this suffix'.PHP_EOL;
1475
-        echo '               ("diff" and "patch" are not used in this mode)'.PHP_EOL;
1476
-        echo ' <tabWidth>    The number of spaces each tab represents'.PHP_EOL;
1477
-
1478
-    }//end printPHPCBFUsage()
1479
-
1480
-
1481
-    /**
1482
-     * Get a single config value.
1483
-     *
1484
-     * @param string $key The name of the config value.
1485
-     *
1486
-     * @return string|null
1487
-     * @see    setConfigData()
1488
-     * @see    getAllConfigData()
1489
-     */
1490
-    public static function getConfigData($key)
1491
-    {
1492
-        $phpCodeSnifferConfig = self::getAllConfigData();
1493
-
1494
-        if ($phpCodeSnifferConfig === null) {
1495
-            return null;
1496
-        }
1497
-
1498
-        if (isset($phpCodeSnifferConfig[$key]) === false) {
1499
-            return null;
1500
-        }
1501
-
1502
-        return $phpCodeSnifferConfig[$key];
1503
-
1504
-    }//end getConfigData()
1505
-
1506
-
1507
-    /**
1508
-     * Get the path to an executable utility.
1509
-     *
1510
-     * @param string $name The name of the executable utility.
1511
-     *
1512
-     * @return string|null
1513
-     * @see    getConfigData()
1514
-     */
1515
-    public static function getExecutablePath($name)
1516
-    {
1517
-        $data = self::getConfigData($name.'_path');
1518
-        if ($data !== null) {
1519
-            return $data;
1520
-        }
1521
-
1522
-        if ($name === "php") {
1523
-            // For php, we know the executable path. There's no need to look it up.
1524
-            return PHP_BINARY;
1525
-        }
1526
-
1527
-        if (array_key_exists($name, self::$executablePaths) === true) {
1528
-            return self::$executablePaths[$name];
1529
-        }
1530
-
1531
-        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1532
-            $cmd = 'where '.escapeshellarg($name).' 2> nul';
1533
-        } else {
1534
-            $cmd = 'which '.escapeshellarg($name).' 2> /dev/null';
1535
-        }
1536
-
1537
-        $result = exec($cmd, $output, $retVal);
1538
-        if ($retVal !== 0) {
1539
-            $result = null;
1540
-        }
1541
-
1542
-        self::$executablePaths[$name] = $result;
1543
-        return $result;
1544
-
1545
-    }//end getExecutablePath()
1546
-
1547
-
1548
-    /**
1549
-     * Set a single config value.
1550
-     *
1551
-     * @param string      $key   The name of the config value.
1552
-     * @param string|null $value The value to set. If null, the config
1553
-     *                           entry is deleted, reverting it to the
1554
-     *                           default value.
1555
-     * @param boolean     $temp  Set this config data temporarily for this
1556
-     *                           script run. This will not write the config
1557
-     *                           data to the config file.
1558
-     *
1559
-     * @return bool
1560
-     * @see    getConfigData()
1561
-     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the config file can not be written.
1562
-     */
1563
-    public static function setConfigData($key, $value, $temp=false)
1564
-    {
1565
-        if (isset(self::$overriddenDefaults['runtime-set']) === true
1566
-            && isset(self::$overriddenDefaults['runtime-set'][$key]) === true
1567
-        ) {
1568
-            return false;
1569
-        }
1570
-
1571
-        if ($temp === false) {
1572
-            $path = '';
1573
-            if (is_callable('\Phar::running') === true) {
1574
-                $path = \Phar::running(false);
1575
-            }
1576
-
1577
-            if ($path !== '') {
1578
-                $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1579
-            } else {
1580
-                $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1581
-                if (is_file($configFile) === false
1582
-                    && strpos('@data_dir@', '@data_dir') === false
1583
-                ) {
1584
-                    // If data_dir was replaced, this is a PEAR install and we can
1585
-                    // use the PEAR data dir to store the conf file.
1586
-                    $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
1587
-                }
1588
-            }
1589
-
1590
-            if (is_file($configFile) === true
1591
-                && is_writable($configFile) === false
1592
-            ) {
1593
-                $error = 'ERROR: Config file '.$configFile.' is not writable'.PHP_EOL.PHP_EOL;
1594
-                throw new DeepExitException($error, 3);
1595
-            }
1596
-        }//end if
1597
-
1598
-        $phpCodeSnifferConfig = self::getAllConfigData();
1599
-
1600
-        if ($value === null) {
1601
-            if (isset($phpCodeSnifferConfig[$key]) === true) {
1602
-                unset($phpCodeSnifferConfig[$key]);
1603
-            }
1604
-        } else {
1605
-            $phpCodeSnifferConfig[$key] = $value;
1606
-        }
1607
-
1608
-        if ($temp === false) {
1609
-            $output  = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
1610
-            $output .= var_export($phpCodeSnifferConfig, true);
1611
-            $output .= "\n?".'>';
1612
-
1613
-            if (file_put_contents($configFile, $output) === false) {
1614
-                $error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL;
1615
-                throw new DeepExitException($error, 3);
1616
-            }
1617
-
1618
-            self::$configDataFile = $configFile;
1619
-        }
1620
-
1621
-        self::$configData = $phpCodeSnifferConfig;
1622
-
1623
-        // If the installed paths are being set, make sure all known
1624
-        // standards paths are added to the autoloader.
1625
-        if ($key === 'installed_paths') {
1626
-            $installedStandards = Util\Standards::getInstalledStandardDetails();
1627
-            foreach ($installedStandards as $name => $details) {
1628
-                Autoload::addSearchPath($details['path'], $details['namespace']);
1629
-            }
1630
-        }
1631
-
1632
-        return true;
1633
-
1634
-    }//end setConfigData()
1635
-
1636
-
1637
-    /**
1638
-     * Get all config data.
1639
-     *
1640
-     * @return array<string, string>
1641
-     * @see    getConfigData()
1642
-     */
1643
-    public static function getAllConfigData()
1644
-    {
1645
-        if (self::$configData !== null) {
1646
-            return self::$configData;
1647
-        }
1648
-
1649
-        $path = '';
1650
-        if (is_callable('\Phar::running') === true) {
1651
-            $path = \Phar::running(false);
1652
-        }
1653
-
1654
-        if ($path !== '') {
1655
-            $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1656
-        } else {
1657
-            $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1658
-            if (is_file($configFile) === false
1659
-                && strpos('@data_dir@', '@data_dir') === false
1660
-            ) {
1661
-                $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
1662
-            }
1663
-        }
1664
-
1665
-        if (is_file($configFile) === false) {
1666
-            self::$configData = [];
1667
-            return [];
1668
-        }
1669
-
1670
-        if (is_readable($configFile) === false) {
1671
-            $error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL;
1672
-            throw new DeepExitException($error, 3);
1673
-        }
1674
-
1675
-        include $configFile;
1676
-        self::$configDataFile = $configFile;
1677
-        self::$configData     = $phpCodeSnifferConfig;
1678
-        return self::$configData;
1679
-
1680
-    }//end getAllConfigData()
1681
-
1682
-
1683
-    /**
1684
-     * Prints out the gathered config data.
1685
-     *
1686
-     * @param array $data The config data to print.
1687
-     *
1688
-     * @return void
1689
-     */
1690
-    public function printConfigData($data)
1691
-    {
1692
-        $max  = 0;
1693
-        $keys = array_keys($data);
1694
-        foreach ($keys as $key) {
1695
-            $len = strlen($key);
1696
-            if (strlen($key) > $max) {
1697
-                $max = $len;
1698
-            }
1699
-        }
1700
-
1701
-        if ($max === 0) {
1702
-            return;
1703
-        }
1704
-
1705
-        $max += 2;
1706
-        ksort($data);
1707
-        foreach ($data as $name => $value) {
1708
-            echo str_pad($name.': ', $max).$value.PHP_EOL;
1709
-        }
1710
-
1711
-    }//end printConfigData()
21
+	/**
22
+	 * The current version.
23
+	 *
24
+	 * @var string
25
+	 */
26
+	const VERSION = '3.4.2';
27
+
28
+	/**
29
+	 * Package stability; either stable, beta or alpha.
30
+	 *
31
+	 * @var string
32
+	 */
33
+	const STABILITY = 'stable';
34
+
35
+	/**
36
+	 * An array of settings that PHPCS and PHPCBF accept.
37
+	 *
38
+	 * This array is not meant to be accessed directly. Instead, use the settings
39
+	 * as if they are class member vars so the __get() and __set() magic methods
40
+	 * can be used to validate the values. For example, to set the verbosity level to
41
+	 * level 2, use $this->verbosity = 2; instead of accessing this property directly.
42
+	 *
43
+	 * The list of settings are:
44
+	 *
45
+	 * string[] files           The files and directories to check.
46
+	 * string[] standards       The standards being used for checking.
47
+	 * int      verbosity       How verbose the output should be.
48
+	 *                          0: no unnecessary output
49
+	 *                          1: basic output for files being checked
50
+	 *                          2: ruleset and file parsing output
51
+	 *                          3: sniff execution output
52
+	 * bool     interactive     Enable interactive checking mode.
53
+	 * bool     parallel        Check files in parallel.
54
+	 * bool     cache           Enable the use of the file cache.
55
+	 * bool     cacheFile       A file where the cache data should be written
56
+	 * bool     colors          Display colours in output.
57
+	 * bool     explain         Explain the coding standards.
58
+	 * bool     local           Process local files in directories only (no recursion).
59
+	 * bool     showSources     Show sniff source codes in report output.
60
+	 * bool     showProgress    Show basic progress information while running.
61
+	 * bool     quiet           Quiet mode; disables progress and verbose output.
62
+	 * bool     annotations     Process phpcs: annotations.
63
+	 * int      tabWidth        How many spaces each tab is worth.
64
+	 * string   encoding        The encoding of the files being checked.
65
+	 * string[] sniffs          The sniffs that should be used for checking.
66
+	 *                          If empty, all sniffs in the supplied standards will be used.
67
+	 * string[] exclude         The sniffs that should be excluded from checking.
68
+	 *                          If empty, all sniffs in the supplied standards will be used.
69
+	 * string[] ignored         Regular expressions used to ignore files and folders during checking.
70
+	 * string   reportFile      A file where the report output should be written.
71
+	 * string   generator       The documentation generator to use.
72
+	 * string   filter          The filter to use for the run.
73
+	 * string[] bootstrap       One of more files to include before the run begins.
74
+	 * int      reportWidth     The maximum number of columns that reports should use for output.
75
+	 *                          Set to "auto" for have this value changed to the width of the terminal.
76
+	 * int      errorSeverity   The minimum severity an error must have to be displayed.
77
+	 * int      warningSeverity The minimum severity a warning must have to be displayed.
78
+	 * bool     recordErrors    Record the content of error messages as well as error counts.
79
+	 * string   suffix          A suffix to add to fixed files.
80
+	 * string   basepath        A file system location to strip from the paths of files shown in reports.
81
+	 * bool     stdin           Read content from STDIN instead of supplied files.
82
+	 * string   stdinContent    Content passed directly to PHPCS on STDIN.
83
+	 * string   stdinPath       The path to use for content passed on STDIN.
84
+	 *
85
+	 * array<string, string>      extensions File extensions that should be checked, and what tokenizer to use.
86
+	 *                                       E.g., array('inc' => 'PHP');
87
+	 * array<string, string|null> reports    The reports to use for printing output after the run.
88
+	 *                                       The format of the array is:
89
+	 *                                           array(
90
+	 *                                            'reportName1' => 'outputFile',
91
+	 *                                            'reportName2' => null,
92
+	 *                                           );
93
+	 *                                       If the array value is NULL, the report will be written to the screen.
94
+	 *
95
+	 * string[] unknown Any arguments gathered on the command line that are unknown to us.
96
+	 *                  E.g., using `phpcs -c` will give array('c');
97
+	 *
98
+	 * @var array<string, mixed>
99
+	 */
100
+	private $settings = [
101
+		'files'           => null,
102
+		'standards'       => null,
103
+		'verbosity'       => null,
104
+		'interactive'     => null,
105
+		'parallel'        => null,
106
+		'cache'           => null,
107
+		'cacheFile'       => null,
108
+		'colors'          => null,
109
+		'explain'         => null,
110
+		'local'           => null,
111
+		'showSources'     => null,
112
+		'showProgress'    => null,
113
+		'quiet'           => null,
114
+		'annotations'     => null,
115
+		'tabWidth'        => null,
116
+		'encoding'        => null,
117
+		'extensions'      => null,
118
+		'sniffs'          => null,
119
+		'exclude'         => null,
120
+		'ignored'         => null,
121
+		'reportFile'      => null,
122
+		'generator'       => null,
123
+		'filter'          => null,
124
+		'bootstrap'       => null,
125
+		'reports'         => null,
126
+		'basepath'        => null,
127
+		'reportWidth'     => null,
128
+		'errorSeverity'   => null,
129
+		'warningSeverity' => null,
130
+		'recordErrors'    => null,
131
+		'suffix'          => null,
132
+		'stdin'           => null,
133
+		'stdinContent'    => null,
134
+		'stdinPath'       => null,
135
+		'unknown'         => null,
136
+	];
137
+
138
+	/**
139
+	 * Whether or not to kill the process when an unknown command line arg is found.
140
+	 *
141
+	 * If FALSE, arguments that are not command line options or file/directory paths
142
+	 * will be ignored and execution will continue. These values will be stored in
143
+	 * $this->unknown.
144
+	 *
145
+	 * @var boolean
146
+	 */
147
+	public $dieOnUnknownArg;
148
+
149
+	/**
150
+	 * The current command line arguments we are processing.
151
+	 *
152
+	 * @var string[]
153
+	 */
154
+	private $cliArgs = [];
155
+
156
+	/**
157
+	 * Command line values that the user has supplied directly.
158
+	 *
159
+	 * @var array<string, TRUE>
160
+	 */
161
+	private static $overriddenDefaults = [];
162
+
163
+	/**
164
+	 * Config file data that has been loaded for the run.
165
+	 *
166
+	 * @var array<string, string>
167
+	 */
168
+	private static $configData = null;
169
+
170
+	/**
171
+	 * The full path to the config data file that has been loaded.
172
+	 *
173
+	 * @var string
174
+	 */
175
+	private static $configDataFile = null;
176
+
177
+	/**
178
+	 * Automatically discovered executable utility paths.
179
+	 *
180
+	 * @var array<string, string>
181
+	 */
182
+	private static $executablePaths = [];
183
+
184
+
185
+	/**
186
+	 * Get the value of an inaccessible property.
187
+	 *
188
+	 * @param string $name The name of the property.
189
+	 *
190
+	 * @return mixed
191
+	 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
192
+	 */
193
+	public function __get($name)
194
+	{
195
+		if (array_key_exists($name, $this->settings) === false) {
196
+			throw new RuntimeException("ERROR: unable to get value of property \"$name\"");
197
+		}
198
+
199
+		return $this->settings[$name];
200
+
201
+	}//end __get()
202
+
203
+
204
+	/**
205
+	 * Set the value of an inaccessible property.
206
+	 *
207
+	 * @param string $name  The name of the property.
208
+	 * @param mixed  $value The value of the property.
209
+	 *
210
+	 * @return void
211
+	 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
212
+	 */
213
+	public function __set($name, $value)
214
+	{
215
+		if (array_key_exists($name, $this->settings) === false) {
216
+			throw new RuntimeException("Can't __set() $name; setting doesn't exist");
217
+		}
218
+
219
+		switch ($name) {
220
+		case 'reportWidth' :
221
+			// Support auto terminal width.
222
+			if ($value === 'auto' && preg_match('|\d+ (\d+)|', shell_exec('stty size 2>&1'), $matches) === 1) {
223
+				$value = (int) $matches[1];
224
+			} else {
225
+				$value = (int) $value;
226
+			}
227
+			break;
228
+		case 'standards' :
229
+			$cleaned = [];
230
+
231
+			// Check if the standard name is valid, or if the case is invalid.
232
+			$installedStandards = Util\Standards::getInstalledStandards();
233
+			foreach ($value as $standard) {
234
+				foreach ($installedStandards as $validStandard) {
235
+					if (strtolower($standard) === strtolower($validStandard)) {
236
+						$standard = $validStandard;
237
+						break;
238
+					}
239
+				}
240
+
241
+				$cleaned[] = $standard;
242
+			}
243
+
244
+			$value = $cleaned;
245
+			break;
246
+		default :
247
+			// No validation required.
248
+			break;
249
+		}//end switch
250
+
251
+		$this->settings[$name] = $value;
252
+
253
+	}//end __set()
254
+
255
+
256
+	/**
257
+	 * Check if the value of an inaccessible property is set.
258
+	 *
259
+	 * @param string $name The name of the property.
260
+	 *
261
+	 * @return bool
262
+	 */
263
+	public function __isset($name)
264
+	{
265
+		return isset($this->settings[$name]);
266
+
267
+	}//end __isset()
268
+
269
+
270
+	/**
271
+	 * Unset the value of an inaccessible property.
272
+	 *
273
+	 * @param string $name The name of the property.
274
+	 *
275
+	 * @return void
276
+	 */
277
+	public function __unset($name)
278
+	{
279
+		$this->settings[$name] = null;
280
+
281
+	}//end __unset()
282
+
283
+
284
+	/**
285
+	 * Get the array of all config settings.
286
+	 *
287
+	 * @return array<string, mixed>
288
+	 */
289
+	public function getSettings()
290
+	{
291
+		return $this->settings;
292
+
293
+	}//end getSettings()
294
+
295
+
296
+	/**
297
+	 * Set the array of all config settings.
298
+	 *
299
+	 * @param array<string, mixed> $settings The array of config settings.
300
+	 *
301
+	 * @return void
302
+	 */
303
+	public function setSettings($settings)
304
+	{
305
+		return $this->settings = $settings;
306
+
307
+	}//end setSettings()
308
+
309
+
310
+	/**
311
+	 * Creates a Config object and populates it with command line values.
312
+	 *
313
+	 * @param array $cliArgs         An array of values gathered from CLI args.
314
+	 * @param bool  $dieOnUnknownArg Whether or not to kill the process when an
315
+	 *                               unknown command line arg is found.
316
+	 *
317
+	 * @return void
318
+	 */
319
+	public function __construct(array $cliArgs=[], $dieOnUnknownArg=true)
320
+	{
321
+		if (defined('PHP_CODESNIFFER_IN_TESTS') === true) {
322
+			// Let everything through during testing so that we can
323
+			// make use of PHPUnit command line arguments as well.
324
+			$this->dieOnUnknownArg = false;
325
+		} else {
326
+			$this->dieOnUnknownArg = $dieOnUnknownArg;
327
+		}
328
+
329
+		if (empty($cliArgs) === true) {
330
+			$cliArgs = $_SERVER['argv'];
331
+			array_shift($cliArgs);
332
+		}
333
+
334
+		$this->restoreDefaults();
335
+		$this->setCommandLineValues($cliArgs);
336
+
337
+		if (isset(self::$overriddenDefaults['standards']) === false) {
338
+			// They did not supply a standard to use.
339
+			// Look for a default ruleset in the current directory or higher.
340
+			$currentDir = getcwd();
341
+
342
+			$defaultFiles = [
343
+				'.phpcs.xml',
344
+				'phpcs.xml',
345
+				'.phpcs.xml.dist',
346
+				'phpcs.xml.dist',
347
+			];
348
+
349
+			do {
350
+				foreach ($defaultFiles as $defaultFilename) {
351
+					$default = $currentDir.DIRECTORY_SEPARATOR.$defaultFilename;
352
+					if (is_file($default) === true) {
353
+						$this->standards = [$default];
354
+						break(2);
355
+					}
356
+				}
357
+
358
+				$lastDir    = $currentDir;
359
+				$currentDir = dirname($currentDir);
360
+			} while ($currentDir !== '.' && $currentDir !== $lastDir);
361
+		}//end if
362
+
363
+		if (defined('STDIN') === false
364
+			|| strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'
365
+		) {
366
+			return;
367
+		}
368
+
369
+		$handle = fopen('php://stdin', 'r');
370
+
371
+		// Check for content on STDIN.
372
+		if ($this->stdin === true
373
+			|| (Util\Common::isStdinATTY() === false
374
+			&& feof($handle) === false)
375
+		) {
376
+			$readStreams = [$handle];
377
+			$writeSteams = null;
378
+
379
+			$fileContents = '';
380
+			while (is_resource($handle) === true && feof($handle) === false) {
381
+				// Set a timeout of 200ms.
382
+				if (stream_select($readStreams, $writeSteams, $writeSteams, 0, 200000) === 0) {
383
+					break;
384
+				}
385
+
386
+				$fileContents .= fgets($handle);
387
+			}
388
+
389
+			if (trim($fileContents) !== '') {
390
+				$this->stdin        = true;
391
+				$this->stdinContent = $fileContents;
392
+				self::$overriddenDefaults['stdin']        = true;
393
+				self::$overriddenDefaults['stdinContent'] = true;
394
+			}
395
+		}//end if
396
+
397
+		fclose($handle);
398
+
399
+	}//end __construct()
400
+
401
+
402
+	/**
403
+	 * Set the command line values.
404
+	 *
405
+	 * @param array $args An array of command line arguments to set.
406
+	 *
407
+	 * @return void
408
+	 */
409
+	public function setCommandLineValues($args)
410
+	{
411
+		$this->cliArgs = $args;
412
+		$numArgs       = count($args);
413
+
414
+		for ($i = 0; $i < $numArgs; $i++) {
415
+			$arg = $this->cliArgs[$i];
416
+			if ($arg === '') {
417
+				continue;
418
+			}
419
+
420
+			if ($arg{0} === '-') {
421
+				if ($arg === '-') {
422
+					// Asking to read from STDIN.
423
+					$this->stdin = true;
424
+					self::$overriddenDefaults['stdin'] = true;
425
+					continue;
426
+				}
427
+
428
+				if ($arg === '--') {
429
+					// Empty argument, ignore it.
430
+					continue;
431
+				}
432
+
433
+				if ($arg{1} === '-') {
434
+					$this->processLongArgument(substr($arg, 2), $i);
435
+				} else {
436
+					$switches = str_split($arg);
437
+					foreach ($switches as $switch) {
438
+						if ($switch === '-') {
439
+							continue;
440
+						}
441
+
442
+						$this->processShortArgument($switch, $i);
443
+					}
444
+				}
445
+			} else {
446
+				$this->processUnknownArgument($arg, $i);
447
+			}//end if
448
+		}//end for
449
+
450
+	}//end setCommandLineValues()
451
+
452
+
453
+	/**
454
+	 * Restore default values for all possible command line arguments.
455
+	 *
456
+	 * @return array
457
+	 */
458
+	public function restoreDefaults()
459
+	{
460
+		$this->files           = [];
461
+		$this->standards       = ['PEAR'];
462
+		$this->verbosity       = 0;
463
+		$this->interactive     = false;
464
+		$this->cache           = false;
465
+		$this->cacheFile       = null;
466
+		$this->colors          = false;
467
+		$this->explain         = false;
468
+		$this->local           = false;
469
+		$this->showSources     = false;
470
+		$this->showProgress    = false;
471
+		$this->quiet           = false;
472
+		$this->annotations     = true;
473
+		$this->parallel        = 1;
474
+		$this->tabWidth        = 0;
475
+		$this->encoding        = 'utf-8';
476
+		$this->extensions      = [
477
+			'php' => 'PHP',
478
+			'inc' => 'PHP',
479
+			'js'  => 'JS',
480
+			'css' => 'CSS',
481
+		];
482
+		$this->sniffs          = [];
483
+		$this->exclude         = [];
484
+		$this->ignored         = [];
485
+		$this->reportFile      = null;
486
+		$this->generator       = null;
487
+		$this->filter          = null;
488
+		$this->bootstrap       = [];
489
+		$this->basepath        = null;
490
+		$this->reports         = ['full' => null];
491
+		$this->reportWidth     = 'auto';
492
+		$this->errorSeverity   = 5;
493
+		$this->warningSeverity = 5;
494
+		$this->recordErrors    = true;
495
+		$this->suffix          = '';
496
+		$this->stdin           = false;
497
+		$this->stdinContent    = null;
498
+		$this->stdinPath       = null;
499
+		$this->unknown         = [];
500
+
501
+		$standard = self::getConfigData('default_standard');
502
+		if ($standard !== null) {
503
+			$this->standards = explode(',', $standard);
504
+		}
505
+
506
+		$reportFormat = self::getConfigData('report_format');
507
+		if ($reportFormat !== null) {
508
+			$this->reports = [$reportFormat => null];
509
+		}
510
+
511
+		$tabWidth = self::getConfigData('tab_width');
512
+		if ($tabWidth !== null) {
513
+			$this->tabWidth = (int) $tabWidth;
514
+		}
515
+
516
+		$encoding = self::getConfigData('encoding');
517
+		if ($encoding !== null) {
518
+			$this->encoding = strtolower($encoding);
519
+		}
520
+
521
+		$severity = self::getConfigData('severity');
522
+		if ($severity !== null) {
523
+			$this->errorSeverity   = (int) $severity;
524
+			$this->warningSeverity = (int) $severity;
525
+		}
526
+
527
+		$severity = self::getConfigData('error_severity');
528
+		if ($severity !== null) {
529
+			$this->errorSeverity = (int) $severity;
530
+		}
531
+
532
+		$severity = self::getConfigData('warning_severity');
533
+		if ($severity !== null) {
534
+			$this->warningSeverity = (int) $severity;
535
+		}
536
+
537
+		$showWarnings = self::getConfigData('show_warnings');
538
+		if ($showWarnings !== null) {
539
+			$showWarnings = (bool) $showWarnings;
540
+			if ($showWarnings === false) {
541
+				$this->warningSeverity = 0;
542
+			}
543
+		}
544
+
545
+		$reportWidth = self::getConfigData('report_width');
546
+		if ($reportWidth !== null) {
547
+			$this->reportWidth = $reportWidth;
548
+		}
549
+
550
+		$showProgress = self::getConfigData('show_progress');
551
+		if ($showProgress !== null) {
552
+			$this->showProgress = (bool) $showProgress;
553
+		}
554
+
555
+		$quiet = self::getConfigData('quiet');
556
+		if ($quiet !== null) {
557
+			$this->quiet = (bool) $quiet;
558
+		}
559
+
560
+		$colors = self::getConfigData('colors');
561
+		if ($colors !== null) {
562
+			$this->colors = (bool) $colors;
563
+		}
564
+
565
+		if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
566
+			$cache = self::getConfigData('cache');
567
+			if ($cache !== null) {
568
+				$this->cache = (bool) $cache;
569
+			}
570
+
571
+			$parallel = self::getConfigData('parallel');
572
+			if ($parallel !== null) {
573
+				$this->parallel = max((int) $parallel, 1);
574
+			}
575
+		}
576
+
577
+	}//end restoreDefaults()
578
+
579
+
580
+	/**
581
+	 * Processes a short (-e) command line argument.
582
+	 *
583
+	 * @param string $arg The command line argument.
584
+	 * @param int    $pos The position of the argument on the command line.
585
+	 *
586
+	 * @return void
587
+	 */
588
+	public function processShortArgument($arg, $pos)
589
+	{
590
+		switch ($arg) {
591
+		case 'h':
592
+		case '?':
593
+			ob_start();
594
+			$this->printUsage();
595
+			$output = ob_get_contents();
596
+			ob_end_clean();
597
+			throw new DeepExitException($output, 0);
598
+		case 'i' :
599
+			ob_start();
600
+			Util\Standards::printInstalledStandards();
601
+			$output = ob_get_contents();
602
+			ob_end_clean();
603
+			throw new DeepExitException($output, 0);
604
+		case 'v' :
605
+			if ($this->quiet === true) {
606
+				// Ignore when quiet mode is enabled.
607
+				break;
608
+			}
609
+
610
+			$this->verbosity++;
611
+			self::$overriddenDefaults['verbosity'] = true;
612
+			break;
613
+		case 'l' :
614
+			$this->local = true;
615
+			self::$overriddenDefaults['local'] = true;
616
+			break;
617
+		case 's' :
618
+			$this->showSources = true;
619
+			self::$overriddenDefaults['showSources'] = true;
620
+			break;
621
+		case 'a' :
622
+			$this->interactive = true;
623
+			self::$overriddenDefaults['interactive'] = true;
624
+			break;
625
+		case 'e':
626
+			$this->explain = true;
627
+			self::$overriddenDefaults['explain'] = true;
628
+			break;
629
+		case 'p' :
630
+			if ($this->quiet === true) {
631
+				// Ignore when quiet mode is enabled.
632
+				break;
633
+			}
634
+
635
+			$this->showProgress = true;
636
+			self::$overriddenDefaults['showProgress'] = true;
637
+			break;
638
+		case 'q' :
639
+			// Quiet mode disables a few other settings as well.
640
+			$this->quiet        = true;
641
+			$this->showProgress = false;
642
+			$this->verbosity    = 0;
643
+
644
+			self::$overriddenDefaults['quiet'] = true;
645
+			break;
646
+		case 'm' :
647
+			$this->recordErrors = false;
648
+			self::$overriddenDefaults['recordErrors'] = true;
649
+			break;
650
+		case 'd' :
651
+			$ini = explode('=', $this->cliArgs[($pos + 1)]);
652
+			$this->cliArgs[($pos + 1)] = '';
653
+			if (isset($ini[1]) === true) {
654
+				ini_set($ini[0], $ini[1]);
655
+			} else {
656
+				ini_set($ini[0], true);
657
+			}
658
+			break;
659
+		case 'n' :
660
+			if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
661
+				$this->warningSeverity = 0;
662
+				self::$overriddenDefaults['warningSeverity'] = true;
663
+			}
664
+			break;
665
+		case 'w' :
666
+			if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
667
+				$this->warningSeverity = $this->errorSeverity;
668
+				self::$overriddenDefaults['warningSeverity'] = true;
669
+			}
670
+			break;
671
+		default:
672
+			if ($this->dieOnUnknownArg === false) {
673
+				$unknown       = $this->unknown;
674
+				$unknown[]     = $arg;
675
+				$this->unknown = $unknown;
676
+			} else {
677
+				$this->processUnknownArgument('-'.$arg, $pos);
678
+			}
679
+		}//end switch
680
+
681
+	}//end processShortArgument()
682
+
683
+
684
+	/**
685
+	 * Processes a long (--example) command line argument.
686
+	 *
687
+	 * @param string $arg The command line argument.
688
+	 * @param int    $pos The position of the argument on the command line.
689
+	 *
690
+	 * @return void
691
+	 */
692
+	public function processLongArgument($arg, $pos)
693
+	{
694
+		switch ($arg) {
695
+		case 'help':
696
+			ob_start();
697
+			$this->printUsage();
698
+			$output = ob_get_contents();
699
+			ob_end_clean();
700
+			throw new DeepExitException($output, 0);
701
+		case 'version':
702
+			$output  = 'PHP_CodeSniffer version '.self::VERSION.' ('.self::STABILITY.') ';
703
+			$output .= 'by Squiz (http://www.squiz.net)'.PHP_EOL;
704
+			throw new DeepExitException($output, 0);
705
+		case 'colors':
706
+			if (isset(self::$overriddenDefaults['colors']) === true) {
707
+				break;
708
+			}
709
+
710
+			$this->colors = true;
711
+			self::$overriddenDefaults['colors'] = true;
712
+			break;
713
+		case 'no-colors':
714
+			if (isset(self::$overriddenDefaults['colors']) === true) {
715
+				break;
716
+			}
717
+
718
+			$this->colors = false;
719
+			self::$overriddenDefaults['colors'] = true;
720
+			break;
721
+		case 'cache':
722
+			if (isset(self::$overriddenDefaults['cache']) === true) {
723
+				break;
724
+			}
725
+
726
+			if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
727
+				$this->cache = true;
728
+				self::$overriddenDefaults['cache'] = true;
729
+			}
730
+			break;
731
+		case 'no-cache':
732
+			if (isset(self::$overriddenDefaults['cache']) === true) {
733
+				break;
734
+			}
735
+
736
+			$this->cache = false;
737
+			self::$overriddenDefaults['cache'] = true;
738
+			break;
739
+		case 'ignore-annotations':
740
+			if (isset(self::$overriddenDefaults['annotations']) === true) {
741
+				break;
742
+			}
743
+
744
+			$this->annotations = false;
745
+			self::$overriddenDefaults['annotations'] = true;
746
+			break;
747
+		case 'config-set':
748
+			if (isset($this->cliArgs[($pos + 1)]) === false
749
+				|| isset($this->cliArgs[($pos + 2)]) === false
750
+			) {
751
+				$error  = 'ERROR: Setting a config option requires a name and value'.PHP_EOL.PHP_EOL;
752
+				$error .= $this->printShortUsage(true);
753
+				throw new DeepExitException($error, 3);
754
+			}
755
+
756
+			$key     = $this->cliArgs[($pos + 1)];
757
+			$value   = $this->cliArgs[($pos + 2)];
758
+			$current = self::getConfigData($key);
759
+
760
+			try {
761
+				$this->setConfigData($key, $value);
762
+			} catch (\Exception $e) {
763
+				throw new DeepExitException($e->getMessage().PHP_EOL, 3);
764
+			}
765
+
766
+			$output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
767
+
768
+			if ($current === null) {
769
+				$output .= "Config value \"$key\" added successfully".PHP_EOL;
770
+			} else {
771
+				$output .= "Config value \"$key\" updated successfully; old value was \"$current\"".PHP_EOL;
772
+			}
773
+			throw new DeepExitException($output, 0);
774
+		case 'config-delete':
775
+			if (isset($this->cliArgs[($pos + 1)]) === false) {
776
+				$error  = 'ERROR: Deleting a config option requires the name of the option'.PHP_EOL.PHP_EOL;
777
+				$error .= $this->printShortUsage(true);
778
+				throw new DeepExitException($error, 3);
779
+			}
780
+
781
+			$output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
782
+
783
+			$key     = $this->cliArgs[($pos + 1)];
784
+			$current = self::getConfigData($key);
785
+			if ($current === null) {
786
+				$output .= "Config value \"$key\" has not been set".PHP_EOL;
787
+			} else {
788
+				try {
789
+					$this->setConfigData($key, null);
790
+				} catch (\Exception $e) {
791
+					throw new DeepExitException($e->getMessage().PHP_EOL, 3);
792
+				}
793
+
794
+				$output .= "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL;
795
+			}
796
+			throw new DeepExitException($output, 0);
797
+		case 'config-show':
798
+			ob_start();
799
+			$data = self::getAllConfigData();
800
+			echo 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
801
+			$this->printConfigData($data);
802
+			$output = ob_get_contents();
803
+			ob_end_clean();
804
+			throw new DeepExitException($output, 0);
805
+		case 'runtime-set':
806
+			if (isset($this->cliArgs[($pos + 1)]) === false
807
+				|| isset($this->cliArgs[($pos + 2)]) === false
808
+			) {
809
+				$error  = 'ERROR: Setting a runtime config option requires a name and value'.PHP_EOL.PHP_EOL;
810
+				$error .= $this->printShortUsage(true);
811
+				throw new DeepExitException($error, 3);
812
+			}
813
+
814
+			$key   = $this->cliArgs[($pos + 1)];
815
+			$value = $this->cliArgs[($pos + 2)];
816
+			$this->cliArgs[($pos + 1)] = '';
817
+			$this->cliArgs[($pos + 2)] = '';
818
+			self::setConfigData($key, $value, true);
819
+			if (isset(self::$overriddenDefaults['runtime-set']) === false) {
820
+				self::$overriddenDefaults['runtime-set'] = [];
821
+			}
822
+
823
+			self::$overriddenDefaults['runtime-set'][$key] = true;
824
+			break;
825
+		default:
826
+			if (substr($arg, 0, 7) === 'sniffs=') {
827
+				if (isset(self::$overriddenDefaults['sniffs']) === true) {
828
+					break;
829
+				}
830
+
831
+				$sniffs = explode(',', substr($arg, 7));
832
+				foreach ($sniffs as $sniff) {
833
+					if (substr_count($sniff, '.') !== 2) {
834
+						$error  = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL;
835
+						$error .= $this->printShortUsage(true);
836
+						throw new DeepExitException($error, 3);
837
+					}
838
+				}
839
+
840
+				$this->sniffs = $sniffs;
841
+				self::$overriddenDefaults['sniffs'] = true;
842
+			} else if (substr($arg, 0, 8) === 'exclude=') {
843
+				if (isset(self::$overriddenDefaults['exclude']) === true) {
844
+					break;
845
+				}
846
+
847
+				$sniffs = explode(',', substr($arg, 8));
848
+				foreach ($sniffs as $sniff) {
849
+					if (substr_count($sniff, '.') !== 2) {
850
+						$error  = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL;
851
+						$error .= $this->printShortUsage(true);
852
+						throw new DeepExitException($error, 3);
853
+					}
854
+				}
855
+
856
+				$this->exclude = $sniffs;
857
+				self::$overriddenDefaults['exclude'] = true;
858
+			} else if (defined('PHP_CODESNIFFER_IN_TESTS') === false
859
+				&& substr($arg, 0, 6) === 'cache='
860
+			) {
861
+				if ((isset(self::$overriddenDefaults['cache']) === true
862
+					&& $this->cache === false)
863
+					|| isset(self::$overriddenDefaults['cacheFile']) === true
864
+				) {
865
+					break;
866
+				}
867
+
868
+				// Turn caching on.
869
+				$this->cache = true;
870
+				self::$overriddenDefaults['cache'] = true;
871
+
872
+				$this->cacheFile = Util\Common::realpath(substr($arg, 6));
873
+
874
+				// It may not exist and return false instead.
875
+				if ($this->cacheFile === false) {
876
+					$this->cacheFile = substr($arg, 6);
877
+
878
+					$dir = dirname($this->cacheFile);
879
+					if (is_dir($dir) === false) {
880
+						$error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
881
+						$error .= $this->printShortUsage(true);
882
+						throw new DeepExitException($error, 3);
883
+					}
884
+
885
+					if ($dir === '.') {
886
+						// Passed cache file is a file in the current directory.
887
+						$this->cacheFile = getcwd().'/'.basename($this->cacheFile);
888
+					} else {
889
+						if ($dir{0} === '/') {
890
+							// An absolute path.
891
+							$dir = Util\Common::realpath($dir);
892
+						} else {
893
+							$dir = Util\Common::realpath(getcwd().'/'.$dir);
894
+						}
895
+
896
+						if ($dir !== false) {
897
+							// Cache file path is relative.
898
+							$this->cacheFile = $dir.'/'.basename($this->cacheFile);
899
+						}
900
+					}
901
+				}//end if
902
+
903
+				self::$overriddenDefaults['cacheFile'] = true;
904
+
905
+				if (is_dir($this->cacheFile) === true) {
906
+					$error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" is a directory'.PHP_EOL.PHP_EOL;
907
+					$error .= $this->printShortUsage(true);
908
+					throw new DeepExitException($error, 3);
909
+				}
910
+			} else if (substr($arg, 0, 10) === 'bootstrap=') {
911
+				$files     = explode(',', substr($arg, 10));
912
+				$bootstrap = [];
913
+				foreach ($files as $file) {
914
+					$path = Util\Common::realpath($file);
915
+					if ($path === false) {
916
+						$error  = 'ERROR: The specified bootstrap file "'.$file.'" does not exist'.PHP_EOL.PHP_EOL;
917
+						$error .= $this->printShortUsage(true);
918
+						throw new DeepExitException($error, 3);
919
+					}
920
+
921
+					$bootstrap[] = $path;
922
+				}
923
+
924
+				$this->bootstrap = array_merge($this->bootstrap, $bootstrap);
925
+				self::$overriddenDefaults['bootstrap'] = true;
926
+			} else if (substr($arg, 0, 10) === 'file-list=') {
927
+				$fileList = substr($arg, 10);
928
+				$path     = Util\Common::realpath($fileList);
929
+				if ($path === false) {
930
+					$error  = 'ERROR: The specified file list "'.$fileList.'" does not exist'.PHP_EOL.PHP_EOL;
931
+					$error .= $this->printShortUsage(true);
932
+					throw new DeepExitException($error, 3);
933
+				}
934
+
935
+				$files = file($path);
936
+				foreach ($files as $inputFile) {
937
+					$inputFile = trim($inputFile);
938
+
939
+					// Skip empty lines.
940
+					if ($inputFile === '') {
941
+						continue;
942
+					}
943
+
944
+					$this->processFilePath($inputFile);
945
+				}
946
+			} else if (substr($arg, 0, 11) === 'stdin-path=') {
947
+				if (isset(self::$overriddenDefaults['stdinPath']) === true) {
948
+					break;
949
+				}
950
+
951
+				$this->stdinPath = Util\Common::realpath(substr($arg, 11));
952
+
953
+				// It may not exist and return false instead, so use whatever they gave us.
954
+				if ($this->stdinPath === false) {
955
+					$this->stdinPath = trim(substr($arg, 11));
956
+				}
957
+
958
+				self::$overriddenDefaults['stdinPath'] = true;
959
+			} else if (PHP_CODESNIFFER_CBF === false && substr($arg, 0, 12) === 'report-file=') {
960
+				if (isset(self::$overriddenDefaults['reportFile']) === true) {
961
+					break;
962
+				}
963
+
964
+				$this->reportFile = Util\Common::realpath(substr($arg, 12));
965
+
966
+				// It may not exist and return false instead.
967
+				if ($this->reportFile === false) {
968
+					$this->reportFile = substr($arg, 12);
969
+
970
+					$dir = dirname($this->reportFile);
971
+					if (is_dir($dir) === false) {
972
+						$error  = 'ERROR: The specified report file path "'.$this->reportFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
973
+						$error .= $this->printShortUsage(true);
974
+						throw new DeepExitException($error, 3);
975
+					}
976
+
977
+					if ($dir === '.') {
978
+						// Passed report file is a file in the current directory.
979
+						$this->reportFile = getcwd().'/'.basename($this->reportFile);
980
+					} else {
981
+						if ($dir{0} === '/') {
982
+							// An absolute path.
983
+							$dir = Util\Common::realpath($dir);
984
+						} else {
985
+							$dir = Util\Common::realpath(getcwd().'/'.$dir);
986
+						}
987
+
988
+						if ($dir !== false) {
989
+							// Report file path is relative.
990
+							$this->reportFile = $dir.'/'.basename($this->reportFile);
991
+						}
992
+					}
993
+				}//end if
994
+
995
+				self::$overriddenDefaults['reportFile'] = true;
996
+
997
+				if (is_dir($this->reportFile) === true) {
998
+					$error  = 'ERROR: The specified report file path "'.$this->reportFile.'" is a directory'.PHP_EOL.PHP_EOL;
999
+					$error .= $this->printShortUsage(true);
1000
+					throw new DeepExitException($error, 3);
1001
+				}
1002
+			} else if (substr($arg, 0, 13) === 'report-width=') {
1003
+				if (isset(self::$overriddenDefaults['reportWidth']) === true) {
1004
+					break;
1005
+				}
1006
+
1007
+				$this->reportWidth = substr($arg, 13);
1008
+				self::$overriddenDefaults['reportWidth'] = true;
1009
+			} else if (substr($arg, 0, 9) === 'basepath=') {
1010
+				if (isset(self::$overriddenDefaults['basepath']) === true) {
1011
+					break;
1012
+				}
1013
+
1014
+				self::$overriddenDefaults['basepath'] = true;
1015
+
1016
+				if (substr($arg, 9) === '') {
1017
+					$this->basepath = null;
1018
+					break;
1019
+				}
1020
+
1021
+				$this->basepath = Util\Common::realpath(substr($arg, 9));
1022
+
1023
+				// It may not exist and return false instead.
1024
+				if ($this->basepath === false) {
1025
+					$this->basepath = substr($arg, 9);
1026
+				}
1027
+
1028
+				if (is_dir($this->basepath) === false) {
1029
+					$error  = 'ERROR: The specified basepath "'.$this->basepath.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
1030
+					$error .= $this->printShortUsage(true);
1031
+					throw new DeepExitException($error, 3);
1032
+				}
1033
+			} else if ((substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-')) {
1034
+				$reports = [];
1035
+
1036
+				if ($arg[6] === '-') {
1037
+					// This is a report with file output.
1038
+					$split = strpos($arg, '=');
1039
+					if ($split === false) {
1040
+						$report = substr($arg, 7);
1041
+						$output = null;
1042
+					} else {
1043
+						$report = substr($arg, 7, ($split - 7));
1044
+						$output = substr($arg, ($split + 1));
1045
+						if ($output === false) {
1046
+							$output = null;
1047
+						} else {
1048
+							$dir = dirname($output);
1049
+							if (is_dir($dir) === false) {
1050
+								$error  = 'ERROR: The specified '.$report.' report file path "'.$output.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
1051
+								$error .= $this->printShortUsage(true);
1052
+								throw new DeepExitException($error, 3);
1053
+							}
1054
+
1055
+							if ($dir === '.') {
1056
+								// Passed report file is a filename in the current directory.
1057
+								$output = getcwd().'/'.basename($output);
1058
+							} else {
1059
+								if ($dir{0} === '/') {
1060
+									// An absolute path.
1061
+									$dir = Util\Common::realpath($dir);
1062
+								} else {
1063
+									$dir = Util\Common::realpath(getcwd().'/'.$dir);
1064
+								}
1065
+
1066
+								if ($dir !== false) {
1067
+									// Report file path is relative.
1068
+									$output = $dir.'/'.basename($output);
1069
+								}
1070
+							}
1071
+						}//end if
1072
+					}//end if
1073
+
1074
+					$reports[$report] = $output;
1075
+				} else {
1076
+					// This is a single report.
1077
+					if (isset(self::$overriddenDefaults['reports']) === true) {
1078
+						break;
1079
+					}
1080
+
1081
+					$reportNames = explode(',', substr($arg, 7));
1082
+					foreach ($reportNames as $report) {
1083
+						$reports[$report] = null;
1084
+					}
1085
+				}//end if
1086
+
1087
+				// Remove the default value so the CLI value overrides it.
1088
+				if (isset(self::$overriddenDefaults['reports']) === false) {
1089
+					$this->reports = $reports;
1090
+				} else {
1091
+					$this->reports = array_merge($this->reports, $reports);
1092
+				}
1093
+
1094
+				self::$overriddenDefaults['reports'] = true;
1095
+			} else if (substr($arg, 0, 7) === 'filter=') {
1096
+				if (isset(self::$overriddenDefaults['filter']) === true) {
1097
+					break;
1098
+				}
1099
+
1100
+				$this->filter = substr($arg, 7);
1101
+				self::$overriddenDefaults['filter'] = true;
1102
+			} else if (substr($arg, 0, 9) === 'standard=') {
1103
+				$standards = trim(substr($arg, 9));
1104
+				if ($standards !== '') {
1105
+					$this->standards = explode(',', $standards);
1106
+				}
1107
+
1108
+				self::$overriddenDefaults['standards'] = true;
1109
+			} else if (substr($arg, 0, 11) === 'extensions=') {
1110
+				if (isset(self::$overriddenDefaults['extensions']) === true) {
1111
+					break;
1112
+				}
1113
+
1114
+				$extensions    = explode(',', substr($arg, 11));
1115
+				$newExtensions = [];
1116
+				foreach ($extensions as $ext) {
1117
+					$slash = strpos($ext, '/');
1118
+					if ($slash !== false) {
1119
+						// They specified the tokenizer too.
1120
+						list($ext, $tokenizer) = explode('/', $ext);
1121
+						$newExtensions[$ext]   = strtoupper($tokenizer);
1122
+						continue;
1123
+					}
1124
+
1125
+					if (isset($this->extensions[$ext]) === true) {
1126
+						$newExtensions[$ext] = $this->extensions[$ext];
1127
+					} else {
1128
+						$newExtensions[$ext] = 'PHP';
1129
+					}
1130
+				}
1131
+
1132
+				$this->extensions = $newExtensions;
1133
+				self::$overriddenDefaults['extensions'] = true;
1134
+			} else if (substr($arg, 0, 7) === 'suffix=') {
1135
+				if (isset(self::$overriddenDefaults['suffix']) === true) {
1136
+					break;
1137
+				}
1138
+
1139
+				$this->suffix = substr($arg, 7);
1140
+				self::$overriddenDefaults['suffix'] = true;
1141
+			} else if (substr($arg, 0, 9) === 'parallel=') {
1142
+				if (isset(self::$overriddenDefaults['parallel']) === true) {
1143
+					break;
1144
+				}
1145
+
1146
+				$this->parallel = max((int) substr($arg, 9), 1);
1147
+				self::$overriddenDefaults['parallel'] = true;
1148
+			} else if (substr($arg, 0, 9) === 'severity=') {
1149
+				$this->errorSeverity   = (int) substr($arg, 9);
1150
+				$this->warningSeverity = $this->errorSeverity;
1151
+				if (isset(self::$overriddenDefaults['errorSeverity']) === false) {
1152
+					self::$overriddenDefaults['errorSeverity'] = true;
1153
+				}
1154
+
1155
+				if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
1156
+					self::$overriddenDefaults['warningSeverity'] = true;
1157
+				}
1158
+			} else if (substr($arg, 0, 15) === 'error-severity=') {
1159
+				if (isset(self::$overriddenDefaults['errorSeverity']) === true) {
1160
+					break;
1161
+				}
1162
+
1163
+				$this->errorSeverity = (int) substr($arg, 15);
1164
+				self::$overriddenDefaults['errorSeverity'] = true;
1165
+			} else if (substr($arg, 0, 17) === 'warning-severity=') {
1166
+				if (isset(self::$overriddenDefaults['warningSeverity']) === true) {
1167
+					break;
1168
+				}
1169
+
1170
+				$this->warningSeverity = (int) substr($arg, 17);
1171
+				self::$overriddenDefaults['warningSeverity'] = true;
1172
+			} else if (substr($arg, 0, 7) === 'ignore=') {
1173
+				if (isset(self::$overriddenDefaults['ignored']) === true) {
1174
+					break;
1175
+				}
1176
+
1177
+				// Split the ignore string on commas, unless the comma is escaped
1178
+				// using 1 or 3 slashes (\, or \\\,).
1179
+				$patterns = preg_split(
1180
+					'/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/',
1181
+					substr($arg, 7)
1182
+				);
1183
+
1184
+				$ignored = [];
1185
+				foreach ($patterns as $pattern) {
1186
+					$pattern = trim($pattern);
1187
+					if ($pattern === '') {
1188
+						continue;
1189
+					}
1190
+
1191
+					$ignored[$pattern] = 'absolute';
1192
+				}
1193
+
1194
+				$this->ignored = $ignored;
1195
+				self::$overriddenDefaults['ignored'] = true;
1196
+			} else if (substr($arg, 0, 10) === 'generator='
1197
+				&& PHP_CODESNIFFER_CBF === false
1198
+			) {
1199
+				if (isset(self::$overriddenDefaults['generator']) === true) {
1200
+					break;
1201
+				}
1202
+
1203
+				$this->generator = substr($arg, 10);
1204
+				self::$overriddenDefaults['generator'] = true;
1205
+			} else if (substr($arg, 0, 9) === 'encoding=') {
1206
+				if (isset(self::$overriddenDefaults['encoding']) === true) {
1207
+					break;
1208
+				}
1209
+
1210
+				$this->encoding = strtolower(substr($arg, 9));
1211
+				self::$overriddenDefaults['encoding'] = true;
1212
+			} else if (substr($arg, 0, 10) === 'tab-width=') {
1213
+				if (isset(self::$overriddenDefaults['tabWidth']) === true) {
1214
+					break;
1215
+				}
1216
+
1217
+				$this->tabWidth = (int) substr($arg, 10);
1218
+				self::$overriddenDefaults['tabWidth'] = true;
1219
+			} else {
1220
+				if ($this->dieOnUnknownArg === false) {
1221
+					$eqPos = strpos($arg, '=');
1222
+					try {
1223
+						if ($eqPos === false) {
1224
+							$this->values[$arg] = $arg;
1225
+						} else {
1226
+							$value = substr($arg, ($eqPos + 1));
1227
+							$arg   = substr($arg, 0, $eqPos);
1228
+							$this->values[$arg] = $value;
1229
+						}
1230
+					} catch (RuntimeException $e) {
1231
+						// Value is not valid, so just ignore it.
1232
+					}
1233
+				} else {
1234
+					$this->processUnknownArgument('--'.$arg, $pos);
1235
+				}
1236
+			}//end if
1237
+			break;
1238
+		}//end switch
1239
+
1240
+	}//end processLongArgument()
1241
+
1242
+
1243
+	/**
1244
+	 * Processes an unknown command line argument.
1245
+	 *
1246
+	 * Assumes all unknown arguments are files and folders to check.
1247
+	 *
1248
+	 * @param string $arg The command line argument.
1249
+	 * @param int    $pos The position of the argument on the command line.
1250
+	 *
1251
+	 * @return void
1252
+	 */
1253
+	public function processUnknownArgument($arg, $pos)
1254
+	{
1255
+		// We don't know about any additional switches; just files.
1256
+		if ($arg{0} === '-') {
1257
+			if ($this->dieOnUnknownArg === false) {
1258
+				return;
1259
+			}
1260
+
1261
+			$error  = "ERROR: option \"$arg\" not known".PHP_EOL.PHP_EOL;
1262
+			$error .= $this->printShortUsage(true);
1263
+			throw new DeepExitException($error, 3);
1264
+		}
1265
+
1266
+		$this->processFilePath($arg);
1267
+
1268
+	}//end processUnknownArgument()
1269
+
1270
+
1271
+	/**
1272
+	 * Processes a file path and add it to the file list.
1273
+	 *
1274
+	 * @param string $path The path to the file to add.
1275
+	 *
1276
+	 * @return void
1277
+	 */
1278
+	public function processFilePath($path)
1279
+	{
1280
+		// If we are processing STDIN, don't record any files to check.
1281
+		if ($this->stdin === true) {
1282
+			return;
1283
+		}
1284
+
1285
+		$file = Util\Common::realpath($path);
1286
+		if (file_exists($file) === false) {
1287
+			if ($this->dieOnUnknownArg === false) {
1288
+				return;
1289
+			}
1290
+
1291
+			$error  = 'ERROR: The file "'.$path.'" does not exist.'.PHP_EOL.PHP_EOL;
1292
+			$error .= $this->printShortUsage(true);
1293
+			throw new DeepExitException($error, 3);
1294
+		} else {
1295
+			// Can't modify the files array directly because it's not a real
1296
+			// class member, so need to use this little get/modify/set trick.
1297
+			$files       = $this->files;
1298
+			$files[]     = $file;
1299
+			$this->files = $files;
1300
+			self::$overriddenDefaults['files'] = true;
1301
+		}
1302
+
1303
+	}//end processFilePath()
1304
+
1305
+
1306
+	/**
1307
+	 * Prints out the usage information for this script.
1308
+	 *
1309
+	 * @return void
1310
+	 */
1311
+	public function printUsage()
1312
+	{
1313
+		echo PHP_EOL;
1314
+
1315
+		if (PHP_CODESNIFFER_CBF === true) {
1316
+			$this->printPHPCBFUsage();
1317
+		} else {
1318
+			$this->printPHPCSUsage();
1319
+		}
1320
+
1321
+		echo PHP_EOL;
1322
+
1323
+	}//end printUsage()
1324
+
1325
+
1326
+	/**
1327
+	 * Prints out the short usage information for this script.
1328
+	 *
1329
+	 * @param bool $return If TRUE, the usage string is returned
1330
+	 *                     instead of output to screen.
1331
+	 *
1332
+	 * @return string|void
1333
+	 */
1334
+	public function printShortUsage($return=false)
1335
+	{
1336
+		if (PHP_CODESNIFFER_CBF === true) {
1337
+			$usage = 'Run "phpcbf --help" for usage information';
1338
+		} else {
1339
+			$usage = 'Run "phpcs --help" for usage information';
1340
+		}
1341
+
1342
+		$usage .= PHP_EOL.PHP_EOL;
1343
+
1344
+		if ($return === true) {
1345
+			return $usage;
1346
+		}
1347
+
1348
+		echo $usage;
1349
+
1350
+	}//end printShortUsage()
1351
+
1352
+
1353
+	/**
1354
+	 * Prints out the usage information for PHPCS.
1355
+	 *
1356
+	 * @return void
1357
+	 */
1358
+	public function printPHPCSUsage()
1359
+	{
1360
+		echo 'Usage: phpcs [-nwlsaepqvi] [-d key[=value]] [--colors] [--no-colors]'.PHP_EOL;
1361
+		echo '  [--cache[=<cacheFile>]] [--no-cache] [--tab-width=<tabWidth>]'.PHP_EOL;
1362
+		echo '  [--report=<report>] [--report-file=<reportFile>] [--report-<report>=<reportFile>]'.PHP_EOL;
1363
+		echo '  [--report-width=<reportWidth>] [--basepath=<basepath>] [--bootstrap=<bootstrap>]'.PHP_EOL;
1364
+		echo '  [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL;
1365
+		echo '  [--runtime-set key value] [--config-set key value] [--config-delete key] [--config-show]'.PHP_EOL;
1366
+		echo '  [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>]'.PHP_EOL;
1367
+		echo '  [--encoding=<encoding>] [--parallel=<processes>] [--generator=<generator>]'.PHP_EOL;
1368
+		echo '  [--extensions=<extensions>] [--ignore=<patterns>] [--ignore-annotations]'.PHP_EOL;
1369
+		echo '  [--stdin-path=<stdinPath>] [--file-list=<fileList>] [--filter=<filter>] <file> - ...'.PHP_EOL;
1370
+		echo PHP_EOL;
1371
+		echo ' -     Check STDIN instead of local files and directories'.PHP_EOL;
1372
+		echo ' -n    Do not print warnings (shortcut for --warning-severity=0)'.PHP_EOL;
1373
+		echo ' -w    Print both warnings and errors (this is the default)'.PHP_EOL;
1374
+		echo ' -l    Local directory only, no recursion'.PHP_EOL;
1375
+		echo ' -s    Show sniff codes in all reports'.PHP_EOL;
1376
+		echo ' -a    Run interactively'.PHP_EOL;
1377
+		echo ' -e    Explain a standard by showing the sniffs it includes'.PHP_EOL;
1378
+		echo ' -p    Show progress of the run'.PHP_EOL;
1379
+		echo ' -q    Quiet mode; disables progress and verbose output'.PHP_EOL;
1380
+		echo ' -m    Stop error messages from being recorded'.PHP_EOL;
1381
+		echo '       (saves a lot of memory, but stops many reports from being used)'.PHP_EOL;
1382
+		echo ' -v    Print processed files'.PHP_EOL;
1383
+		echo ' -vv   Print ruleset and token output'.PHP_EOL;
1384
+		echo ' -vvv  Print sniff processing information'.PHP_EOL;
1385
+		echo ' -i    Show a list of installed coding standards'.PHP_EOL;
1386
+		echo ' -d    Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL;
1387
+		echo PHP_EOL;
1388
+		echo ' --help                Print this help message'.PHP_EOL;
1389
+		echo ' --version             Print version information'.PHP_EOL;
1390
+		echo ' --colors              Use colors in output'.PHP_EOL;
1391
+		echo ' --no-colors           Do not use colors in output (this is the default)'.PHP_EOL;
1392
+		echo ' --cache               Cache results between runs'.PHP_EOL;
1393
+		echo ' --no-cache            Do not cache results between runs (this is the default)'.PHP_EOL;
1394
+		echo ' --ignore-annotations  Ignore all phpcs: annotations in code comments'.PHP_EOL;
1395
+		echo PHP_EOL;
1396
+		echo ' <cacheFile>    Use a specific file for caching (uses a temporary file by default)'.PHP_EOL;
1397
+		echo ' <basepath>     A path to strip from the front of file paths inside reports'.PHP_EOL;
1398
+		echo ' <bootstrap>    A comma separated list of files to run before processing begins'.PHP_EOL;
1399
+		echo ' <encoding>     The encoding of the files being checked (default is utf-8)'.PHP_EOL;
1400
+		echo ' <extensions>   A comma separated list of file extensions to check'.PHP_EOL;
1401
+		echo '                The type of the file can be specified using: ext/type'.PHP_EOL;
1402
+		echo '                e.g., module/php,es/js'.PHP_EOL;
1403
+		echo ' <file>         One or more files and/or directories to check'.PHP_EOL;
1404
+		echo ' <fileList>     A file containing a list of files and/or directories to check (one per line)'.PHP_EOL;
1405
+		echo ' <filter>       Use the "gitmodified" filter, or specify the path to a custom filter class'.PHP_EOL;
1406
+		echo ' <generator>    Uses either the "HTML", "Markdown" or "Text" generator'.PHP_EOL;
1407
+		echo '                (forces documentation generation instead of checking)'.PHP_EOL;
1408
+		echo ' <patterns>     A comma separated list of patterns to ignore files and directories'.PHP_EOL;
1409
+		echo ' <processes>    How many files should be checked simultaneously (default is 1)'.PHP_EOL;
1410
+		echo ' <report>       Print either the "full", "xml", "checkstyle", "csv"'.PHP_EOL;
1411
+		echo '                "json", "junit", "emacs", "source", "summary", "diff"'.PHP_EOL;
1412
+		echo '                "svnblame", "gitblame", "hgblame" or "notifysend" report,'.PHP_EOL;
1413
+		echo '                or specify the path to a custom report class'.PHP_EOL;
1414
+		echo '                (the "full" report is printed by default)'.PHP_EOL;
1415
+		echo ' <reportFile>   Write the report to the specified file path'.PHP_EOL;
1416
+		echo ' <reportWidth>  How many columns wide screen reports should be printed'.PHP_EOL;
1417
+		echo '                or set to "auto" to use current screen width, where supported'.PHP_EOL;
1418
+		echo ' <severity>     The minimum severity required to display an error or warning'.PHP_EOL;
1419
+		echo ' <sniffs>       A comma separated list of sniff codes to include or exclude from checking'.PHP_EOL;
1420
+		echo '                (all sniffs must be part of the specified standard)'.PHP_EOL;
1421
+		echo ' <standard>     The name or path of the coding standard to use'.PHP_EOL;
1422
+		echo ' <stdinPath>    If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL;
1423
+		echo ' <tabWidth>     The number of spaces each tab represents'.PHP_EOL;
1424
+
1425
+	}//end printPHPCSUsage()
1426
+
1427
+
1428
+	/**
1429
+	 * Prints out the usage information for PHPCBF.
1430
+	 *
1431
+	 * @return void
1432
+	 */
1433
+	public function printPHPCBFUsage()
1434
+	{
1435
+		echo 'Usage: phpcbf [-nwli] [-d key[=value]] [--ignore-annotations] [--bootstrap=<bootstrap>]'.PHP_EOL;
1436
+		echo '  [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>] [--suffix=<suffix>]'.PHP_EOL;
1437
+		echo '  [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL;
1438
+		echo '  [--tab-width=<tabWidth>] [--encoding=<encoding>] [--parallel=<processes>]'.PHP_EOL;
1439
+		echo '  [--basepath=<basepath>] [--extensions=<extensions>] [--ignore=<patterns>]'.PHP_EOL;
1440
+		echo '  [--stdin-path=<stdinPath>] [--file-list=<fileList>] [--filter=<filter>] <file> - ...'.PHP_EOL;
1441
+		echo PHP_EOL;
1442
+		echo ' -     Fix STDIN instead of local files and directories'.PHP_EOL;
1443
+		echo ' -n    Do not fix warnings (shortcut for --warning-severity=0)'.PHP_EOL;
1444
+		echo ' -w    Fix both warnings and errors (on by default)'.PHP_EOL;
1445
+		echo ' -l    Local directory only, no recursion'.PHP_EOL;
1446
+		echo ' -p    Show progress of the run'.PHP_EOL;
1447
+		echo ' -q    Quiet mode; disables progress and verbose output'.PHP_EOL;
1448
+		echo ' -v    Print processed files'.PHP_EOL;
1449
+		echo ' -vv   Print ruleset and token output'.PHP_EOL;
1450
+		echo ' -vvv  Print sniff processing information'.PHP_EOL;
1451
+		echo ' -i    Show a list of installed coding standards'.PHP_EOL;
1452
+		echo ' -d    Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL;
1453
+		echo PHP_EOL;
1454
+		echo ' --help                Print this help message'.PHP_EOL;
1455
+		echo ' --version             Print version information'.PHP_EOL;
1456
+		echo ' --ignore-annotations  Ignore all phpcs: annotations in code comments'.PHP_EOL;
1457
+		echo PHP_EOL;
1458
+		echo ' <basepath>    A path to strip from the front of file paths inside reports'.PHP_EOL;
1459
+		echo ' <bootstrap>   A comma separated list of files to run before processing begins'.PHP_EOL;
1460
+		echo ' <encoding>    The encoding of the files being fixed (default is utf-8)'.PHP_EOL;
1461
+		echo ' <extensions>  A comma separated list of file extensions to fix'.PHP_EOL;
1462
+		echo '               The type of the file can be specified using: ext/type'.PHP_EOL;
1463
+		echo '               e.g., module/php,es/js'.PHP_EOL;
1464
+		echo ' <file>        One or more files and/or directories to fix'.PHP_EOL;
1465
+		echo ' <fileList>    A file containing a list of files and/or directories to fix (one per line)'.PHP_EOL;
1466
+		echo ' <filter>      Use the "gitmodified" filter, or specify the path to a custom filter class'.PHP_EOL;
1467
+		echo ' <patterns>    A comma separated list of patterns to ignore files and directories'.PHP_EOL;
1468
+		echo ' <processes>   How many files should be fixed simultaneously (default is 1)'.PHP_EOL;
1469
+		echo ' <severity>    The minimum severity required to fix an error or warning'.PHP_EOL;
1470
+		echo ' <sniffs>      A comma separated list of sniff codes to include or exclude from fixing'.PHP_EOL;
1471
+		echo '               (all sniffs must be part of the specified standard)'.PHP_EOL;
1472
+		echo ' <standard>    The name or path of the coding standard to use'.PHP_EOL;
1473
+		echo ' <stdinPath>   If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL;
1474
+		echo ' <suffix>      Write modified files to a filename using this suffix'.PHP_EOL;
1475
+		echo '               ("diff" and "patch" are not used in this mode)'.PHP_EOL;
1476
+		echo ' <tabWidth>    The number of spaces each tab represents'.PHP_EOL;
1477
+
1478
+	}//end printPHPCBFUsage()
1479
+
1480
+
1481
+	/**
1482
+	 * Get a single config value.
1483
+	 *
1484
+	 * @param string $key The name of the config value.
1485
+	 *
1486
+	 * @return string|null
1487
+	 * @see    setConfigData()
1488
+	 * @see    getAllConfigData()
1489
+	 */
1490
+	public static function getConfigData($key)
1491
+	{
1492
+		$phpCodeSnifferConfig = self::getAllConfigData();
1493
+
1494
+		if ($phpCodeSnifferConfig === null) {
1495
+			return null;
1496
+		}
1497
+
1498
+		if (isset($phpCodeSnifferConfig[$key]) === false) {
1499
+			return null;
1500
+		}
1501
+
1502
+		return $phpCodeSnifferConfig[$key];
1503
+
1504
+	}//end getConfigData()
1505
+
1506
+
1507
+	/**
1508
+	 * Get the path to an executable utility.
1509
+	 *
1510
+	 * @param string $name The name of the executable utility.
1511
+	 *
1512
+	 * @return string|null
1513
+	 * @see    getConfigData()
1514
+	 */
1515
+	public static function getExecutablePath($name)
1516
+	{
1517
+		$data = self::getConfigData($name.'_path');
1518
+		if ($data !== null) {
1519
+			return $data;
1520
+		}
1521
+
1522
+		if ($name === "php") {
1523
+			// For php, we know the executable path. There's no need to look it up.
1524
+			return PHP_BINARY;
1525
+		}
1526
+
1527
+		if (array_key_exists($name, self::$executablePaths) === true) {
1528
+			return self::$executablePaths[$name];
1529
+		}
1530
+
1531
+		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1532
+			$cmd = 'where '.escapeshellarg($name).' 2> nul';
1533
+		} else {
1534
+			$cmd = 'which '.escapeshellarg($name).' 2> /dev/null';
1535
+		}
1536
+
1537
+		$result = exec($cmd, $output, $retVal);
1538
+		if ($retVal !== 0) {
1539
+			$result = null;
1540
+		}
1541
+
1542
+		self::$executablePaths[$name] = $result;
1543
+		return $result;
1544
+
1545
+	}//end getExecutablePath()
1546
+
1547
+
1548
+	/**
1549
+	 * Set a single config value.
1550
+	 *
1551
+	 * @param string      $key   The name of the config value.
1552
+	 * @param string|null $value The value to set. If null, the config
1553
+	 *                           entry is deleted, reverting it to the
1554
+	 *                           default value.
1555
+	 * @param boolean     $temp  Set this config data temporarily for this
1556
+	 *                           script run. This will not write the config
1557
+	 *                           data to the config file.
1558
+	 *
1559
+	 * @return bool
1560
+	 * @see    getConfigData()
1561
+	 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the config file can not be written.
1562
+	 */
1563
+	public static function setConfigData($key, $value, $temp=false)
1564
+	{
1565
+		if (isset(self::$overriddenDefaults['runtime-set']) === true
1566
+			&& isset(self::$overriddenDefaults['runtime-set'][$key]) === true
1567
+		) {
1568
+			return false;
1569
+		}
1570
+
1571
+		if ($temp === false) {
1572
+			$path = '';
1573
+			if (is_callable('\Phar::running') === true) {
1574
+				$path = \Phar::running(false);
1575
+			}
1576
+
1577
+			if ($path !== '') {
1578
+				$configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1579
+			} else {
1580
+				$configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1581
+				if (is_file($configFile) === false
1582
+					&& strpos('@data_dir@', '@data_dir') === false
1583
+				) {
1584
+					// If data_dir was replaced, this is a PEAR install and we can
1585
+					// use the PEAR data dir to store the conf file.
1586
+					$configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
1587
+				}
1588
+			}
1589
+
1590
+			if (is_file($configFile) === true
1591
+				&& is_writable($configFile) === false
1592
+			) {
1593
+				$error = 'ERROR: Config file '.$configFile.' is not writable'.PHP_EOL.PHP_EOL;
1594
+				throw new DeepExitException($error, 3);
1595
+			}
1596
+		}//end if
1597
+
1598
+		$phpCodeSnifferConfig = self::getAllConfigData();
1599
+
1600
+		if ($value === null) {
1601
+			if (isset($phpCodeSnifferConfig[$key]) === true) {
1602
+				unset($phpCodeSnifferConfig[$key]);
1603
+			}
1604
+		} else {
1605
+			$phpCodeSnifferConfig[$key] = $value;
1606
+		}
1607
+
1608
+		if ($temp === false) {
1609
+			$output  = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
1610
+			$output .= var_export($phpCodeSnifferConfig, true);
1611
+			$output .= "\n?".'>';
1612
+
1613
+			if (file_put_contents($configFile, $output) === false) {
1614
+				$error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL;
1615
+				throw new DeepExitException($error, 3);
1616
+			}
1617
+
1618
+			self::$configDataFile = $configFile;
1619
+		}
1620
+
1621
+		self::$configData = $phpCodeSnifferConfig;
1622
+
1623
+		// If the installed paths are being set, make sure all known
1624
+		// standards paths are added to the autoloader.
1625
+		if ($key === 'installed_paths') {
1626
+			$installedStandards = Util\Standards::getInstalledStandardDetails();
1627
+			foreach ($installedStandards as $name => $details) {
1628
+				Autoload::addSearchPath($details['path'], $details['namespace']);
1629
+			}
1630
+		}
1631
+
1632
+		return true;
1633
+
1634
+	}//end setConfigData()
1635
+
1636
+
1637
+	/**
1638
+	 * Get all config data.
1639
+	 *
1640
+	 * @return array<string, string>
1641
+	 * @see    getConfigData()
1642
+	 */
1643
+	public static function getAllConfigData()
1644
+	{
1645
+		if (self::$configData !== null) {
1646
+			return self::$configData;
1647
+		}
1648
+
1649
+		$path = '';
1650
+		if (is_callable('\Phar::running') === true) {
1651
+			$path = \Phar::running(false);
1652
+		}
1653
+
1654
+		if ($path !== '') {
1655
+			$configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1656
+		} else {
1657
+			$configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1658
+			if (is_file($configFile) === false
1659
+				&& strpos('@data_dir@', '@data_dir') === false
1660
+			) {
1661
+				$configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
1662
+			}
1663
+		}
1664
+
1665
+		if (is_file($configFile) === false) {
1666
+			self::$configData = [];
1667
+			return [];
1668
+		}
1669
+
1670
+		if (is_readable($configFile) === false) {
1671
+			$error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL;
1672
+			throw new DeepExitException($error, 3);
1673
+		}
1674
+
1675
+		include $configFile;
1676
+		self::$configDataFile = $configFile;
1677
+		self::$configData     = $phpCodeSnifferConfig;
1678
+		return self::$configData;
1679
+
1680
+	}//end getAllConfigData()
1681
+
1682
+
1683
+	/**
1684
+	 * Prints out the gathered config data.
1685
+	 *
1686
+	 * @param array $data The config data to print.
1687
+	 *
1688
+	 * @return void
1689
+	 */
1690
+	public function printConfigData($data)
1691
+	{
1692
+		$max  = 0;
1693
+		$keys = array_keys($data);
1694
+		foreach ($keys as $key) {
1695
+			$len = strlen($key);
1696
+			if (strlen($key) > $max) {
1697
+				$max = $len;
1698
+			}
1699
+		}
1700
+
1701
+		if ($max === 0) {
1702
+			return;
1703
+		}
1704
+
1705
+		$max += 2;
1706
+		ksort($data);
1707
+		foreach ($data as $name => $value) {
1708
+			echo str_pad($name.': ', $max).$value.PHP_EOL;
1709
+		}
1710
+
1711
+	}//end printConfigData()
1712 1712
 
1713 1713
 
1714 1714
 }//end class
Please login to merge, or discard this patch.
vendor/squizlabs/php_codesniffer/src/Sniffs/Sniff.php 1 patch
Indentation   +53 added lines, -53 removed lines patch added patch discarded remove patch
@@ -20,61 +20,61 @@
 block discarded – undo
20 20
 {
21 21
 
22 22
 
23
-    /**
24
-     * Registers the tokens that this sniff wants to listen for.
25
-     *
26
-     * An example return value for a sniff that wants to listen for whitespace
27
-     * and any comments would be:
28
-     *
29
-     * <code>
30
-     *    return array(
31
-     *            T_WHITESPACE,
32
-     *            T_DOC_COMMENT,
33
-     *            T_COMMENT,
34
-     *           );
35
-     * </code>
36
-     *
37
-     * @return mixed[]
38
-     * @see    Tokens.php
39
-     */
40
-    public function register();
23
+	/**
24
+	 * Registers the tokens that this sniff wants to listen for.
25
+	 *
26
+	 * An example return value for a sniff that wants to listen for whitespace
27
+	 * and any comments would be:
28
+	 *
29
+	 * <code>
30
+	 *    return array(
31
+	 *            T_WHITESPACE,
32
+	 *            T_DOC_COMMENT,
33
+	 *            T_COMMENT,
34
+	 *           );
35
+	 * </code>
36
+	 *
37
+	 * @return mixed[]
38
+	 * @see    Tokens.php
39
+	 */
40
+	public function register();
41 41
 
42 42
 
43
-    /**
44
-     * Called when one of the token types that this sniff is listening for
45
-     * is found.
46
-     *
47
-     * The stackPtr variable indicates where in the stack the token was found.
48
-     * A sniff can acquire information this token, along with all the other
49
-     * tokens within the stack by first acquiring the token stack:
50
-     *
51
-     * <code>
52
-     *    $tokens = $phpcsFile->getTokens();
53
-     *    echo 'Encountered a '.$tokens[$stackPtr]['type'].' token';
54
-     *    echo 'token information: ';
55
-     *    print_r($tokens[$stackPtr]);
56
-     * </code>
57
-     *
58
-     * If the sniff discovers an anomaly in the code, they can raise an error
59
-     * by calling addError() on the \PHP_CodeSniffer\Files\File object, specifying an error
60
-     * message and the position of the offending token:
61
-     *
62
-     * <code>
63
-     *    $phpcsFile->addError('Encountered an error', $stackPtr);
64
-     * </code>
65
-     *
66
-     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
67
-     *                                               token was found.
68
-     * @param int                         $stackPtr  The position in the PHP_CodeSniffer
69
-     *                                               file's token stack where the token
70
-     *                                               was found.
71
-     *
72
-     * @return void|int Optionally returns a stack pointer. The sniff will not be
73
-     *                  called again on the current file until the returned stack
74
-     *                  pointer is reached. Return (count($tokens) + 1) to skip
75
-     *                  the rest of the file.
76
-     */
77
-    public function process(File $phpcsFile, $stackPtr);
43
+	/**
44
+	 * Called when one of the token types that this sniff is listening for
45
+	 * is found.
46
+	 *
47
+	 * The stackPtr variable indicates where in the stack the token was found.
48
+	 * A sniff can acquire information this token, along with all the other
49
+	 * tokens within the stack by first acquiring the token stack:
50
+	 *
51
+	 * <code>
52
+	 *    $tokens = $phpcsFile->getTokens();
53
+	 *    echo 'Encountered a '.$tokens[$stackPtr]['type'].' token';
54
+	 *    echo 'token information: ';
55
+	 *    print_r($tokens[$stackPtr]);
56
+	 * </code>
57
+	 *
58
+	 * If the sniff discovers an anomaly in the code, they can raise an error
59
+	 * by calling addError() on the \PHP_CodeSniffer\Files\File object, specifying an error
60
+	 * message and the position of the offending token:
61
+	 *
62
+	 * <code>
63
+	 *    $phpcsFile->addError('Encountered an error', $stackPtr);
64
+	 * </code>
65
+	 *
66
+	 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
67
+	 *                                               token was found.
68
+	 * @param int                         $stackPtr  The position in the PHP_CodeSniffer
69
+	 *                                               file's token stack where the token
70
+	 *                                               was found.
71
+	 *
72
+	 * @return void|int Optionally returns a stack pointer. The sniff will not be
73
+	 *                  called again on the current file until the returned stack
74
+	 *                  pointer is reached. Return (count($tokens) + 1) to skip
75
+	 *                  the rest of the file.
76
+	 */
77
+	public function process(File $phpcsFile, $stackPtr);
78 78
 
79 79
 
80 80
 }//end interface
Please login to merge, or discard this patch.
vendor/squizlabs/php_codesniffer/src/Sniffs/AbstractScopeSniff.php 1 patch
Indentation   +154 added lines, -154 removed lines patch added patch discarded remove patch
@@ -32,160 +32,160 @@
 block discarded – undo
32 32
 abstract class AbstractScopeSniff implements Sniff
33 33
 {
34 34
 
35
-    /**
36
-     * The token types that this test wishes to listen to within the scope.
37
-     *
38
-     * @var array
39
-     */
40
-    private $tokens = [];
41
-
42
-    /**
43
-     * The type of scope opener tokens that this test wishes to listen to.
44
-     *
45
-     * @var string
46
-     */
47
-    private $scopeTokens = [];
48
-
49
-    /**
50
-     * True if this test should fire on tokens outside of the scope.
51
-     *
52
-     * @var boolean
53
-     */
54
-    private $listenOutside = false;
55
-
56
-
57
-    /**
58
-     * Constructs a new AbstractScopeTest.
59
-     *
60
-     * @param array   $scopeTokens   The type of scope the test wishes to listen to.
61
-     * @param array   $tokens        The tokens that the test wishes to listen to
62
-     *                               within the scope.
63
-     * @param boolean $listenOutside If true this test will also alert the
64
-     *                               extending class when a token is found outside
65
-     *                               the scope, by calling the
66
-     *                               processTokenOutsideScope method.
67
-     *
68
-     * @see    PHP_CodeSniffer.getValidScopeTokeners()
69
-     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified tokens array is empty.
70
-     */
71
-    public function __construct(
72
-        array $scopeTokens,
73
-        array $tokens,
74
-        $listenOutside=false
75
-    ) {
76
-        if (empty($scopeTokens) === true) {
77
-            $error = 'The scope tokens list cannot be empty';
78
-            throw new RuntimeException($error);
79
-        }
80
-
81
-        if (empty($tokens) === true) {
82
-            $error = 'The tokens list cannot be empty';
83
-            throw new RuntimeException($error);
84
-        }
85
-
86
-        $invalidScopeTokens = array_intersect($scopeTokens, $tokens);
87
-        if (empty($invalidScopeTokens) === false) {
88
-            $invalid = implode(', ', $invalidScopeTokens);
89
-            $error   = "Scope tokens [$invalid] can't be in the tokens array";
90
-            throw new RuntimeException($error);
91
-        }
92
-
93
-        $this->listenOutside = $listenOutside;
94
-        $this->scopeTokens   = array_flip($scopeTokens);
95
-        $this->tokens        = $tokens;
96
-
97
-    }//end __construct()
98
-
99
-
100
-    /**
101
-     * The method that is called to register the tokens this test wishes to
102
-     * listen to.
103
-     *
104
-     * DO NOT OVERRIDE THIS METHOD. Use the constructor of this class to register
105
-     * for the desired tokens and scope.
106
-     *
107
-     * @return int[]
108
-     * @see    __constructor()
109
-     */
110
-    final public function register()
111
-    {
112
-        return $this->tokens;
113
-
114
-    }//end register()
115
-
116
-
117
-    /**
118
-     * Processes the tokens that this test is listening for.
119
-     *
120
-     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
121
-     * @param int                         $stackPtr  The position in the stack where this
122
-     *                                               token was found.
123
-     *
124
-     * @return void|int Optionally returns a stack pointer. The sniff will not be
125
-     *                  called again on the current file until the returned stack
126
-     *                  pointer is reached. Return ($phpcsFile->numTokens + 1) to skip
127
-     *                  the rest of the file.
128
-     * @see    processTokenWithinScope()
129
-     */
130
-    final public function process(File $phpcsFile, $stackPtr)
131
-    {
132
-        $tokens = $phpcsFile->getTokens();
133
-
134
-        $foundScope = false;
135
-        $skipPtrs   = [];
136
-        foreach ($tokens[$stackPtr]['conditions'] as $scope => $code) {
137
-            if (isset($this->scopeTokens[$code]) === true) {
138
-                $skipPtrs[] = $this->processTokenWithinScope($phpcsFile, $stackPtr, $scope);
139
-                $foundScope = true;
140
-            }
141
-        }
142
-
143
-        if ($this->listenOutside === true && $foundScope === false) {
144
-            $skipPtrs[] = $this->processTokenOutsideScope($phpcsFile, $stackPtr);
145
-        }
146
-
147
-        if (empty($skipPtrs) === false) {
148
-            return min($skipPtrs);
149
-        }
150
-
151
-        return;
152
-
153
-    }//end process()
154
-
155
-
156
-    /**
157
-     * Processes a token that is found within the scope that this test is
158
-     * listening to.
159
-     *
160
-     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
161
-     * @param int                         $stackPtr  The position in the stack where this
162
-     *                                               token was found.
163
-     * @param int                         $currScope The position in the tokens array that
164
-     *                                               opened the scope that this test is
165
-     *                                               listening for.
166
-     *
167
-     * @return void|int Optionally returns a stack pointer. The sniff will not be
168
-     *                  called again on the current file until the returned stack
169
-     *                  pointer is reached. Return ($phpcsFile->numTokens + 1) to skip
170
-     *                  the rest of the file.
171
-     */
172
-    abstract protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope);
173
-
174
-
175
-    /**
176
-     * Processes a token that is found outside the scope that this test is
177
-     * listening to.
178
-     *
179
-     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
180
-     * @param int                         $stackPtr  The position in the stack where this
181
-     *                                               token was found.
182
-     *
183
-     * @return void|int Optionally returns a stack pointer. The sniff will not be
184
-     *                  called again on the current file until the returned stack
185
-     *                  pointer is reached. Return (count($tokens) + 1) to skip
186
-     *                  the rest of the file.
187
-     */
188
-    abstract protected function processTokenOutsideScope(File $phpcsFile, $stackPtr);
35
+	/**
36
+	 * The token types that this test wishes to listen to within the scope.
37
+	 *
38
+	 * @var array
39
+	 */
40
+	private $tokens = [];
41
+
42
+	/**
43
+	 * The type of scope opener tokens that this test wishes to listen to.
44
+	 *
45
+	 * @var string
46
+	 */
47
+	private $scopeTokens = [];
48
+
49
+	/**
50
+	 * True if this test should fire on tokens outside of the scope.
51
+	 *
52
+	 * @var boolean
53
+	 */
54
+	private $listenOutside = false;
55
+
56
+
57
+	/**
58
+	 * Constructs a new AbstractScopeTest.
59
+	 *
60
+	 * @param array   $scopeTokens   The type of scope the test wishes to listen to.
61
+	 * @param array   $tokens        The tokens that the test wishes to listen to
62
+	 *                               within the scope.
63
+	 * @param boolean $listenOutside If true this test will also alert the
64
+	 *                               extending class when a token is found outside
65
+	 *                               the scope, by calling the
66
+	 *                               processTokenOutsideScope method.
67
+	 *
68
+	 * @see    PHP_CodeSniffer.getValidScopeTokeners()
69
+	 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified tokens array is empty.
70
+	 */
71
+	public function __construct(
72
+		array $scopeTokens,
73
+		array $tokens,
74
+		$listenOutside=false
75
+	) {
76
+		if (empty($scopeTokens) === true) {
77
+			$error = 'The scope tokens list cannot be empty';
78
+			throw new RuntimeException($error);
79
+		}
80
+
81
+		if (empty($tokens) === true) {
82
+			$error = 'The tokens list cannot be empty';
83
+			throw new RuntimeException($error);
84
+		}
85
+
86
+		$invalidScopeTokens = array_intersect($scopeTokens, $tokens);
87
+		if (empty($invalidScopeTokens) === false) {
88
+			$invalid = implode(', ', $invalidScopeTokens);
89
+			$error   = "Scope tokens [$invalid] can't be in the tokens array";
90
+			throw new RuntimeException($error);
91
+		}
92
+
93
+		$this->listenOutside = $listenOutside;
94
+		$this->scopeTokens   = array_flip($scopeTokens);
95
+		$this->tokens        = $tokens;
96
+
97
+	}//end __construct()
98
+
99
+
100
+	/**
101
+	 * The method that is called to register the tokens this test wishes to
102
+	 * listen to.
103
+	 *
104
+	 * DO NOT OVERRIDE THIS METHOD. Use the constructor of this class to register
105
+	 * for the desired tokens and scope.
106
+	 *
107
+	 * @return int[]
108
+	 * @see    __constructor()
109
+	 */
110
+	final public function register()
111
+	{
112
+		return $this->tokens;
113
+
114
+	}//end register()
115
+
116
+
117
+	/**
118
+	 * Processes the tokens that this test is listening for.
119
+	 *
120
+	 * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
121
+	 * @param int                         $stackPtr  The position in the stack where this
122
+	 *                                               token was found.
123
+	 *
124
+	 * @return void|int Optionally returns a stack pointer. The sniff will not be
125
+	 *                  called again on the current file until the returned stack
126
+	 *                  pointer is reached. Return ($phpcsFile->numTokens + 1) to skip
127
+	 *                  the rest of the file.
128
+	 * @see    processTokenWithinScope()
129
+	 */
130
+	final public function process(File $phpcsFile, $stackPtr)
131
+	{
132
+		$tokens = $phpcsFile->getTokens();
133
+
134
+		$foundScope = false;
135
+		$skipPtrs   = [];
136
+		foreach ($tokens[$stackPtr]['conditions'] as $scope => $code) {
137
+			if (isset($this->scopeTokens[$code]) === true) {
138
+				$skipPtrs[] = $this->processTokenWithinScope($phpcsFile, $stackPtr, $scope);
139
+				$foundScope = true;
140
+			}
141
+		}
142
+
143
+		if ($this->listenOutside === true && $foundScope === false) {
144
+			$skipPtrs[] = $this->processTokenOutsideScope($phpcsFile, $stackPtr);
145
+		}
146
+
147
+		if (empty($skipPtrs) === false) {
148
+			return min($skipPtrs);
149
+		}
150
+
151
+		return;
152
+
153
+	}//end process()
154
+
155
+
156
+	/**
157
+	 * Processes a token that is found within the scope that this test is
158
+	 * listening to.
159
+	 *
160
+	 * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
161
+	 * @param int                         $stackPtr  The position in the stack where this
162
+	 *                                               token was found.
163
+	 * @param int                         $currScope The position in the tokens array that
164
+	 *                                               opened the scope that this test is
165
+	 *                                               listening for.
166
+	 *
167
+	 * @return void|int Optionally returns a stack pointer. The sniff will not be
168
+	 *                  called again on the current file until the returned stack
169
+	 *                  pointer is reached. Return ($phpcsFile->numTokens + 1) to skip
170
+	 *                  the rest of the file.
171
+	 */
172
+	abstract protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope);
173
+
174
+
175
+	/**
176
+	 * Processes a token that is found outside the scope that this test is
177
+	 * listening to.
178
+	 *
179
+	 * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
180
+	 * @param int                         $stackPtr  The position in the stack where this
181
+	 *                                               token was found.
182
+	 *
183
+	 * @return void|int Optionally returns a stack pointer. The sniff will not be
184
+	 *                  called again on the current file until the returned stack
185
+	 *                  pointer is reached. Return (count($tokens) + 1) to skip
186
+	 *                  the rest of the file.
187
+	 */
188
+	abstract protected function processTokenOutsideScope(File $phpcsFile, $stackPtr);
189 189
 
190 190
 
191 191
 }//end class
Please login to merge, or discard this patch.
vendor/squizlabs/php_codesniffer/src/Sniffs/AbstractPatternSniff.php 1 patch
Indentation   +914 added lines, -914 removed lines patch added patch discarded remove patch
@@ -17,920 +17,920 @@
 block discarded – undo
17 17
 abstract class AbstractPatternSniff implements Sniff
18 18
 {
19 19
 
20
-    /**
21
-     * If true, comments will be ignored if they are found in the code.
22
-     *
23
-     * @var boolean
24
-     */
25
-    public $ignoreComments = false;
26
-
27
-    /**
28
-     * The current file being checked.
29
-     *
30
-     * @var string
31
-     */
32
-    protected $currFile = '';
33
-
34
-    /**
35
-     * The parsed patterns array.
36
-     *
37
-     * @var array
38
-     */
39
-    private $parsedPatterns = [];
40
-
41
-    /**
42
-     * Tokens that this sniff wishes to process outside of the patterns.
43
-     *
44
-     * @var int[]
45
-     * @see registerSupplementary()
46
-     * @see processSupplementary()
47
-     */
48
-    private $supplementaryTokens = [];
49
-
50
-    /**
51
-     * Positions in the stack where errors have occurred.
52
-     *
53
-     * @var array<int, bool>
54
-     */
55
-    private $errorPos = [];
56
-
57
-
58
-    /**
59
-     * Constructs a AbstractPatternSniff.
60
-     *
61
-     * @param boolean $ignoreComments If true, comments will be ignored.
62
-     */
63
-    public function __construct($ignoreComments=null)
64
-    {
65
-        // This is here for backwards compatibility.
66
-        if ($ignoreComments !== null) {
67
-            $this->ignoreComments = $ignoreComments;
68
-        }
69
-
70
-        $this->supplementaryTokens = $this->registerSupplementary();
71
-
72
-    }//end __construct()
73
-
74
-
75
-    /**
76
-     * Registers the tokens to listen to.
77
-     *
78
-     * Classes extending <i>AbstractPatternTest</i> should implement the
79
-     * <i>getPatterns()</i> method to register the patterns they wish to test.
80
-     *
81
-     * @return int[]
82
-     * @see    process()
83
-     */
84
-    final public function register()
85
-    {
86
-        $listenTypes = [];
87
-        $patterns    = $this->getPatterns();
88
-
89
-        foreach ($patterns as $pattern) {
90
-            $parsedPattern = $this->parse($pattern);
91
-
92
-            // Find a token position in the pattern that we can use
93
-            // for a listener token.
94
-            $pos           = $this->getListenerTokenPos($parsedPattern);
95
-            $tokenType     = $parsedPattern[$pos]['token'];
96
-            $listenTypes[] = $tokenType;
97
-
98
-            $patternArray = [
99
-                'listen_pos'   => $pos,
100
-                'pattern'      => $parsedPattern,
101
-                'pattern_code' => $pattern,
102
-            ];
103
-
104
-            if (isset($this->parsedPatterns[$tokenType]) === false) {
105
-                $this->parsedPatterns[$tokenType] = [];
106
-            }
107
-
108
-            $this->parsedPatterns[$tokenType][] = $patternArray;
109
-        }//end foreach
110
-
111
-        return array_unique(array_merge($listenTypes, $this->supplementaryTokens));
112
-
113
-    }//end register()
114
-
115
-
116
-    /**
117
-     * Returns the token types that the specified pattern is checking for.
118
-     *
119
-     * Returned array is in the format:
120
-     * <code>
121
-     *   array(
122
-     *      T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token
123
-     *                         // should occur in the pattern.
124
-     *   );
125
-     * </code>
126
-     *
127
-     * @param array $pattern The parsed pattern to find the acquire the token
128
-     *                       types from.
129
-     *
130
-     * @return array<int, int>
131
-     */
132
-    private function getPatternTokenTypes($pattern)
133
-    {
134
-        $tokenTypes = [];
135
-        foreach ($pattern as $pos => $patternInfo) {
136
-            if ($patternInfo['type'] === 'token') {
137
-                if (isset($tokenTypes[$patternInfo['token']]) === false) {
138
-                    $tokenTypes[$patternInfo['token']] = $pos;
139
-                }
140
-            }
141
-        }
142
-
143
-        return $tokenTypes;
144
-
145
-    }//end getPatternTokenTypes()
146
-
147
-
148
-    /**
149
-     * Returns the position in the pattern that this test should register as
150
-     * a listener for the pattern.
151
-     *
152
-     * @param array $pattern The pattern to acquire the listener for.
153
-     *
154
-     * @return int The position in the pattern that this test should register
155
-     *             as the listener.
156
-     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If we could not determine a token to listen for.
157
-     */
158
-    private function getListenerTokenPos($pattern)
159
-    {
160
-        $tokenTypes = $this->getPatternTokenTypes($pattern);
161
-        $tokenCodes = array_keys($tokenTypes);
162
-        $token      = Tokens::getHighestWeightedToken($tokenCodes);
163
-
164
-        // If we could not get a token.
165
-        if ($token === false) {
166
-            $error = 'Could not determine a token to listen for';
167
-            throw new RuntimeException($error);
168
-        }
169
-
170
-        return $tokenTypes[$token];
171
-
172
-    }//end getListenerTokenPos()
173
-
174
-
175
-    /**
176
-     * Processes the test.
177
-     *
178
-     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
179
-     *                                               token occurred.
180
-     * @param int                         $stackPtr  The position in the tokens stack
181
-     *                                               where the listening token type
182
-     *                                               was found.
183
-     *
184
-     * @return void
185
-     * @see    register()
186
-     */
187
-    final public function process(File $phpcsFile, $stackPtr)
188
-    {
189
-        $file = $phpcsFile->getFilename();
190
-        if ($this->currFile !== $file) {
191
-            // We have changed files, so clean up.
192
-            $this->errorPos = [];
193
-            $this->currFile = $file;
194
-        }
195
-
196
-        $tokens = $phpcsFile->getTokens();
197
-
198
-        if (in_array($tokens[$stackPtr]['code'], $this->supplementaryTokens, true) === true) {
199
-            $this->processSupplementary($phpcsFile, $stackPtr);
200
-        }
201
-
202
-        $type = $tokens[$stackPtr]['code'];
203
-
204
-        // If the type is not set, then it must have been a token registered
205
-        // with registerSupplementary().
206
-        if (isset($this->parsedPatterns[$type]) === false) {
207
-            return;
208
-        }
209
-
210
-        $allErrors = [];
211
-
212
-        // Loop over each pattern that is listening to the current token type
213
-        // that we are processing.
214
-        foreach ($this->parsedPatterns[$type] as $patternInfo) {
215
-            // If processPattern returns false, then the pattern that we are
216
-            // checking the code with must not be designed to check that code.
217
-            $errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr);
218
-            if ($errors === false) {
219
-                // The pattern didn't match.
220
-                continue;
221
-            } else if (empty($errors) === true) {
222
-                // The pattern matched, but there were no errors.
223
-                break;
224
-            }
225
-
226
-            foreach ($errors as $stackPtr => $error) {
227
-                if (isset($this->errorPos[$stackPtr]) === false) {
228
-                    $this->errorPos[$stackPtr] = true;
229
-                    $allErrors[$stackPtr]      = $error;
230
-                }
231
-            }
232
-        }
233
-
234
-        foreach ($allErrors as $stackPtr => $error) {
235
-            $phpcsFile->addError($error, $stackPtr, 'Found');
236
-        }
237
-
238
-    }//end process()
239
-
240
-
241
-    /**
242
-     * Processes the pattern and verifies the code at $stackPtr.
243
-     *
244
-     * @param array                       $patternInfo Information about the pattern used
245
-     *                                                 for checking, which includes are
246
-     *                                                 parsed token representation of the
247
-     *                                                 pattern.
248
-     * @param \PHP_CodeSniffer\Files\File $phpcsFile   The PHP_CodeSniffer file where the
249
-     *                                                 token occurred.
250
-     * @param int                         $stackPtr    The position in the tokens stack where
251
-     *                                                 the listening token type was found.
252
-     *
253
-     * @return array
254
-     */
255
-    protected function processPattern($patternInfo, File $phpcsFile, $stackPtr)
256
-    {
257
-        $tokens      = $phpcsFile->getTokens();
258
-        $pattern     = $patternInfo['pattern'];
259
-        $patternCode = $patternInfo['pattern_code'];
260
-        $errors      = [];
261
-        $found       = '';
262
-
263
-        $ignoreTokens = [T_WHITESPACE => T_WHITESPACE];
264
-        if ($this->ignoreComments === true) {
265
-            $ignoreTokens += Tokens::$commentTokens;
266
-        }
267
-
268
-        $origStackPtr = $stackPtr;
269
-        $hasError     = false;
270
-
271
-        if ($patternInfo['listen_pos'] > 0) {
272
-            $stackPtr--;
273
-
274
-            for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) {
275
-                if ($pattern[$i]['type'] === 'token') {
276
-                    if ($pattern[$i]['token'] === T_WHITESPACE) {
277
-                        if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
278
-                            $found = $tokens[$stackPtr]['content'].$found;
279
-                        }
280
-
281
-                        // Only check the size of the whitespace if this is not
282
-                        // the first token. We don't care about the size of
283
-                        // leading whitespace, just that there is some.
284
-                        if ($i !== 0) {
285
-                            if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) {
286
-                                $hasError = true;
287
-                            }
288
-                        }
289
-                    } else {
290
-                        // Check to see if this important token is the same as the
291
-                        // previous important token in the pattern. If it is not,
292
-                        // then the pattern cannot be for this piece of code.
293
-                        $prev = $phpcsFile->findPrevious(
294
-                            $ignoreTokens,
295
-                            $stackPtr,
296
-                            null,
297
-                            true
298
-                        );
299
-
300
-                        if ($prev === false
301
-                            || $tokens[$prev]['code'] !== $pattern[$i]['token']
302
-                        ) {
303
-                            return false;
304
-                        }
305
-
306
-                        // If we skipped past some whitespace tokens, then add them
307
-                        // to the found string.
308
-                        $tokenContent = $phpcsFile->getTokensAsString(
309
-                            ($prev + 1),
310
-                            ($stackPtr - $prev - 1)
311
-                        );
312
-
313
-                        $found = $tokens[$prev]['content'].$tokenContent.$found;
314
-
315
-                        if (isset($pattern[($i - 1)]) === true
316
-                            && $pattern[($i - 1)]['type'] === 'skip'
317
-                        ) {
318
-                            $stackPtr = $prev;
319
-                        } else {
320
-                            $stackPtr = ($prev - 1);
321
-                        }
322
-                    }//end if
323
-                } else if ($pattern[$i]['type'] === 'skip') {
324
-                    // Skip to next piece of relevant code.
325
-                    if ($pattern[$i]['to'] === 'parenthesis_closer') {
326
-                        $to = 'parenthesis_opener';
327
-                    } else {
328
-                        $to = 'scope_opener';
329
-                    }
330
-
331
-                    // Find the previous opener.
332
-                    $next = $phpcsFile->findPrevious(
333
-                        $ignoreTokens,
334
-                        $stackPtr,
335
-                        null,
336
-                        true
337
-                    );
338
-
339
-                    if ($next === false || isset($tokens[$next][$to]) === false) {
340
-                        // If there was not opener, then we must be
341
-                        // using the wrong pattern.
342
-                        return false;
343
-                    }
344
-
345
-                    if ($to === 'parenthesis_opener') {
346
-                        $found = '{'.$found;
347
-                    } else {
348
-                        $found = '('.$found;
349
-                    }
350
-
351
-                    $found = '...'.$found;
352
-
353
-                    // Skip to the opening token.
354
-                    $stackPtr = ($tokens[$next][$to] - 1);
355
-                } else if ($pattern[$i]['type'] === 'string') {
356
-                    $found = 'abc';
357
-                } else if ($pattern[$i]['type'] === 'newline') {
358
-                    if ($this->ignoreComments === true
359
-                        && isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true
360
-                    ) {
361
-                        $startComment = $phpcsFile->findPrevious(
362
-                            Tokens::$commentTokens,
363
-                            ($stackPtr - 1),
364
-                            null,
365
-                            true
366
-                        );
367
-
368
-                        if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) {
369
-                            $startComment++;
370
-                        }
371
-
372
-                        $tokenContent = $phpcsFile->getTokensAsString(
373
-                            $startComment,
374
-                            ($stackPtr - $startComment + 1)
375
-                        );
376
-
377
-                        $found    = $tokenContent.$found;
378
-                        $stackPtr = ($startComment - 1);
379
-                    }
380
-
381
-                    if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
382
-                        if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) {
383
-                            $found = $tokens[$stackPtr]['content'].$found;
384
-
385
-                            // This may just be an indent that comes after a newline
386
-                            // so check the token before to make sure. If it is a newline, we
387
-                            // can ignore the error here.
388
-                            if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar)
389
-                                && ($this->ignoreComments === true
390
-                                && isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false)
391
-                            ) {
392
-                                $hasError = true;
393
-                            } else {
394
-                                $stackPtr--;
395
-                            }
396
-                        } else {
397
-                            $found = 'EOL'.$found;
398
-                        }
399
-                    } else {
400
-                        $found    = $tokens[$stackPtr]['content'].$found;
401
-                        $hasError = true;
402
-                    }//end if
403
-
404
-                    if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') {
405
-                        // Make sure they only have 1 newline.
406
-                        $prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
407
-                        if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) {
408
-                            $hasError = true;
409
-                        }
410
-                    }
411
-                }//end if
412
-            }//end for
413
-        }//end if
414
-
415
-        $stackPtr          = $origStackPtr;
416
-        $lastAddedStackPtr = null;
417
-        $patternLen        = count($pattern);
418
-
419
-        for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) {
420
-            if (isset($tokens[$stackPtr]) === false) {
421
-                break;
422
-            }
423
-
424
-            if ($pattern[$i]['type'] === 'token') {
425
-                if ($pattern[$i]['token'] === T_WHITESPACE) {
426
-                    if ($this->ignoreComments === true) {
427
-                        // If we are ignoring comments, check to see if this current
428
-                        // token is a comment. If so skip it.
429
-                        if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) {
430
-                            continue;
431
-                        }
432
-
433
-                        // If the next token is a comment, the we need to skip the
434
-                        // current token as we should allow a space before a
435
-                        // comment for readability.
436
-                        if (isset($tokens[($stackPtr + 1)]) === true
437
-                            && isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true
438
-                        ) {
439
-                            continue;
440
-                        }
441
-                    }
442
-
443
-                    $tokenContent = '';
444
-                    if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
445
-                        if (isset($pattern[($i + 1)]) === false) {
446
-                            // This is the last token in the pattern, so just compare
447
-                            // the next token of content.
448
-                            $tokenContent = $tokens[$stackPtr]['content'];
449
-                        } else {
450
-                            // Get all the whitespace to the next token.
451
-                            $next = $phpcsFile->findNext(
452
-                                Tokens::$emptyTokens,
453
-                                $stackPtr,
454
-                                null,
455
-                                true
456
-                            );
457
-
458
-                            $tokenContent = $phpcsFile->getTokensAsString(
459
-                                $stackPtr,
460
-                                ($next - $stackPtr)
461
-                            );
462
-
463
-                            $lastAddedStackPtr = $stackPtr;
464
-                            $stackPtr          = $next;
465
-                        }//end if
466
-
467
-                        if ($stackPtr !== $lastAddedStackPtr) {
468
-                            $found .= $tokenContent;
469
-                        }
470
-                    } else {
471
-                        if ($stackPtr !== $lastAddedStackPtr) {
472
-                            $found            .= $tokens[$stackPtr]['content'];
473
-                            $lastAddedStackPtr = $stackPtr;
474
-                        }
475
-                    }//end if
476
-
477
-                    if (isset($pattern[($i + 1)]) === true
478
-                        && $pattern[($i + 1)]['type'] === 'skip'
479
-                    ) {
480
-                        // The next token is a skip token, so we just need to make
481
-                        // sure the whitespace we found has *at least* the
482
-                        // whitespace required.
483
-                        if (strpos($tokenContent, $pattern[$i]['value']) !== 0) {
484
-                            $hasError = true;
485
-                        }
486
-                    } else {
487
-                        if ($tokenContent !== $pattern[$i]['value']) {
488
-                            $hasError = true;
489
-                        }
490
-                    }
491
-                } else {
492
-                    // Check to see if this important token is the same as the
493
-                    // next important token in the pattern. If it is not, then
494
-                    // the pattern cannot be for this piece of code.
495
-                    $next = $phpcsFile->findNext(
496
-                        $ignoreTokens,
497
-                        $stackPtr,
498
-                        null,
499
-                        true
500
-                    );
501
-
502
-                    if ($next === false
503
-                        || $tokens[$next]['code'] !== $pattern[$i]['token']
504
-                    ) {
505
-                        // The next important token did not match the pattern.
506
-                        return false;
507
-                    }
508
-
509
-                    if ($lastAddedStackPtr !== null) {
510
-                        if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET
511
-                            || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET)
512
-                            && isset($tokens[$next]['scope_condition']) === true
513
-                            && $tokens[$next]['scope_condition'] > $lastAddedStackPtr
514
-                        ) {
515
-                            // This is a brace, but the owner of it is after the current
516
-                            // token, which means it does not belong to any token in
517
-                            // our pattern. This means the pattern is not for us.
518
-                            return false;
519
-                        }
520
-
521
-                        if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS
522
-                            || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS)
523
-                            && isset($tokens[$next]['parenthesis_owner']) === true
524
-                            && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr
525
-                        ) {
526
-                            // This is a bracket, but the owner of it is after the current
527
-                            // token, which means it does not belong to any token in
528
-                            // our pattern. This means the pattern is not for us.
529
-                            return false;
530
-                        }
531
-                    }//end if
532
-
533
-                    // If we skipped past some whitespace tokens, then add them
534
-                    // to the found string.
535
-                    if (($next - $stackPtr) > 0) {
536
-                        $hasComment = false;
537
-                        for ($j = $stackPtr; $j < $next; $j++) {
538
-                            $found .= $tokens[$j]['content'];
539
-                            if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) {
540
-                                $hasComment = true;
541
-                            }
542
-                        }
543
-
544
-                        // If we are not ignoring comments, this additional
545
-                        // whitespace or comment is not allowed. If we are
546
-                        // ignoring comments, there needs to be at least one
547
-                        // comment for this to be allowed.
548
-                        if ($this->ignoreComments === false
549
-                            || ($this->ignoreComments === true
550
-                            && $hasComment === false)
551
-                        ) {
552
-                            $hasError = true;
553
-                        }
554
-
555
-                        // Even when ignoring comments, we are not allowed to include
556
-                        // newlines without the pattern specifying them, so
557
-                        // everything should be on the same line.
558
-                        if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) {
559
-                            $hasError = true;
560
-                        }
561
-                    }//end if
562
-
563
-                    if ($next !== $lastAddedStackPtr) {
564
-                        $found            .= $tokens[$next]['content'];
565
-                        $lastAddedStackPtr = $next;
566
-                    }
567
-
568
-                    if (isset($pattern[($i + 1)]) === true
569
-                        && $pattern[($i + 1)]['type'] === 'skip'
570
-                    ) {
571
-                        $stackPtr = $next;
572
-                    } else {
573
-                        $stackPtr = ($next + 1);
574
-                    }
575
-                }//end if
576
-            } else if ($pattern[$i]['type'] === 'skip') {
577
-                if ($pattern[$i]['to'] === 'unknown') {
578
-                    $next = $phpcsFile->findNext(
579
-                        $pattern[($i + 1)]['token'],
580
-                        $stackPtr
581
-                    );
582
-
583
-                    if ($next === false) {
584
-                        // Couldn't find the next token, so we must
585
-                        // be using the wrong pattern.
586
-                        return false;
587
-                    }
588
-
589
-                    $found   .= '...';
590
-                    $stackPtr = $next;
591
-                } else {
592
-                    // Find the previous opener.
593
-                    $next = $phpcsFile->findPrevious(
594
-                        Tokens::$blockOpeners,
595
-                        $stackPtr
596
-                    );
597
-
598
-                    if ($next === false
599
-                        || isset($tokens[$next][$pattern[$i]['to']]) === false
600
-                    ) {
601
-                        // If there was not opener, then we must
602
-                        // be using the wrong pattern.
603
-                        return false;
604
-                    }
605
-
606
-                    $found .= '...';
607
-                    if ($pattern[$i]['to'] === 'parenthesis_closer') {
608
-                        $found .= ')';
609
-                    } else {
610
-                        $found .= '}';
611
-                    }
612
-
613
-                    // Skip to the closing token.
614
-                    $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1);
615
-                }//end if
616
-            } else if ($pattern[$i]['type'] === 'string') {
617
-                if ($tokens[$stackPtr]['code'] !== T_STRING) {
618
-                    $hasError = true;
619
-                }
620
-
621
-                if ($stackPtr !== $lastAddedStackPtr) {
622
-                    $found            .= 'abc';
623
-                    $lastAddedStackPtr = $stackPtr;
624
-                }
625
-
626
-                $stackPtr++;
627
-            } else if ($pattern[$i]['type'] === 'newline') {
628
-                // Find the next token that contains a newline character.
629
-                $newline = 0;
630
-                for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) {
631
-                    if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) {
632
-                        $newline = $j;
633
-                        break;
634
-                    }
635
-                }
636
-
637
-                if ($newline === 0) {
638
-                    // We didn't find a newline character in the rest of the file.
639
-                    $next     = ($phpcsFile->numTokens - 1);
640
-                    $hasError = true;
641
-                } else {
642
-                    if ($this->ignoreComments === false) {
643
-                        // The newline character cannot be part of a comment.
644
-                        if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) {
645
-                            $hasError = true;
646
-                        }
647
-                    }
648
-
649
-                    if ($newline === $stackPtr) {
650
-                        $next = ($stackPtr + 1);
651
-                    } else {
652
-                        // Check that there were no significant tokens that we
653
-                        // skipped over to find our newline character.
654
-                        $next = $phpcsFile->findNext(
655
-                            $ignoreTokens,
656
-                            $stackPtr,
657
-                            null,
658
-                            true
659
-                        );
660
-
661
-                        if ($next < $newline) {
662
-                            // We skipped a non-ignored token.
663
-                            $hasError = true;
664
-                        } else {
665
-                            $next = ($newline + 1);
666
-                        }
667
-                    }
668
-                }//end if
669
-
670
-                if ($stackPtr !== $lastAddedStackPtr) {
671
-                    $found .= $phpcsFile->getTokensAsString(
672
-                        $stackPtr,
673
-                        ($next - $stackPtr)
674
-                    );
675
-
676
-                    $lastAddedStackPtr = ($next - 1);
677
-                }
678
-
679
-                $stackPtr = $next;
680
-            }//end if
681
-        }//end for
682
-
683
-        if ($hasError === true) {
684
-            $error = $this->prepareError($found, $patternCode);
685
-            $errors[$origStackPtr] = $error;
686
-        }
687
-
688
-        return $errors;
689
-
690
-    }//end processPattern()
691
-
692
-
693
-    /**
694
-     * Prepares an error for the specified patternCode.
695
-     *
696
-     * @param string $found       The actual found string in the code.
697
-     * @param string $patternCode The expected pattern code.
698
-     *
699
-     * @return string The error message.
700
-     */
701
-    protected function prepareError($found, $patternCode)
702
-    {
703
-        $found    = str_replace("\r\n", '\n', $found);
704
-        $found    = str_replace("\n", '\n', $found);
705
-        $found    = str_replace("\r", '\n', $found);
706
-        $found    = str_replace("\t", '\t', $found);
707
-        $found    = str_replace('EOL', '\n', $found);
708
-        $expected = str_replace('EOL', '\n', $patternCode);
709
-
710
-        $error = "Expected \"$expected\"; found \"$found\"";
711
-
712
-        return $error;
713
-
714
-    }//end prepareError()
715
-
716
-
717
-    /**
718
-     * Returns the patterns that should be checked.
719
-     *
720
-     * @return string[]
721
-     */
722
-    abstract protected function getPatterns();
723
-
724
-
725
-    /**
726
-     * Registers any supplementary tokens that this test might wish to process.
727
-     *
728
-     * A sniff may wish to register supplementary tests when it wishes to group
729
-     * an arbitrary validation that cannot be performed using a pattern, with
730
-     * other pattern tests.
731
-     *
732
-     * @return int[]
733
-     * @see    processSupplementary()
734
-     */
735
-    protected function registerSupplementary()
736
-    {
737
-        return [];
738
-
739
-    }//end registerSupplementary()
740
-
741
-
742
-     /**
743
-      * Processes any tokens registered with registerSupplementary().
744
-      *
745
-      * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where to
746
-      *                                               process the skip.
747
-      * @param int                         $stackPtr  The position in the tokens stack to
748
-      *                                               process.
749
-      *
750
-      * @return void
751
-      * @see    registerSupplementary()
752
-      */
753
-    protected function processSupplementary(File $phpcsFile, $stackPtr)
754
-    {
755
-
756
-    }//end processSupplementary()
757
-
758
-
759
-    /**
760
-     * Parses a pattern string into an array of pattern steps.
761
-     *
762
-     * @param string $pattern The pattern to parse.
763
-     *
764
-     * @return array The parsed pattern array.
765
-     * @see    createSkipPattern()
766
-     * @see    createTokenPattern()
767
-     */
768
-    private function parse($pattern)
769
-    {
770
-        $patterns   = [];
771
-        $length     = strlen($pattern);
772
-        $lastToken  = 0;
773
-        $firstToken = 0;
774
-
775
-        for ($i = 0; $i < $length; $i++) {
776
-            $specialPattern = false;
777
-            $isLastChar     = ($i === ($length - 1));
778
-            $oldFirstToken  = $firstToken;
779
-
780
-            if (substr($pattern, $i, 3) === '...') {
781
-                // It's a skip pattern. The skip pattern requires the
782
-                // content of the token in the "from" position and the token
783
-                // to skip to.
784
-                $specialPattern = $this->createSkipPattern($pattern, ($i - 1));
785
-                $lastToken      = ($i - $firstToken);
786
-                $firstToken     = ($i + 3);
787
-                $i += 2;
788
-
789
-                if ($specialPattern['to'] !== 'unknown') {
790
-                    $firstToken++;
791
-                }
792
-            } else if (substr($pattern, $i, 3) === 'abc') {
793
-                $specialPattern = ['type' => 'string'];
794
-                $lastToken      = ($i - $firstToken);
795
-                $firstToken     = ($i + 3);
796
-                $i += 2;
797
-            } else if (substr($pattern, $i, 3) === 'EOL') {
798
-                $specialPattern = ['type' => 'newline'];
799
-                $lastToken      = ($i - $firstToken);
800
-                $firstToken     = ($i + 3);
801
-                $i += 2;
802
-            }//end if
803
-
804
-            if ($specialPattern !== false || $isLastChar === true) {
805
-                // If we are at the end of the string, don't worry about a limit.
806
-                if ($isLastChar === true) {
807
-                    // Get the string from the end of the last skip pattern, if any,
808
-                    // to the end of the pattern string.
809
-                    $str = substr($pattern, $oldFirstToken);
810
-                } else {
811
-                    // Get the string from the end of the last special pattern,
812
-                    // if any, to the start of this special pattern.
813
-                    if ($lastToken === 0) {
814
-                        // Note that if the last special token was zero characters ago,
815
-                        // there will be nothing to process so we can skip this bit.
816
-                        // This happens if you have something like: EOL... in your pattern.
817
-                        $str = '';
818
-                    } else {
819
-                        $str = substr($pattern, $oldFirstToken, $lastToken);
820
-                    }
821
-                }
822
-
823
-                if ($str !== '') {
824
-                    $tokenPatterns = $this->createTokenPattern($str);
825
-                    foreach ($tokenPatterns as $tokenPattern) {
826
-                        $patterns[] = $tokenPattern;
827
-                    }
828
-                }
829
-
830
-                // Make sure we don't skip the last token.
831
-                if ($isLastChar === false && $i === ($length - 1)) {
832
-                    $i--;
833
-                }
834
-            }//end if
835
-
836
-            // Add the skip pattern *after* we have processed
837
-            // all the tokens from the end of the last skip pattern
838
-            // to the start of this skip pattern.
839
-            if ($specialPattern !== false) {
840
-                $patterns[] = $specialPattern;
841
-            }
842
-        }//end for
843
-
844
-        return $patterns;
845
-
846
-    }//end parse()
847
-
848
-
849
-    /**
850
-     * Creates a skip pattern.
851
-     *
852
-     * @param string $pattern The pattern being parsed.
853
-     * @param string $from    The token content that the skip pattern starts from.
854
-     *
855
-     * @return array The pattern step.
856
-     * @see    createTokenPattern()
857
-     * @see    parse()
858
-     */
859
-    private function createSkipPattern($pattern, $from)
860
-    {
861
-        $skip = ['type' => 'skip'];
862
-
863
-        $nestedParenthesis = 0;
864
-        $nestedBraces      = 0;
865
-        for ($start = $from; $start >= 0; $start--) {
866
-            switch ($pattern[$start]) {
867
-            case '(':
868
-                if ($nestedParenthesis === 0) {
869
-                    $skip['to'] = 'parenthesis_closer';
870
-                }
871
-
872
-                $nestedParenthesis--;
873
-                break;
874
-            case '{':
875
-                if ($nestedBraces === 0) {
876
-                    $skip['to'] = 'scope_closer';
877
-                }
878
-
879
-                $nestedBraces--;
880
-                break;
881
-            case '}':
882
-                $nestedBraces++;
883
-                break;
884
-            case ')':
885
-                $nestedParenthesis++;
886
-                break;
887
-            }//end switch
888
-
889
-            if (isset($skip['to']) === true) {
890
-                break;
891
-            }
892
-        }//end for
893
-
894
-        if (isset($skip['to']) === false) {
895
-            $skip['to'] = 'unknown';
896
-        }
897
-
898
-        return $skip;
899
-
900
-    }//end createSkipPattern()
901
-
902
-
903
-    /**
904
-     * Creates a token pattern.
905
-     *
906
-     * @param string $str The tokens string that the pattern should match.
907
-     *
908
-     * @return array The pattern step.
909
-     * @see    createSkipPattern()
910
-     * @see    parse()
911
-     */
912
-    private function createTokenPattern($str)
913
-    {
914
-        // Don't add a space after the closing php tag as it will add a new
915
-        // whitespace token.
916
-        $tokenizer = new PHP('<?php '.$str.'?>', null);
917
-
918
-        // Remove the <?php tag from the front and the end php tag from the back.
919
-        $tokens = $tokenizer->getTokens();
920
-        $tokens = array_slice($tokens, 1, (count($tokens) - 2));
921
-
922
-        $patterns = [];
923
-        foreach ($tokens as $patternInfo) {
924
-            $patterns[] = [
925
-                'type'  => 'token',
926
-                'token' => $patternInfo['code'],
927
-                'value' => $patternInfo['content'],
928
-            ];
929
-        }
930
-
931
-        return $patterns;
932
-
933
-    }//end createTokenPattern()
20
+	/**
21
+	 * If true, comments will be ignored if they are found in the code.
22
+	 *
23
+	 * @var boolean
24
+	 */
25
+	public $ignoreComments = false;
26
+
27
+	/**
28
+	 * The current file being checked.
29
+	 *
30
+	 * @var string
31
+	 */
32
+	protected $currFile = '';
33
+
34
+	/**
35
+	 * The parsed patterns array.
36
+	 *
37
+	 * @var array
38
+	 */
39
+	private $parsedPatterns = [];
40
+
41
+	/**
42
+	 * Tokens that this sniff wishes to process outside of the patterns.
43
+	 *
44
+	 * @var int[]
45
+	 * @see registerSupplementary()
46
+	 * @see processSupplementary()
47
+	 */
48
+	private $supplementaryTokens = [];
49
+
50
+	/**
51
+	 * Positions in the stack where errors have occurred.
52
+	 *
53
+	 * @var array<int, bool>
54
+	 */
55
+	private $errorPos = [];
56
+
57
+
58
+	/**
59
+	 * Constructs a AbstractPatternSniff.
60
+	 *
61
+	 * @param boolean $ignoreComments If true, comments will be ignored.
62
+	 */
63
+	public function __construct($ignoreComments=null)
64
+	{
65
+		// This is here for backwards compatibility.
66
+		if ($ignoreComments !== null) {
67
+			$this->ignoreComments = $ignoreComments;
68
+		}
69
+
70
+		$this->supplementaryTokens = $this->registerSupplementary();
71
+
72
+	}//end __construct()
73
+
74
+
75
+	/**
76
+	 * Registers the tokens to listen to.
77
+	 *
78
+	 * Classes extending <i>AbstractPatternTest</i> should implement the
79
+	 * <i>getPatterns()</i> method to register the patterns they wish to test.
80
+	 *
81
+	 * @return int[]
82
+	 * @see    process()
83
+	 */
84
+	final public function register()
85
+	{
86
+		$listenTypes = [];
87
+		$patterns    = $this->getPatterns();
88
+
89
+		foreach ($patterns as $pattern) {
90
+			$parsedPattern = $this->parse($pattern);
91
+
92
+			// Find a token position in the pattern that we can use
93
+			// for a listener token.
94
+			$pos           = $this->getListenerTokenPos($parsedPattern);
95
+			$tokenType     = $parsedPattern[$pos]['token'];
96
+			$listenTypes[] = $tokenType;
97
+
98
+			$patternArray = [
99
+				'listen_pos'   => $pos,
100
+				'pattern'      => $parsedPattern,
101
+				'pattern_code' => $pattern,
102
+			];
103
+
104
+			if (isset($this->parsedPatterns[$tokenType]) === false) {
105
+				$this->parsedPatterns[$tokenType] = [];
106
+			}
107
+
108
+			$this->parsedPatterns[$tokenType][] = $patternArray;
109
+		}//end foreach
110
+
111
+		return array_unique(array_merge($listenTypes, $this->supplementaryTokens));
112
+
113
+	}//end register()
114
+
115
+
116
+	/**
117
+	 * Returns the token types that the specified pattern is checking for.
118
+	 *
119
+	 * Returned array is in the format:
120
+	 * <code>
121
+	 *   array(
122
+	 *      T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token
123
+	 *                         // should occur in the pattern.
124
+	 *   );
125
+	 * </code>
126
+	 *
127
+	 * @param array $pattern The parsed pattern to find the acquire the token
128
+	 *                       types from.
129
+	 *
130
+	 * @return array<int, int>
131
+	 */
132
+	private function getPatternTokenTypes($pattern)
133
+	{
134
+		$tokenTypes = [];
135
+		foreach ($pattern as $pos => $patternInfo) {
136
+			if ($patternInfo['type'] === 'token') {
137
+				if (isset($tokenTypes[$patternInfo['token']]) === false) {
138
+					$tokenTypes[$patternInfo['token']] = $pos;
139
+				}
140
+			}
141
+		}
142
+
143
+		return $tokenTypes;
144
+
145
+	}//end getPatternTokenTypes()
146
+
147
+
148
+	/**
149
+	 * Returns the position in the pattern that this test should register as
150
+	 * a listener for the pattern.
151
+	 *
152
+	 * @param array $pattern The pattern to acquire the listener for.
153
+	 *
154
+	 * @return int The position in the pattern that this test should register
155
+	 *             as the listener.
156
+	 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If we could not determine a token to listen for.
157
+	 */
158
+	private function getListenerTokenPos($pattern)
159
+	{
160
+		$tokenTypes = $this->getPatternTokenTypes($pattern);
161
+		$tokenCodes = array_keys($tokenTypes);
162
+		$token      = Tokens::getHighestWeightedToken($tokenCodes);
163
+
164
+		// If we could not get a token.
165
+		if ($token === false) {
166
+			$error = 'Could not determine a token to listen for';
167
+			throw new RuntimeException($error);
168
+		}
169
+
170
+		return $tokenTypes[$token];
171
+
172
+	}//end getListenerTokenPos()
173
+
174
+
175
+	/**
176
+	 * Processes the test.
177
+	 *
178
+	 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
179
+	 *                                               token occurred.
180
+	 * @param int                         $stackPtr  The position in the tokens stack
181
+	 *                                               where the listening token type
182
+	 *                                               was found.
183
+	 *
184
+	 * @return void
185
+	 * @see    register()
186
+	 */
187
+	final public function process(File $phpcsFile, $stackPtr)
188
+	{
189
+		$file = $phpcsFile->getFilename();
190
+		if ($this->currFile !== $file) {
191
+			// We have changed files, so clean up.
192
+			$this->errorPos = [];
193
+			$this->currFile = $file;
194
+		}
195
+
196
+		$tokens = $phpcsFile->getTokens();
197
+
198
+		if (in_array($tokens[$stackPtr]['code'], $this->supplementaryTokens, true) === true) {
199
+			$this->processSupplementary($phpcsFile, $stackPtr);
200
+		}
201
+
202
+		$type = $tokens[$stackPtr]['code'];
203
+
204
+		// If the type is not set, then it must have been a token registered
205
+		// with registerSupplementary().
206
+		if (isset($this->parsedPatterns[$type]) === false) {
207
+			return;
208
+		}
209
+
210
+		$allErrors = [];
211
+
212
+		// Loop over each pattern that is listening to the current token type
213
+		// that we are processing.
214
+		foreach ($this->parsedPatterns[$type] as $patternInfo) {
215
+			// If processPattern returns false, then the pattern that we are
216
+			// checking the code with must not be designed to check that code.
217
+			$errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr);
218
+			if ($errors === false) {
219
+				// The pattern didn't match.
220
+				continue;
221
+			} else if (empty($errors) === true) {
222
+				// The pattern matched, but there were no errors.
223
+				break;
224
+			}
225
+
226
+			foreach ($errors as $stackPtr => $error) {
227
+				if (isset($this->errorPos[$stackPtr]) === false) {
228
+					$this->errorPos[$stackPtr] = true;
229
+					$allErrors[$stackPtr]      = $error;
230
+				}
231
+			}
232
+		}
233
+
234
+		foreach ($allErrors as $stackPtr => $error) {
235
+			$phpcsFile->addError($error, $stackPtr, 'Found');
236
+		}
237
+
238
+	}//end process()
239
+
240
+
241
+	/**
242
+	 * Processes the pattern and verifies the code at $stackPtr.
243
+	 *
244
+	 * @param array                       $patternInfo Information about the pattern used
245
+	 *                                                 for checking, which includes are
246
+	 *                                                 parsed token representation of the
247
+	 *                                                 pattern.
248
+	 * @param \PHP_CodeSniffer\Files\File $phpcsFile   The PHP_CodeSniffer file where the
249
+	 *                                                 token occurred.
250
+	 * @param int                         $stackPtr    The position in the tokens stack where
251
+	 *                                                 the listening token type was found.
252
+	 *
253
+	 * @return array
254
+	 */
255
+	protected function processPattern($patternInfo, File $phpcsFile, $stackPtr)
256
+	{
257
+		$tokens      = $phpcsFile->getTokens();
258
+		$pattern     = $patternInfo['pattern'];
259
+		$patternCode = $patternInfo['pattern_code'];
260
+		$errors      = [];
261
+		$found       = '';
262
+
263
+		$ignoreTokens = [T_WHITESPACE => T_WHITESPACE];
264
+		if ($this->ignoreComments === true) {
265
+			$ignoreTokens += Tokens::$commentTokens;
266
+		}
267
+
268
+		$origStackPtr = $stackPtr;
269
+		$hasError     = false;
270
+
271
+		if ($patternInfo['listen_pos'] > 0) {
272
+			$stackPtr--;
273
+
274
+			for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) {
275
+				if ($pattern[$i]['type'] === 'token') {
276
+					if ($pattern[$i]['token'] === T_WHITESPACE) {
277
+						if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
278
+							$found = $tokens[$stackPtr]['content'].$found;
279
+						}
280
+
281
+						// Only check the size of the whitespace if this is not
282
+						// the first token. We don't care about the size of
283
+						// leading whitespace, just that there is some.
284
+						if ($i !== 0) {
285
+							if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) {
286
+								$hasError = true;
287
+							}
288
+						}
289
+					} else {
290
+						// Check to see if this important token is the same as the
291
+						// previous important token in the pattern. If it is not,
292
+						// then the pattern cannot be for this piece of code.
293
+						$prev = $phpcsFile->findPrevious(
294
+							$ignoreTokens,
295
+							$stackPtr,
296
+							null,
297
+							true
298
+						);
299
+
300
+						if ($prev === false
301
+							|| $tokens[$prev]['code'] !== $pattern[$i]['token']
302
+						) {
303
+							return false;
304
+						}
305
+
306
+						// If we skipped past some whitespace tokens, then add them
307
+						// to the found string.
308
+						$tokenContent = $phpcsFile->getTokensAsString(
309
+							($prev + 1),
310
+							($stackPtr - $prev - 1)
311
+						);
312
+
313
+						$found = $tokens[$prev]['content'].$tokenContent.$found;
314
+
315
+						if (isset($pattern[($i - 1)]) === true
316
+							&& $pattern[($i - 1)]['type'] === 'skip'
317
+						) {
318
+							$stackPtr = $prev;
319
+						} else {
320
+							$stackPtr = ($prev - 1);
321
+						}
322
+					}//end if
323
+				} else if ($pattern[$i]['type'] === 'skip') {
324
+					// Skip to next piece of relevant code.
325
+					if ($pattern[$i]['to'] === 'parenthesis_closer') {
326
+						$to = 'parenthesis_opener';
327
+					} else {
328
+						$to = 'scope_opener';
329
+					}
330
+
331
+					// Find the previous opener.
332
+					$next = $phpcsFile->findPrevious(
333
+						$ignoreTokens,
334
+						$stackPtr,
335
+						null,
336
+						true
337
+					);
338
+
339
+					if ($next === false || isset($tokens[$next][$to]) === false) {
340
+						// If there was not opener, then we must be
341
+						// using the wrong pattern.
342
+						return false;
343
+					}
344
+
345
+					if ($to === 'parenthesis_opener') {
346
+						$found = '{'.$found;
347
+					} else {
348
+						$found = '('.$found;
349
+					}
350
+
351
+					$found = '...'.$found;
352
+
353
+					// Skip to the opening token.
354
+					$stackPtr = ($tokens[$next][$to] - 1);
355
+				} else if ($pattern[$i]['type'] === 'string') {
356
+					$found = 'abc';
357
+				} else if ($pattern[$i]['type'] === 'newline') {
358
+					if ($this->ignoreComments === true
359
+						&& isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true
360
+					) {
361
+						$startComment = $phpcsFile->findPrevious(
362
+							Tokens::$commentTokens,
363
+							($stackPtr - 1),
364
+							null,
365
+							true
366
+						);
367
+
368
+						if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) {
369
+							$startComment++;
370
+						}
371
+
372
+						$tokenContent = $phpcsFile->getTokensAsString(
373
+							$startComment,
374
+							($stackPtr - $startComment + 1)
375
+						);
376
+
377
+						$found    = $tokenContent.$found;
378
+						$stackPtr = ($startComment - 1);
379
+					}
380
+
381
+					if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
382
+						if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) {
383
+							$found = $tokens[$stackPtr]['content'].$found;
384
+
385
+							// This may just be an indent that comes after a newline
386
+							// so check the token before to make sure. If it is a newline, we
387
+							// can ignore the error here.
388
+							if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar)
389
+								&& ($this->ignoreComments === true
390
+								&& isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false)
391
+							) {
392
+								$hasError = true;
393
+							} else {
394
+								$stackPtr--;
395
+							}
396
+						} else {
397
+							$found = 'EOL'.$found;
398
+						}
399
+					} else {
400
+						$found    = $tokens[$stackPtr]['content'].$found;
401
+						$hasError = true;
402
+					}//end if
403
+
404
+					if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') {
405
+						// Make sure they only have 1 newline.
406
+						$prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
407
+						if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) {
408
+							$hasError = true;
409
+						}
410
+					}
411
+				}//end if
412
+			}//end for
413
+		}//end if
414
+
415
+		$stackPtr          = $origStackPtr;
416
+		$lastAddedStackPtr = null;
417
+		$patternLen        = count($pattern);
418
+
419
+		for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) {
420
+			if (isset($tokens[$stackPtr]) === false) {
421
+				break;
422
+			}
423
+
424
+			if ($pattern[$i]['type'] === 'token') {
425
+				if ($pattern[$i]['token'] === T_WHITESPACE) {
426
+					if ($this->ignoreComments === true) {
427
+						// If we are ignoring comments, check to see if this current
428
+						// token is a comment. If so skip it.
429
+						if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) {
430
+							continue;
431
+						}
432
+
433
+						// If the next token is a comment, the we need to skip the
434
+						// current token as we should allow a space before a
435
+						// comment for readability.
436
+						if (isset($tokens[($stackPtr + 1)]) === true
437
+							&& isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true
438
+						) {
439
+							continue;
440
+						}
441
+					}
442
+
443
+					$tokenContent = '';
444
+					if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
445
+						if (isset($pattern[($i + 1)]) === false) {
446
+							// This is the last token in the pattern, so just compare
447
+							// the next token of content.
448
+							$tokenContent = $tokens[$stackPtr]['content'];
449
+						} else {
450
+							// Get all the whitespace to the next token.
451
+							$next = $phpcsFile->findNext(
452
+								Tokens::$emptyTokens,
453
+								$stackPtr,
454
+								null,
455
+								true
456
+							);
457
+
458
+							$tokenContent = $phpcsFile->getTokensAsString(
459
+								$stackPtr,
460
+								($next - $stackPtr)
461
+							);
462
+
463
+							$lastAddedStackPtr = $stackPtr;
464
+							$stackPtr          = $next;
465
+						}//end if
466
+
467
+						if ($stackPtr !== $lastAddedStackPtr) {
468
+							$found .= $tokenContent;
469
+						}
470
+					} else {
471
+						if ($stackPtr !== $lastAddedStackPtr) {
472
+							$found            .= $tokens[$stackPtr]['content'];
473
+							$lastAddedStackPtr = $stackPtr;
474
+						}
475
+					}//end if
476
+
477
+					if (isset($pattern[($i + 1)]) === true
478
+						&& $pattern[($i + 1)]['type'] === 'skip'
479
+					) {
480
+						// The next token is a skip token, so we just need to make
481
+						// sure the whitespace we found has *at least* the
482
+						// whitespace required.
483
+						if (strpos($tokenContent, $pattern[$i]['value']) !== 0) {
484
+							$hasError = true;
485
+						}
486
+					} else {
487
+						if ($tokenContent !== $pattern[$i]['value']) {
488
+							$hasError = true;
489
+						}
490
+					}
491
+				} else {
492
+					// Check to see if this important token is the same as the
493
+					// next important token in the pattern. If it is not, then
494
+					// the pattern cannot be for this piece of code.
495
+					$next = $phpcsFile->findNext(
496
+						$ignoreTokens,
497
+						$stackPtr,
498
+						null,
499
+						true
500
+					);
501
+
502
+					if ($next === false
503
+						|| $tokens[$next]['code'] !== $pattern[$i]['token']
504
+					) {
505
+						// The next important token did not match the pattern.
506
+						return false;
507
+					}
508
+
509
+					if ($lastAddedStackPtr !== null) {
510
+						if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET
511
+							|| $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET)
512
+							&& isset($tokens[$next]['scope_condition']) === true
513
+							&& $tokens[$next]['scope_condition'] > $lastAddedStackPtr
514
+						) {
515
+							// This is a brace, but the owner of it is after the current
516
+							// token, which means it does not belong to any token in
517
+							// our pattern. This means the pattern is not for us.
518
+							return false;
519
+						}
520
+
521
+						if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS
522
+							|| $tokens[$next]['code'] === T_CLOSE_PARENTHESIS)
523
+							&& isset($tokens[$next]['parenthesis_owner']) === true
524
+							&& $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr
525
+						) {
526
+							// This is a bracket, but the owner of it is after the current
527
+							// token, which means it does not belong to any token in
528
+							// our pattern. This means the pattern is not for us.
529
+							return false;
530
+						}
531
+					}//end if
532
+
533
+					// If we skipped past some whitespace tokens, then add them
534
+					// to the found string.
535
+					if (($next - $stackPtr) > 0) {
536
+						$hasComment = false;
537
+						for ($j = $stackPtr; $j < $next; $j++) {
538
+							$found .= $tokens[$j]['content'];
539
+							if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) {
540
+								$hasComment = true;
541
+							}
542
+						}
543
+
544
+						// If we are not ignoring comments, this additional
545
+						// whitespace or comment is not allowed. If we are
546
+						// ignoring comments, there needs to be at least one
547
+						// comment for this to be allowed.
548
+						if ($this->ignoreComments === false
549
+							|| ($this->ignoreComments === true
550
+							&& $hasComment === false)
551
+						) {
552
+							$hasError = true;
553
+						}
554
+
555
+						// Even when ignoring comments, we are not allowed to include
556
+						// newlines without the pattern specifying them, so
557
+						// everything should be on the same line.
558
+						if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) {
559
+							$hasError = true;
560
+						}
561
+					}//end if
562
+
563
+					if ($next !== $lastAddedStackPtr) {
564
+						$found            .= $tokens[$next]['content'];
565
+						$lastAddedStackPtr = $next;
566
+					}
567
+
568
+					if (isset($pattern[($i + 1)]) === true
569
+						&& $pattern[($i + 1)]['type'] === 'skip'
570
+					) {
571
+						$stackPtr = $next;
572
+					} else {
573
+						$stackPtr = ($next + 1);
574
+					}
575
+				}//end if
576
+			} else if ($pattern[$i]['type'] === 'skip') {
577
+				if ($pattern[$i]['to'] === 'unknown') {
578
+					$next = $phpcsFile->findNext(
579
+						$pattern[($i + 1)]['token'],
580
+						$stackPtr
581
+					);
582
+
583
+					if ($next === false) {
584
+						// Couldn't find the next token, so we must
585
+						// be using the wrong pattern.
586
+						return false;
587
+					}
588
+
589
+					$found   .= '...';
590
+					$stackPtr = $next;
591
+				} else {
592
+					// Find the previous opener.
593
+					$next = $phpcsFile->findPrevious(
594
+						Tokens::$blockOpeners,
595
+						$stackPtr
596
+					);
597
+
598
+					if ($next === false
599
+						|| isset($tokens[$next][$pattern[$i]['to']]) === false
600
+					) {
601
+						// If there was not opener, then we must
602
+						// be using the wrong pattern.
603
+						return false;
604
+					}
605
+
606
+					$found .= '...';
607
+					if ($pattern[$i]['to'] === 'parenthesis_closer') {
608
+						$found .= ')';
609
+					} else {
610
+						$found .= '}';
611
+					}
612
+
613
+					// Skip to the closing token.
614
+					$stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1);
615
+				}//end if
616
+			} else if ($pattern[$i]['type'] === 'string') {
617
+				if ($tokens[$stackPtr]['code'] !== T_STRING) {
618
+					$hasError = true;
619
+				}
620
+
621
+				if ($stackPtr !== $lastAddedStackPtr) {
622
+					$found            .= 'abc';
623
+					$lastAddedStackPtr = $stackPtr;
624
+				}
625
+
626
+				$stackPtr++;
627
+			} else if ($pattern[$i]['type'] === 'newline') {
628
+				// Find the next token that contains a newline character.
629
+				$newline = 0;
630
+				for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) {
631
+					if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) {
632
+						$newline = $j;
633
+						break;
634
+					}
635
+				}
636
+
637
+				if ($newline === 0) {
638
+					// We didn't find a newline character in the rest of the file.
639
+					$next     = ($phpcsFile->numTokens - 1);
640
+					$hasError = true;
641
+				} else {
642
+					if ($this->ignoreComments === false) {
643
+						// The newline character cannot be part of a comment.
644
+						if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) {
645
+							$hasError = true;
646
+						}
647
+					}
648
+
649
+					if ($newline === $stackPtr) {
650
+						$next = ($stackPtr + 1);
651
+					} else {
652
+						// Check that there were no significant tokens that we
653
+						// skipped over to find our newline character.
654
+						$next = $phpcsFile->findNext(
655
+							$ignoreTokens,
656
+							$stackPtr,
657
+							null,
658
+							true
659
+						);
660
+
661
+						if ($next < $newline) {
662
+							// We skipped a non-ignored token.
663
+							$hasError = true;
664
+						} else {
665
+							$next = ($newline + 1);
666
+						}
667
+					}
668
+				}//end if
669
+
670
+				if ($stackPtr !== $lastAddedStackPtr) {
671
+					$found .= $phpcsFile->getTokensAsString(
672
+						$stackPtr,
673
+						($next - $stackPtr)
674
+					);
675
+
676
+					$lastAddedStackPtr = ($next - 1);
677
+				}
678
+
679
+				$stackPtr = $next;
680
+			}//end if
681
+		}//end for
682
+
683
+		if ($hasError === true) {
684
+			$error = $this->prepareError($found, $patternCode);
685
+			$errors[$origStackPtr] = $error;
686
+		}
687
+
688
+		return $errors;
689
+
690
+	}//end processPattern()
691
+
692
+
693
+	/**
694
+	 * Prepares an error for the specified patternCode.
695
+	 *
696
+	 * @param string $found       The actual found string in the code.
697
+	 * @param string $patternCode The expected pattern code.
698
+	 *
699
+	 * @return string The error message.
700
+	 */
701
+	protected function prepareError($found, $patternCode)
702
+	{
703
+		$found    = str_replace("\r\n", '\n', $found);
704
+		$found    = str_replace("\n", '\n', $found);
705
+		$found    = str_replace("\r", '\n', $found);
706
+		$found    = str_replace("\t", '\t', $found);
707
+		$found    = str_replace('EOL', '\n', $found);
708
+		$expected = str_replace('EOL', '\n', $patternCode);
709
+
710
+		$error = "Expected \"$expected\"; found \"$found\"";
711
+
712
+		return $error;
713
+
714
+	}//end prepareError()
715
+
716
+
717
+	/**
718
+	 * Returns the patterns that should be checked.
719
+	 *
720
+	 * @return string[]
721
+	 */
722
+	abstract protected function getPatterns();
723
+
724
+
725
+	/**
726
+	 * Registers any supplementary tokens that this test might wish to process.
727
+	 *
728
+	 * A sniff may wish to register supplementary tests when it wishes to group
729
+	 * an arbitrary validation that cannot be performed using a pattern, with
730
+	 * other pattern tests.
731
+	 *
732
+	 * @return int[]
733
+	 * @see    processSupplementary()
734
+	 */
735
+	protected function registerSupplementary()
736
+	{
737
+		return [];
738
+
739
+	}//end registerSupplementary()
740
+
741
+
742
+	 /**
743
+	  * Processes any tokens registered with registerSupplementary().
744
+	  *
745
+	  * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where to
746
+	  *                                               process the skip.
747
+	  * @param int                         $stackPtr  The position in the tokens stack to
748
+	  *                                               process.
749
+	  *
750
+	  * @return void
751
+	  * @see    registerSupplementary()
752
+	  */
753
+	protected function processSupplementary(File $phpcsFile, $stackPtr)
754
+	{
755
+
756
+	}//end processSupplementary()
757
+
758
+
759
+	/**
760
+	 * Parses a pattern string into an array of pattern steps.
761
+	 *
762
+	 * @param string $pattern The pattern to parse.
763
+	 *
764
+	 * @return array The parsed pattern array.
765
+	 * @see    createSkipPattern()
766
+	 * @see    createTokenPattern()
767
+	 */
768
+	private function parse($pattern)
769
+	{
770
+		$patterns   = [];
771
+		$length     = strlen($pattern);
772
+		$lastToken  = 0;
773
+		$firstToken = 0;
774
+
775
+		for ($i = 0; $i < $length; $i++) {
776
+			$specialPattern = false;
777
+			$isLastChar     = ($i === ($length - 1));
778
+			$oldFirstToken  = $firstToken;
779
+
780
+			if (substr($pattern, $i, 3) === '...') {
781
+				// It's a skip pattern. The skip pattern requires the
782
+				// content of the token in the "from" position and the token
783
+				// to skip to.
784
+				$specialPattern = $this->createSkipPattern($pattern, ($i - 1));
785
+				$lastToken      = ($i - $firstToken);
786
+				$firstToken     = ($i + 3);
787
+				$i += 2;
788
+
789
+				if ($specialPattern['to'] !== 'unknown') {
790
+					$firstToken++;
791
+				}
792
+			} else if (substr($pattern, $i, 3) === 'abc') {
793
+				$specialPattern = ['type' => 'string'];
794
+				$lastToken      = ($i - $firstToken);
795
+				$firstToken     = ($i + 3);
796
+				$i += 2;
797
+			} else if (substr($pattern, $i, 3) === 'EOL') {
798
+				$specialPattern = ['type' => 'newline'];
799
+				$lastToken      = ($i - $firstToken);
800
+				$firstToken     = ($i + 3);
801
+				$i += 2;
802
+			}//end if
803
+
804
+			if ($specialPattern !== false || $isLastChar === true) {
805
+				// If we are at the end of the string, don't worry about a limit.
806
+				if ($isLastChar === true) {
807
+					// Get the string from the end of the last skip pattern, if any,
808
+					// to the end of the pattern string.
809
+					$str = substr($pattern, $oldFirstToken);
810
+				} else {
811
+					// Get the string from the end of the last special pattern,
812
+					// if any, to the start of this special pattern.
813
+					if ($lastToken === 0) {
814
+						// Note that if the last special token was zero characters ago,
815
+						// there will be nothing to process so we can skip this bit.
816
+						// This happens if you have something like: EOL... in your pattern.
817
+						$str = '';
818
+					} else {
819
+						$str = substr($pattern, $oldFirstToken, $lastToken);
820
+					}
821
+				}
822
+
823
+				if ($str !== '') {
824
+					$tokenPatterns = $this->createTokenPattern($str);
825
+					foreach ($tokenPatterns as $tokenPattern) {
826
+						$patterns[] = $tokenPattern;
827
+					}
828
+				}
829
+
830
+				// Make sure we don't skip the last token.
831
+				if ($isLastChar === false && $i === ($length - 1)) {
832
+					$i--;
833
+				}
834
+			}//end if
835
+
836
+			// Add the skip pattern *after* we have processed
837
+			// all the tokens from the end of the last skip pattern
838
+			// to the start of this skip pattern.
839
+			if ($specialPattern !== false) {
840
+				$patterns[] = $specialPattern;
841
+			}
842
+		}//end for
843
+
844
+		return $patterns;
845
+
846
+	}//end parse()
847
+
848
+
849
+	/**
850
+	 * Creates a skip pattern.
851
+	 *
852
+	 * @param string $pattern The pattern being parsed.
853
+	 * @param string $from    The token content that the skip pattern starts from.
854
+	 *
855
+	 * @return array The pattern step.
856
+	 * @see    createTokenPattern()
857
+	 * @see    parse()
858
+	 */
859
+	private function createSkipPattern($pattern, $from)
860
+	{
861
+		$skip = ['type' => 'skip'];
862
+
863
+		$nestedParenthesis = 0;
864
+		$nestedBraces      = 0;
865
+		for ($start = $from; $start >= 0; $start--) {
866
+			switch ($pattern[$start]) {
867
+			case '(':
868
+				if ($nestedParenthesis === 0) {
869
+					$skip['to'] = 'parenthesis_closer';
870
+				}
871
+
872
+				$nestedParenthesis--;
873
+				break;
874
+			case '{':
875
+				if ($nestedBraces === 0) {
876
+					$skip['to'] = 'scope_closer';
877
+				}
878
+
879
+				$nestedBraces--;
880
+				break;
881
+			case '}':
882
+				$nestedBraces++;
883
+				break;
884
+			case ')':
885
+				$nestedParenthesis++;
886
+				break;
887
+			}//end switch
888
+
889
+			if (isset($skip['to']) === true) {
890
+				break;
891
+			}
892
+		}//end for
893
+
894
+		if (isset($skip['to']) === false) {
895
+			$skip['to'] = 'unknown';
896
+		}
897
+
898
+		return $skip;
899
+
900
+	}//end createSkipPattern()
901
+
902
+
903
+	/**
904
+	 * Creates a token pattern.
905
+	 *
906
+	 * @param string $str The tokens string that the pattern should match.
907
+	 *
908
+	 * @return array The pattern step.
909
+	 * @see    createSkipPattern()
910
+	 * @see    parse()
911
+	 */
912
+	private function createTokenPattern($str)
913
+	{
914
+		// Don't add a space after the closing php tag as it will add a new
915
+		// whitespace token.
916
+		$tokenizer = new PHP('<?php '.$str.'?>', null);
917
+
918
+		// Remove the <?php tag from the front and the end php tag from the back.
919
+		$tokens = $tokenizer->getTokens();
920
+		$tokens = array_slice($tokens, 1, (count($tokens) - 2));
921
+
922
+		$patterns = [];
923
+		foreach ($tokens as $patternInfo) {
924
+			$patterns[] = [
925
+				'type'  => 'token',
926
+				'token' => $patternInfo['code'],
927
+				'value' => $patternInfo['content'],
928
+			];
929
+		}
930
+
931
+		return $patterns;
932
+
933
+	}//end createTokenPattern()
934 934
 
935 935
 
936 936
 }//end class
Please login to merge, or discard this patch.