• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 17583953915

09 Sep 2025 01:19PM UTC coverage: 78.879%. First build
17583953915

push

github

web-flow
Merge pull request #1222 from PHPCSStandards/phpcs-4.0/feature/modernize-use-class-constants-for-constant-arrays-1

Modernize: use class constant for constant array [1]

4 of 5 new or added lines in 2 files covered. (80.0%)

19838 of 25150 relevant lines covered (78.88%)

94.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

38.38
/src/Config.php
1
<?php
2
/**
3
 * Stores the configuration used to run PHPCS and PHPCBF.
4
 *
5
 * Parses the command line to determine user supplied values
6
 * and provides functions to access data stored in config files.
7
 *
8
 * @author    Greg Sherwood <gsherwood@squiz.net>
9
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
10
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
11
 */
12

13
namespace PHP_CodeSniffer;
14

15
use Exception;
16
use Phar;
17
use PHP_CodeSniffer\Exceptions\DeepExitException;
18
use PHP_CodeSniffer\Exceptions\RuntimeException;
19
use PHP_CodeSniffer\Util\Common;
20
use PHP_CodeSniffer\Util\ExitCode;
21
use PHP_CodeSniffer\Util\Help;
22
use PHP_CodeSniffer\Util\Standards;
23

24
/**
25
 * Stores the configuration used to run PHPCS and PHPCBF.
26
 *
27
 * @property string[]   $files           The files and directories to check.
28
 * @property string[]   $standards       The standards being used for checking.
29
 * @property int        $verbosity       How verbose the output should be.
30
 *                                       0: no unnecessary output
31
 *                                       1: basic output for files being checked
32
 *                                       2: ruleset and file parsing output
33
 *                                       3: sniff execution output
34
 * @property bool       $interactive     Enable interactive checking mode.
35
 * @property int        $parallel        Check files in parallel.
36
 * @property bool       $cache           Enable the use of the file cache.
37
 * @property string     $cacheFile       Path to the file where the cache data should be written
38
 * @property bool       $colors          Display colours in output.
39
 * @property bool       $explain         Explain the coding standards.
40
 * @property bool       $local           Process local files in directories only (no recursion).
41
 * @property bool       $showSources     Show sniff source codes in report output.
42
 * @property bool       $showProgress    Show basic progress information while running.
43
 * @property bool       $quiet           Quiet mode; disables progress and verbose output.
44
 * @property bool       $annotations     Process phpcs: annotations.
45
 * @property int        $tabWidth        How many spaces each tab is worth.
46
 * @property string     $encoding        The encoding of the files being checked.
47
 * @property string[]   $sniffs          The sniffs that should be used for checking.
48
 *                                       If empty, all sniffs in the supplied standards will be used.
49
 * @property string[]   $exclude         The sniffs that should be excluded from checking.
50
 *                                       If empty, all sniffs in the supplied standards will be used.
51
 * @property string[]   $ignored         Regular expressions used to ignore files and folders during checking.
52
 * @property string     $reportFile      A file where the report output should be written.
53
 * @property string     $generator       The documentation generator to use.
54
 * @property string     $filter          The filter to use for the run.
55
 * @property string[]   $bootstrap       One of more files to include before the run begins.
56
 * @property int|string $reportWidth     The maximum number of columns that reports should use for output.
57
 *                                       Set to "auto" for have this value changed to the width of the terminal.
58
 * @property int        $errorSeverity   The minimum severity an error must have to be displayed.
59
 * @property int        $warningSeverity The minimum severity a warning must have to be displayed.
60
 * @property bool       $recordErrors    Record the content of error messages as well as error counts.
61
 * @property string     $suffix          A suffix to add to fixed files.
62
 * @property string     $basepath        A file system location to strip from the paths of files shown in reports.
63
 * @property bool       $stdin           Read content from STDIN instead of supplied files.
64
 * @property string     $stdinContent    Content passed directly to PHPCS on STDIN.
65
 * @property string     $stdinPath       The path to use for content passed on STDIN.
66
 * @property bool       $trackTime       Whether or not to track sniff run time.
67
 *
68
 * @property array<string, string>      $extensions File extensions that should be checked, and what tokenizer is used.
69
 *                                                  E.g., array('inc' => 'PHP');
70
 *                                                  Note: since PHPCS 4.0.0, the tokenizer used will always be 'PHP',
71
 *                                                  but the array format of the property has not been changed to prevent
72
 *                                                  breaking integrations which may be accessing this property.
73
 * @property array<string, string|null> $reports    The reports to use for printing output after the run.
74
 *                                                  The format of the array is:
75
 *                                                      array(
76
 *                                                          'reportName1' => 'outputFile',
77
 *                                                          'reportName2' => null,
78
 *                                                      );
79
 *                                                  If the array value is NULL, the report will be written to the screen.
80
 *
81
 * @property string[] $unknown Any arguments gathered on the command line that are unknown to us.
82
 *                             E.g., using `phpcs -c` will give array('c');
83
 */
84
class Config
85
{
86

87
    /**
88
     * The current version.
89
     *
90
     * @var string
91
     */
92
    public const VERSION = '4.0.0';
93

94
    /**
95
     * Package stability; either stable, RC, beta or alpha.
96
     *
97
     * @var string
98
     */
99
    public const STABILITY = 'RC';
100

101
    /**
102
     * Default report width when no report width is provided and 'auto' does not yield a valid width.
103
     *
104
     * @var int
105
     */
106
    public const DEFAULT_REPORT_WIDTH = 80;
107

108
    /**
109
     * Translation table for config settings which can be changed via multiple CLI flags.
110
     *
111
     * If the flag name matches the setting name, there is no need to add it to this translation table.
112
     * Similarly, if there is only one flag which can change a setting, there is no need to include
113
     * it in this table, even if the flag name and the setting name don't match.
114
     *
115
     * @var array<string, string> Key is the CLI flag name, value the corresponding config setting name.
116
     */
117
    public const CLI_FLAGS_TO_SETTING_NAME = [
118
        'n'                => 'warningSeverity',
119
        'w'                => 'warningSeverity',
120
        'warning-severity' => 'warningSeverity',
121
        'no-colors'        => 'colors',
122
        'no-cache'         => 'cache',
123
    ];
124

125
    /**
126
     * A list of valid generators.
127
     *
128
     * @var array<string, string> Keys are the lowercase version of the generator name, while values
129
     *                            are the name of the associated PHP generator class.
130
     */
131
    private const VALID_GENERATORS = [
132
        'text'     => 'Text',
133
        'html'     => 'HTML',
134
        'markdown' => 'Markdown',
135
    ];
136

137
    /**
138
     * The default configuration file names supported by PHPCS.
139
     *
140
     * @var array<string> The supported file names in order of precedence (highest first).
141
     */
142
    private const CONFIG_FILENAMES = [
143
        '.phpcs.xml',
144
        'phpcs.xml',
145
        '.phpcs.xml.dist',
146
        'phpcs.xml.dist',
147
    ];
148

149
    /**
150
     * An array of settings that PHPCS and PHPCBF accept.
151
     *
152
     * This array is not meant to be accessed directly. Instead, use the settings
153
     * as if they are class member vars so the __get() and __set() magic methods
154
     * can be used to validate the values. For example, to set the verbosity level to
155
     * level 2, use $this->verbosity = 2; instead of accessing this property directly.
156
     *
157
     * Each of these settings is described in the class comment property list.
158
     *
159
     * @var array<string, mixed>
160
     */
161
    private $settings = [
162
        'files'           => null,
163
        'standards'       => null,
164
        'verbosity'       => null,
165
        'interactive'     => null,
166
        'parallel'        => null,
167
        'cache'           => null,
168
        'cacheFile'       => null,
169
        'colors'          => null,
170
        'explain'         => null,
171
        'local'           => null,
172
        'showSources'     => null,
173
        'showProgress'    => null,
174
        'quiet'           => null,
175
        'annotations'     => null,
176
        'tabWidth'        => null,
177
        'encoding'        => null,
178
        'extensions'      => null,
179
        'sniffs'          => null,
180
        'exclude'         => null,
181
        'ignored'         => null,
182
        'reportFile'      => null,
183
        'generator'       => null,
184
        'filter'          => null,
185
        'bootstrap'       => null,
186
        'reports'         => null,
187
        'basepath'        => null,
188
        'reportWidth'     => null,
189
        'errorSeverity'   => null,
190
        'warningSeverity' => null,
191
        'recordErrors'    => null,
192
        'suffix'          => null,
193
        'stdin'           => null,
194
        'stdinContent'    => null,
195
        'stdinPath'       => null,
196
        'trackTime'       => null,
197
        'unknown'         => null,
198
    ];
199

200
    /**
201
     * Whether or not to kill the process when an unknown command line arg is found.
202
     *
203
     * If FALSE, arguments that are not command line options or file/directory paths
204
     * will be ignored and execution will continue. These values will be stored in
205
     * $this->unknown.
206
     *
207
     * @var boolean
208
     */
209
    public $dieOnUnknownArg;
210

211
    /**
212
     * The current command line arguments we are processing.
213
     *
214
     * @var string[]
215
     */
216
    private $cliArgs = [];
217

218
    /**
219
     * Command line values that the user has supplied directly.
220
     *
221
     * @var array<string, true|array<string, true>>
222
     */
223
    private $overriddenDefaults = [];
224

225
    /**
226
     * Config file data that has been loaded for the run.
227
     *
228
     * @var array<string, string>
229
     */
230
    private static $configData = null;
231

232
    /**
233
     * The full path to the config data file that has been loaded.
234
     *
235
     * @var string
236
     */
237
    private static $configDataFile = null;
238

239
    /**
240
     * Automatically discovered executable utility paths.
241
     *
242
     * @var array<string, string>
243
     */
244
    private static $executablePaths = [];
245

246

247
    /**
248
     * Get the value of an inaccessible property.
249
     *
250
     * @param string $name The name of the property.
251
     *
252
     * @return mixed
253
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
254
     */
255
    public function __get($name)
48✔
256
    {
257
        if (array_key_exists($name, $this->settings) === false) {
48✔
258
            throw new RuntimeException("ERROR: unable to get value of property \"$name\"");
×
259
        }
260

261
        // Figure out what the terminal width needs to be for "auto".
262
        if ($name === 'reportWidth' && $this->settings[$name] === 'auto') {
48✔
263
            if (function_exists('shell_exec') === true) {
9✔
264
                $dimensions = shell_exec('stty size 2>&1');
9✔
265
                if (is_string($dimensions) === true && preg_match('|\d+ (\d+)|', $dimensions, $matches) === 1) {
9✔
266
                    $this->settings[$name] = (int) $matches[1];
×
267
                }
268
            }
269

270
            if ($this->settings[$name] === 'auto') {
9✔
271
                // If shell_exec wasn't available or didn't yield a usable value, set to the default.
272
                // This will prevent subsequent retrievals of the reportWidth from making another call to stty.
273
                $this->settings[$name] = self::DEFAULT_REPORT_WIDTH;
9✔
274
            }
275
        }
276

277
        return $this->settings[$name];
48✔
278

279
    }//end __get()
280

281

282
    /**
283
     * Set the value of an inaccessible property.
284
     *
285
     * @param string $name  The name of the property.
286
     * @param mixed  $value The value of the property.
287
     *
288
     * @return void
289
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
290
     */
291
    public function __set($name, $value)
48✔
292
    {
293
        if (array_key_exists($name, $this->settings) === false) {
48✔
294
            throw new RuntimeException("Can't __set() $name; setting doesn't exist");
×
295
        }
296

297
        switch ($name) {
16✔
298
        case 'reportWidth' :
48✔
299
            if (is_string($value) === true && $value === 'auto') {
48✔
300
                // Nothing to do. Leave at 'auto'.
301
                break;
48✔
302
            }
303

304
            if (is_int($value) === true) {
39✔
305
                $value = abs($value);
6✔
306
            } else if (is_string($value) === true && preg_match('`^\d+$`', $value) === 1) {
33✔
307
                $value = (int) $value;
15✔
308
            } else {
309
                $value = self::DEFAULT_REPORT_WIDTH;
18✔
310
            }
311
            break;
39✔
312

313
        case 'standards' :
48✔
314
            $cleaned = [];
48✔
315

316
            // Check if the standard name is valid, or if the case is invalid.
317
            $installedStandards = Standards::getInstalledStandards();
48✔
318
            foreach ($value as $standard) {
48✔
319
                foreach ($installedStandards as $validStandard) {
48✔
320
                    if (strtolower($standard) === strtolower($validStandard)) {
48✔
321
                        $standard = $validStandard;
48✔
322
                        break;
48✔
323
                    }
324
                }
325

326
                $cleaned[] = $standard;
48✔
327
            }
328

329
            $value = $cleaned;
48✔
330
            break;
48✔
331

332
        // Only track time when explicitly needed.
333
        case 'verbosity':
48✔
334
            if ($value > 2) {
48✔
335
                $this->settings['trackTime'] = true;
×
336
            }
337
            break;
48✔
338
        case 'reports':
48✔
339
            $reports = array_change_key_case($value, CASE_LOWER);
48✔
340
            if (array_key_exists('performance', $reports) === true) {
48✔
341
                $this->settings['trackTime'] = true;
×
342
            }
343
            break;
48✔
344

345
        default :
346
            // No validation required.
347
            break;
48✔
348
        }//end switch
349

350
        $this->settings[$name] = $value;
48✔
351

352
    }//end __set()
16✔
353

354

355
    /**
356
     * Check if the value of an inaccessible property is set.
357
     *
358
     * @param string $name The name of the property.
359
     *
360
     * @return bool
361
     */
362
    public function __isset($name)
×
363
    {
364
        return isset($this->settings[$name]);
×
365

366
    }//end __isset()
367

368

369
    /**
370
     * Unset the value of an inaccessible property.
371
     *
372
     * @param string $name The name of the property.
373
     *
374
     * @return void
375
     */
376
    public function __unset($name)
×
377
    {
378
        $this->settings[$name] = null;
×
379

380
    }//end __unset()
381

382

383
    /**
384
     * Get the array of all config settings.
385
     *
386
     * @return array<string, mixed>
387
     */
388
    public function getSettings()
×
389
    {
390
        return $this->settings;
×
391

392
    }//end getSettings()
393

394

395
    /**
396
     * Set the array of all config settings.
397
     *
398
     * @param array<string, mixed> $settings The array of config settings.
399
     *
400
     * @return void
401
     */
402
    public function setSettings($settings)
×
403
    {
404
        $this->settings = $settings;
×
405

406
    }//end setSettings()
407

408

409
    /**
410
     * Creates a Config object and populates it with command line values.
411
     *
412
     * @param array $cliArgs         An array of values gathered from CLI args.
413
     * @param bool  $dieOnUnknownArg Whether or not to kill the process when an
414
     *                               unknown command line arg is found.
415
     *
416
     * @return void
417
     */
418
    public function __construct(array $cliArgs=[], $dieOnUnknownArg=true)
×
419
    {
420
        if (defined('PHP_CODESNIFFER_IN_TESTS') === true) {
×
421
            // Let everything through during testing so that we can
422
            // make use of PHPUnit command line arguments as well.
423
            $this->dieOnUnknownArg = false;
×
424
        } else {
425
            $this->dieOnUnknownArg = $dieOnUnknownArg;
×
426
        }
427

428
        if (empty($cliArgs) === true) {
×
429
            $cliArgs = $_SERVER['argv'];
×
430
            array_shift($cliArgs);
×
431
        }
432

433
        $this->restoreDefaults();
×
434
        $this->setCommandLineValues($cliArgs);
×
435

436
        if (isset($this->overriddenDefaults['standards']) === false) {
×
437
            // They did not supply a standard to use.
438
            // Look for a default ruleset in the current directory or higher.
439
            $currentDir = getcwd();
×
440

441
            do {
NEW
442
                foreach (self::CONFIG_FILENAMES as $defaultFilename) {
×
443
                    $default = $currentDir.DIRECTORY_SEPARATOR.$defaultFilename;
×
444
                    if (is_file($default) === true) {
×
445
                        $this->standards = [$default];
×
446
                        break(2);
×
447
                    }
448
                }
449

450
                $lastDir    = $currentDir;
×
451
                $currentDir = dirname($currentDir);
×
452
            } while ($currentDir !== '.' && $currentDir !== $lastDir && Common::isReadable($currentDir) === true);
×
453
        }//end if
454

455
        if (defined('STDIN') === false
×
456
            || PHP_OS_FAMILY === 'Windows'
×
457
        ) {
458
            return;
×
459
        }
460

461
        $handle = fopen('php://stdin', 'r');
×
462

463
        // Check for content on STDIN.
464
        if ($this->stdin === true
×
465
            || (Common::isStdinATTY() === false
×
466
            && feof($handle) === false)
×
467
        ) {
468
            $readStreams = [$handle];
×
469
            $writeSteams = null;
×
470

471
            $fileContents = '';
×
472
            while (is_resource($handle) === true && feof($handle) === false) {
×
473
                // Set a timeout of 200ms.
474
                if (stream_select($readStreams, $writeSteams, $writeSteams, 0, 200000) === 0) {
×
475
                    break;
×
476
                }
477

478
                $fileContents .= fgets($handle);
×
479
            }
480

481
            if (trim($fileContents) !== '') {
×
482
                $this->stdin        = true;
×
483
                $this->stdinContent = $fileContents;
×
484
                $this->overriddenDefaults['stdin']        = true;
×
485
                $this->overriddenDefaults['stdinContent'] = true;
×
486
            }
487
        }//end if
488

489
        fclose($handle);
×
490

491
    }//end __construct()
492

493

494
    /**
495
     * Set the command line values.
496
     *
497
     * @param array $args An array of command line arguments to set.
498
     *
499
     * @return void
500
     */
501
    public function setCommandLineValues($args)
×
502
    {
503
        $this->cliArgs = $args;
×
504
        $numArgs       = count($args);
×
505

506
        for ($i = 0; $i < $numArgs; $i++) {
×
507
            $arg = $this->cliArgs[$i];
×
508
            if ($arg === '') {
×
509
                continue;
×
510
            }
511

512
            if ($arg[0] === '-') {
×
513
                if ($arg === '-') {
×
514
                    // Asking to read from STDIN.
515
                    $this->stdin = true;
×
516
                    $this->overriddenDefaults['stdin'] = true;
×
517
                    continue;
×
518
                }
519

520
                if ($arg === '--') {
×
521
                    // Empty argument, ignore it.
522
                    continue;
×
523
                }
524

525
                if ($arg[1] === '-') {
×
526
                    $this->processLongArgument(substr($arg, 2), $i);
×
527
                } else {
528
                    $switches = str_split($arg);
×
529
                    foreach ($switches as $switch) {
×
530
                        if ($switch === '-') {
×
531
                            continue;
×
532
                        }
533

534
                        $this->processShortArgument($switch, $i);
×
535
                    }
536
                }
537
            } else {
538
                $this->processUnknownArgument($arg, $i);
×
539
            }//end if
540
        }//end for
541

542
    }//end setCommandLineValues()
543

544

545
    /**
546
     * Restore default values for all possible command line arguments.
547
     *
548
     * @return void
549
     */
550
    public function restoreDefaults()
9✔
551
    {
552
        $this->files           = [];
9✔
553
        $this->standards       = ['PSR12'];
9✔
554
        $this->verbosity       = 0;
9✔
555
        $this->interactive     = false;
9✔
556
        $this->cache           = false;
9✔
557
        $this->cacheFile       = null;
9✔
558
        $this->colors          = false;
9✔
559
        $this->explain         = false;
9✔
560
        $this->local           = false;
9✔
561
        $this->showSources     = false;
9✔
562
        $this->showProgress    = false;
9✔
563
        $this->quiet           = false;
9✔
564
        $this->annotations     = true;
9✔
565
        $this->parallel        = 1;
9✔
566
        $this->tabWidth        = 0;
9✔
567
        $this->encoding        = 'utf-8';
9✔
568
        $this->extensions      = [
9✔
569
            'php' => 'PHP',
6✔
570
            'inc' => 'PHP',
6✔
571
        ];
6✔
572
        $this->sniffs          = [];
9✔
573
        $this->exclude         = [];
9✔
574
        $this->ignored         = [];
9✔
575
        $this->reportFile      = null;
9✔
576
        $this->generator       = null;
9✔
577
        $this->filter          = null;
9✔
578
        $this->bootstrap       = [];
9✔
579
        $this->basepath        = null;
9✔
580
        $this->reports         = ['full' => null];
9✔
581
        $this->reportWidth     = 'auto';
9✔
582
        $this->errorSeverity   = 5;
9✔
583
        $this->warningSeverity = 5;
9✔
584
        $this->recordErrors    = true;
9✔
585
        $this->suffix          = '';
9✔
586
        $this->stdin           = false;
9✔
587
        $this->stdinContent    = null;
9✔
588
        $this->stdinPath       = null;
9✔
589
        $this->trackTime       = false;
9✔
590
        $this->unknown         = [];
9✔
591

592
        $standard = self::getConfigData('default_standard');
9✔
593
        if ($standard !== null) {
9✔
594
            $this->standards = explode(',', $standard);
6✔
595
        }
596

597
        $reportFormat = self::getConfigData('report_format');
9✔
598
        if ($reportFormat !== null) {
9✔
599
            $this->reports = [$reportFormat => null];
×
600
        }
601

602
        $tabWidth = self::getConfigData('tab_width');
9✔
603
        if ($tabWidth !== null) {
9✔
604
            $this->tabWidth = (int) $tabWidth;
×
605
        }
606

607
        $encoding = self::getConfigData('encoding');
9✔
608
        if ($encoding !== null) {
9✔
609
            $this->encoding = strtolower($encoding);
×
610
        }
611

612
        $severity = self::getConfigData('severity');
9✔
613
        if ($severity !== null) {
9✔
614
            $this->errorSeverity   = (int) $severity;
×
615
            $this->warningSeverity = (int) $severity;
×
616
        }
617

618
        $severity = self::getConfigData('error_severity');
9✔
619
        if ($severity !== null) {
9✔
620
            $this->errorSeverity = (int) $severity;
×
621
        }
622

623
        $severity = self::getConfigData('warning_severity');
9✔
624
        if ($severity !== null) {
9✔
625
            $this->warningSeverity = (int) $severity;
×
626
        }
627

628
        $showWarnings = self::getConfigData('show_warnings');
9✔
629
        if ($showWarnings !== null) {
9✔
630
            $showWarnings = (bool) $showWarnings;
3✔
631
            if ($showWarnings === false) {
3✔
632
                $this->warningSeverity = 0;
3✔
633
            }
634
        }
635

636
        $reportWidth = self::getConfigData('report_width');
9✔
637
        if ($reportWidth !== null) {
9✔
638
            $this->reportWidth = $reportWidth;
3✔
639
        }
640

641
        $showProgress = self::getConfigData('show_progress');
9✔
642
        if ($showProgress !== null) {
9✔
643
            $this->showProgress = (bool) $showProgress;
×
644
        }
645

646
        $quiet = self::getConfigData('quiet');
9✔
647
        if ($quiet !== null) {
9✔
648
            $this->quiet = (bool) $quiet;
×
649
        }
650

651
        $colors = self::getConfigData('colors');
9✔
652
        if ($colors !== null) {
9✔
653
            $this->colors = (bool) $colors;
×
654
        }
655

656
        $cache = self::getConfigData('cache');
9✔
657
        if ($cache !== null) {
9✔
658
            $this->cache = (bool) $cache;
×
659
        }
660

661
        $parallel = self::getConfigData('parallel');
9✔
662
        if ($parallel !== null) {
9✔
663
            $this->parallel = max((int) $parallel, 1);
×
664
        }
665

666
    }//end restoreDefaults()
3✔
667

668

669
    /**
670
     * Processes a short (-e) command line argument.
671
     *
672
     * @param string $arg The command line argument.
673
     * @param int    $pos The position of the argument on the command line.
674
     *
675
     * @return void
676
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
677
     */
678
    public function processShortArgument($arg, $pos)
30✔
679
    {
680
        switch ($arg) {
9✔
681
        case 'h':
30✔
682
        case '?':
30✔
683
            $this->printUsage();
×
684
            throw new DeepExitException('', ExitCode::OKAY);
×
685
        case 'i' :
30✔
686
            $output = Standards::prepareInstalledStandardsForDisplay().PHP_EOL;
×
687
            throw new DeepExitException($output, ExitCode::OKAY);
×
688
        case 'v' :
30✔
689
            if ($this->quiet === true) {
×
690
                // Ignore when quiet mode is enabled.
691
                break;
×
692
            }
693

694
            $this->verbosity++;
×
695
            $this->overriddenDefaults['verbosity'] = true;
×
696
            break;
×
697
        case 'l' :
30✔
698
            $this->local = true;
×
699
            $this->overriddenDefaults['local'] = true;
×
700
            break;
×
701
        case 's' :
30✔
702
            $this->showSources = true;
×
703
            $this->overriddenDefaults['showSources'] = true;
×
704
            break;
×
705
        case 'a' :
30✔
706
            $this->interactive = true;
×
707
            $this->overriddenDefaults['interactive'] = true;
×
708
            break;
×
709
        case 'e':
30✔
710
            $this->explain = true;
×
711
            $this->overriddenDefaults['explain'] = true;
×
712
            break;
×
713
        case 'p' :
30✔
714
            if ($this->quiet === true) {
×
715
                // Ignore when quiet mode is enabled.
716
                break;
×
717
            }
718

719
            $this->showProgress = true;
×
720
            $this->overriddenDefaults['showProgress'] = true;
×
721
            break;
×
722
        case 'q' :
30✔
723
            // Quiet mode disables a few other settings as well.
724
            $this->quiet        = true;
×
725
            $this->showProgress = false;
×
726
            $this->verbosity    = 0;
×
727

728
            $this->overriddenDefaults['quiet'] = true;
×
729
            break;
×
730
        case 'm' :
30✔
731
            $this->recordErrors = false;
×
732
            $this->overriddenDefaults['recordErrors'] = true;
×
733
            break;
×
734
        case 'd' :
30✔
735
            $ini = explode('=', $this->cliArgs[($pos + 1)]);
30✔
736
            $this->cliArgs[($pos + 1)] = '';
30✔
737
            if (isset($ini[1]) === false) {
30✔
738
                // Set to true.
739
                $ini[1] = '1';
3✔
740
            }
741

742
            $current = ini_get($ini[0]);
30✔
743
            if ($current === false) {
30✔
744
                // Ini setting which doesn't exist, or is from an unavailable extension.
745
                // Silently ignore it.
746
                break;
4✔
747
            }
748

749
            $changed = ini_set($ini[0], $ini[1]);
26✔
750
            if ($changed === false && ini_get($ini[0]) !== $ini[1]) {
26✔
751
                $error  = sprintf('ERROR: Ini option "%s" cannot be changed at runtime.', $ini[0]).PHP_EOL;
12✔
752
                $error .= $this->printShortUsage(true);
12✔
753
                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
12✔
754
            }
755
            break;
14✔
756
        case 'n' :
×
757
            if (isset($this->overriddenDefaults['warningSeverity']) === false) {
×
758
                $this->warningSeverity = 0;
×
759
                $this->overriddenDefaults['warningSeverity'] = true;
×
760
            }
761
            break;
×
762
        case 'w' :
×
763
            if (isset($this->overriddenDefaults['warningSeverity']) === false) {
×
764
                $this->warningSeverity = $this->errorSeverity;
×
765
                $this->overriddenDefaults['warningSeverity'] = true;
×
766
            }
767
            break;
×
768
        default:
769
            if ($this->dieOnUnknownArg === false) {
×
770
                $unknown       = $this->unknown;
×
771
                $unknown[]     = $arg;
×
772
                $this->unknown = $unknown;
×
773
            } else {
774
                $this->processUnknownArgument('-'.$arg, $pos);
×
775
            }
776
        }//end switch
777

778
    }//end processShortArgument()
5✔
779

780

781
    /**
782
     * Processes a long (--example) command-line argument.
783
     *
784
     * @param string $arg The command line argument.
785
     * @param int    $pos The position of the argument on the command line.
786
     *
787
     * @return void
788
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
789
     */
790
    public function processLongArgument($arg, $pos)
189✔
791
    {
792
        switch ($arg) {
63✔
793
        case 'help':
189✔
794
            $this->printUsage();
×
795
            throw new DeepExitException('', ExitCode::OKAY);
×
796
        case 'version':
189✔
797
            $output  = 'PHP_CodeSniffer version '.self::VERSION.' ('.self::STABILITY.') ';
×
798
            $output .= 'by Squiz and PHPCSStandards'.PHP_EOL;
×
799
            throw new DeepExitException($output, ExitCode::OKAY);
×
800
        case 'colors':
189✔
801
            if (isset($this->overriddenDefaults['colors']) === true) {
×
802
                break;
×
803
            }
804

805
            $this->colors = true;
×
806
            $this->overriddenDefaults['colors'] = true;
×
807
            break;
×
808
        case 'no-colors':
189✔
809
            if (isset($this->overriddenDefaults['colors']) === true) {
×
810
                break;
×
811
            }
812

813
            $this->colors = false;
×
814
            $this->overriddenDefaults['colors'] = true;
×
815
            break;
×
816
        case 'cache':
189✔
817
            if (isset($this->overriddenDefaults['cache']) === true) {
×
818
                break;
×
819
            }
820

821
            $this->cache = true;
×
822
            $this->overriddenDefaults['cache'] = true;
×
823
            break;
×
824
        case 'no-cache':
189✔
825
            if (isset($this->overriddenDefaults['cache']) === true) {
×
826
                break;
×
827
            }
828

829
            $this->cache = false;
×
830
            $this->overriddenDefaults['cache'] = true;
×
831
            break;
×
832
        case 'ignore-annotations':
189✔
833
            if (isset($this->overriddenDefaults['annotations']) === true) {
×
834
                break;
×
835
            }
836

837
            $this->annotations = false;
×
838
            $this->overriddenDefaults['annotations'] = true;
×
839
            break;
×
840
        case 'config-set':
189✔
841
            if (isset($this->cliArgs[($pos + 1)]) === false
×
842
                || isset($this->cliArgs[($pos + 2)]) === false
×
843
            ) {
844
                $error  = 'ERROR: Setting a config option requires a name and value'.PHP_EOL.PHP_EOL;
×
845
                $error .= $this->printShortUsage(true);
×
846
                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
847
            }
848

849
            $key     = $this->cliArgs[($pos + 1)];
×
850
            $value   = $this->cliArgs[($pos + 2)];
×
851
            $current = self::getConfigData($key);
×
852

853
            try {
854
                $this->setConfigData($key, $value);
×
855
            } catch (Exception $e) {
×
856
                throw new DeepExitException($e->getMessage().PHP_EOL, ExitCode::PROCESS_ERROR);
×
857
            }
858

859
            $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
860

861
            if ($current === null) {
×
862
                $output .= "Config value \"$key\" added successfully".PHP_EOL;
×
863
            } else {
864
                $output .= "Config value \"$key\" updated successfully; old value was \"$current\"".PHP_EOL;
×
865
            }
866
            throw new DeepExitException($output, ExitCode::OKAY);
×
867
        case 'config-delete':
189✔
868
            if (isset($this->cliArgs[($pos + 1)]) === false) {
×
869
                $error  = 'ERROR: Deleting a config option requires the name of the option'.PHP_EOL.PHP_EOL;
×
870
                $error .= $this->printShortUsage(true);
×
871
                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
872
            }
873

874
            $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
875

876
            $key     = $this->cliArgs[($pos + 1)];
×
877
            $current = self::getConfigData($key);
×
878
            if ($current === null) {
×
879
                $output .= "Config value \"$key\" has not been set".PHP_EOL;
×
880
            } else {
881
                try {
882
                    $this->setConfigData($key, null);
×
883
                } catch (Exception $e) {
×
884
                    throw new DeepExitException($e->getMessage().PHP_EOL, ExitCode::PROCESS_ERROR);
×
885
                }
886

887
                $output .= "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL;
×
888
            }
889
            throw new DeepExitException($output, ExitCode::OKAY);
×
890
        case 'config-show':
189✔
891
            $data    = self::getAllConfigData();
×
892
            $output  = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
893
            $output .= $this->prepareConfigDataForDisplay($data);
×
894
            throw new DeepExitException($output, ExitCode::OKAY);
×
895
        case 'runtime-set':
189✔
896
            if (isset($this->cliArgs[($pos + 1)]) === false
×
897
                || isset($this->cliArgs[($pos + 2)]) === false
×
898
            ) {
899
                $error  = 'ERROR: Setting a runtime config option requires a name and value'.PHP_EOL.PHP_EOL;
×
900
                $error .= $this->printShortUsage(true);
×
901
                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
902
            }
903

904
            $key   = $this->cliArgs[($pos + 1)];
×
905
            $value = $this->cliArgs[($pos + 2)];
×
906
            $this->cliArgs[($pos + 1)] = '';
×
907
            $this->cliArgs[($pos + 2)] = '';
×
908
            $this->setConfigData($key, $value, true);
×
909
            if (isset($this->overriddenDefaults['runtime-set']) === false) {
×
910
                $this->overriddenDefaults['runtime-set'] = [];
×
911
            }
912

913
            $this->overriddenDefaults['runtime-set'][$key] = true;
×
914
            break;
×
915
        default:
916
            if (substr($arg, 0, 7) === 'sniffs=') {
189✔
917
                if (isset($this->overriddenDefaults['sniffs']) === true) {
57✔
918
                    break;
3✔
919
                }
920

921
                $this->sniffs = $this->parseSniffCodes(substr($arg, 7), 'sniffs');
57✔
922
                $this->overriddenDefaults['sniffs'] = true;
21✔
923
            } else if (substr($arg, 0, 8) === 'exclude=') {
132✔
924
                if (isset($this->overriddenDefaults['exclude']) === true) {
57✔
925
                    break;
3✔
926
                }
927

928
                $this->exclude = $this->parseSniffCodes(substr($arg, 8), 'exclude');
57✔
929
                $this->overriddenDefaults['exclude'] = true;
21✔
930
            } else if (substr($arg, 0, 6) === 'cache=') {
75✔
931
                if ((isset($this->overriddenDefaults['cache']) === true
×
932
                    && $this->cache === false)
×
933
                    || isset($this->overriddenDefaults['cacheFile']) === true
×
934
                ) {
935
                    break;
×
936
                }
937

938
                // Turn caching on.
939
                $this->cache = true;
×
940
                $this->overriddenDefaults['cache'] = true;
×
941

942
                $this->cacheFile = Common::realpath(substr($arg, 6));
×
943

944
                // It may not exist and return false instead.
945
                if ($this->cacheFile === false) {
×
946
                    $this->cacheFile = substr($arg, 6);
×
947

948
                    $dir = dirname($this->cacheFile);
×
949
                    if (is_dir($dir) === false) {
×
950
                        $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
951
                        $error .= $this->printShortUsage(true);
×
952
                        throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
953
                    }
954

955
                    if ($dir === '.') {
×
956
                        // Passed cache file is a file in the current directory.
957
                        $this->cacheFile = getcwd().'/'.basename($this->cacheFile);
×
958
                    } else {
959
                        if ($dir[0] === '/') {
×
960
                            // An absolute path.
961
                            $dir = Common::realpath($dir);
×
962
                        } else {
963
                            $dir = Common::realpath(getcwd().'/'.$dir);
×
964
                        }
965

966
                        if ($dir !== false) {
×
967
                            // Cache file path is relative.
968
                            $this->cacheFile = $dir.'/'.basename($this->cacheFile);
×
969
                        }
970
                    }
971
                }//end if
972

973
                $this->overriddenDefaults['cacheFile'] = true;
×
974

975
                if (is_dir($this->cacheFile) === true) {
×
976
                    $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" is a directory'.PHP_EOL.PHP_EOL;
×
977
                    $error .= $this->printShortUsage(true);
×
978
                    throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
979
                }
980
            } else if (substr($arg, 0, 10) === 'bootstrap=') {
75✔
981
                $files     = explode(',', substr($arg, 10));
×
982
                $bootstrap = [];
×
983
                foreach ($files as $file) {
×
984
                    $path = Common::realpath($file);
×
985
                    if ($path === false) {
×
986
                        $error  = 'ERROR: The specified bootstrap file "'.$file.'" does not exist'.PHP_EOL.PHP_EOL;
×
987
                        $error .= $this->printShortUsage(true);
×
988
                        throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
989
                    }
990

991
                    $bootstrap[] = $path;
×
992
                }
993

994
                $this->bootstrap = array_merge($this->bootstrap, $bootstrap);
×
995
                $this->overriddenDefaults['bootstrap'] = true;
×
996
            } else if (substr($arg, 0, 10) === 'file-list=') {
75✔
997
                $fileList = substr($arg, 10);
×
998
                $path     = Common::realpath($fileList);
×
999
                if ($path === false) {
×
1000
                    $error  = 'ERROR: The specified file list "'.$fileList.'" does not exist'.PHP_EOL.PHP_EOL;
×
1001
                    $error .= $this->printShortUsage(true);
×
1002
                    throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1003
                }
1004

1005
                $files = file($path);
×
1006
                foreach ($files as $inputFile) {
×
1007
                    $inputFile = trim($inputFile);
×
1008

1009
                    // Skip empty lines.
1010
                    if ($inputFile === '') {
×
1011
                        continue;
×
1012
                    }
1013

1014
                    $this->processFilePath($inputFile);
×
1015
                }
1016
            } else if (substr($arg, 0, 11) === 'stdin-path=') {
75✔
1017
                if (isset($this->overriddenDefaults['stdinPath']) === true) {
×
1018
                    break;
×
1019
                }
1020

1021
                $this->stdinPath = Common::realpath(substr($arg, 11));
×
1022

1023
                // It may not exist and return false instead, so use whatever they gave us.
1024
                if ($this->stdinPath === false) {
×
1025
                    $this->stdinPath = trim(substr($arg, 11));
×
1026
                }
1027

1028
                $this->overriddenDefaults['stdinPath'] = true;
×
1029
            } else if (substr($arg, 0, 12) === 'report-file=') {
75✔
1030
                if (PHP_CODESNIFFER_CBF === true || isset($this->overriddenDefaults['reportFile']) === true) {
6✔
1031
                    break;
3✔
1032
                }
1033

1034
                $this->reportFile = Common::realpath(substr($arg, 12));
3✔
1035

1036
                // It may not exist and return false instead.
1037
                if ($this->reportFile === false) {
3✔
1038
                    $this->reportFile = substr($arg, 12);
3✔
1039

1040
                    $dir = Common::realpath(dirname($this->reportFile));
3✔
1041
                    if (is_dir($dir) === false) {
3✔
1042
                        $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1043
                        $error .= $this->printShortUsage(true);
×
1044
                        throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1045
                    }
1046

1047
                    $this->reportFile = $dir.'/'.basename($this->reportFile);
3✔
1048
                }//end if
1049

1050
                $this->overriddenDefaults['reportFile'] = true;
3✔
1051

1052
                if (is_dir($this->reportFile) === true) {
3✔
1053
                    $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" is a directory'.PHP_EOL.PHP_EOL;
×
1054
                    $error .= $this->printShortUsage(true);
×
1055
                    throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
1✔
1056
                }
1057
            } else if (substr($arg, 0, 13) === 'report-width=') {
69✔
1058
                if (isset($this->overriddenDefaults['reportWidth']) === true) {
9✔
1059
                    break;
3✔
1060
                }
1061

1062
                $this->reportWidth = substr($arg, 13);
9✔
1063
                $this->overriddenDefaults['reportWidth'] = true;
9✔
1064
            } else if (substr($arg, 0, 9) === 'basepath=') {
69✔
1065
                if (isset($this->overriddenDefaults['basepath']) === true) {
×
1066
                    break;
×
1067
                }
1068

1069
                $this->overriddenDefaults['basepath'] = true;
×
1070

1071
                if (substr($arg, 9) === '') {
×
1072
                    $this->basepath = null;
×
1073
                    break;
×
1074
                }
1075

1076
                $this->basepath = Common::realpath(substr($arg, 9));
×
1077

1078
                // It may not exist and return false instead.
1079
                if ($this->basepath === false) {
×
1080
                    $this->basepath = substr($arg, 9);
×
1081
                }
1082

1083
                if (is_dir($this->basepath) === false) {
×
1084
                    $error  = 'ERROR: The specified basepath "'.$this->basepath.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1085
                    $error .= $this->printShortUsage(true);
×
1086
                    throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1087
                }
1088
            } else if ((substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-')) {
69✔
1089
                $reports = [];
×
1090

1091
                if ($arg[6] === '-') {
×
1092
                    // This is a report with file output.
1093
                    $split = strpos($arg, '=');
×
1094
                    if ($split === false) {
×
1095
                        $report = substr($arg, 7);
×
1096
                        $output = null;
×
1097
                    } else {
1098
                        $report = substr($arg, 7, ($split - 7));
×
1099
                        $output = substr($arg, ($split + 1));
×
1100
                        if ($output === false) {
×
1101
                            $output = null;
×
1102
                        } else {
1103
                            $dir = Common::realpath(dirname($output));
×
1104
                            if (is_dir($dir) === false) {
×
1105
                                $error  = 'ERROR: The specified '.$report.' report file path "'.$output.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1106
                                $error .= $this->printShortUsage(true);
×
1107
                                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1108
                            }
1109

1110
                            $output = $dir.'/'.basename($output);
×
1111

1112
                            if (is_dir($output) === true) {
×
1113
                                $error  = 'ERROR: The specified '.$report.' report file path "'.$output.'" is a directory'.PHP_EOL.PHP_EOL;
×
1114
                                $error .= $this->printShortUsage(true);
×
1115
                                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1116
                            }
1117
                        }//end if
1118
                    }//end if
1119

1120
                    $reports[$report] = $output;
×
1121
                } else {
1122
                    // This is a single report.
1123
                    if (isset($this->overriddenDefaults['reports']) === true) {
×
1124
                        break;
×
1125
                    }
1126

1127
                    $reportNames = explode(',', substr($arg, 7));
×
1128
                    foreach ($reportNames as $report) {
×
1129
                        $reports[$report] = null;
×
1130
                    }
1131
                }//end if
1132

1133
                // Remove the default value so the CLI value overrides it.
1134
                if (isset($this->overriddenDefaults['reports']) === false) {
×
1135
                    $this->reports = $reports;
×
1136
                } else {
1137
                    $this->reports = array_merge($this->reports, $reports);
×
1138
                }
1139

1140
                $this->overriddenDefaults['reports'] = true;
×
1141
            } else if (substr($arg, 0, 7) === 'filter=') {
69✔
1142
                if (isset($this->overriddenDefaults['filter']) === true) {
×
1143
                    break;
×
1144
                }
1145

1146
                $this->filter = substr($arg, 7);
×
1147
                $this->overriddenDefaults['filter'] = true;
×
1148
            } else if (substr($arg, 0, 9) === 'standard=') {
69✔
1149
                $standards = trim(substr($arg, 9));
9✔
1150
                if ($standards !== '') {
9✔
1151
                    $this->standards = explode(',', $standards);
9✔
1152
                }
1153

1154
                $this->overriddenDefaults['standards'] = true;
9✔
1155
            } else if (substr($arg, 0, 11) === 'extensions=') {
60✔
1156
                if (isset($this->overriddenDefaults['extensions']) === true) {
30✔
1157
                    break;
3✔
1158
                }
1159

1160
                $extensionsString = substr($arg, 11);
30✔
1161
                $newExtensions    = [];
30✔
1162
                if (empty($extensionsString) === false) {
30✔
1163
                    $extensions = explode(',', $extensionsString);
27✔
1164
                    foreach ($extensions as $ext) {
27✔
1165
                        if (strpos($ext, '/') !== false) {
27✔
1166
                            // They specified the tokenizer too.
1167
                            list($ext, $tokenizer) = explode('/', $ext);
12✔
1168
                            if (strtoupper($tokenizer) !== 'PHP') {
12✔
1169
                                $error  = 'ERROR: Specifying the tokenizer to use for an extension is no longer supported.'.PHP_EOL;
9✔
1170
                                $error .= 'PHP_CodeSniffer >= 4.0 only supports scanning PHP files.'.PHP_EOL;
9✔
1171
                                $error .= 'Received: '.substr($arg, 11).PHP_EOL.PHP_EOL;
9✔
1172
                                $error .= $this->printShortUsage(true);
9✔
1173
                                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
9✔
1174
                            }
1175
                        }
1176

1177
                        $newExtensions[$ext] = 'PHP';
21✔
1178
                    }
1179
                }
1180

1181
                $this->extensions = $newExtensions;
21✔
1182
                $this->overriddenDefaults['extensions'] = true;
21✔
1183
            } else if (substr($arg, 0, 7) === 'suffix=') {
30✔
1184
                if (isset($this->overriddenDefaults['suffix']) === true) {
×
1185
                    break;
×
1186
                }
1187

1188
                $this->suffix = substr($arg, 7);
×
1189
                $this->overriddenDefaults['suffix'] = true;
×
1190
            } else if (substr($arg, 0, 9) === 'parallel=') {
30✔
1191
                if (isset($this->overriddenDefaults['parallel']) === true) {
×
1192
                    break;
×
1193
                }
1194

1195
                $this->parallel = max((int) substr($arg, 9), 1);
×
1196
                $this->overriddenDefaults['parallel'] = true;
×
1197
            } else if (substr($arg, 0, 9) === 'severity=') {
30✔
1198
                $this->errorSeverity   = (int) substr($arg, 9);
×
1199
                $this->warningSeverity = $this->errorSeverity;
×
1200
                if (isset($this->overriddenDefaults['errorSeverity']) === false) {
×
1201
                    $this->overriddenDefaults['errorSeverity'] = true;
×
1202
                }
1203

1204
                if (isset($this->overriddenDefaults['warningSeverity']) === false) {
×
1205
                    $this->overriddenDefaults['warningSeverity'] = true;
×
1206
                }
1207
            } else if (substr($arg, 0, 15) === 'error-severity=') {
30✔
1208
                if (isset($this->overriddenDefaults['errorSeverity']) === true) {
×
1209
                    break;
×
1210
                }
1211

1212
                $this->errorSeverity = (int) substr($arg, 15);
×
1213
                $this->overriddenDefaults['errorSeverity'] = true;
×
1214
            } else if (substr($arg, 0, 17) === 'warning-severity=') {
30✔
1215
                if (isset($this->overriddenDefaults['warningSeverity']) === true) {
×
1216
                    break;
×
1217
                }
1218

1219
                $this->warningSeverity = (int) substr($arg, 17);
×
1220
                $this->overriddenDefaults['warningSeverity'] = true;
×
1221
            } else if (substr($arg, 0, 7) === 'ignore=') {
30✔
1222
                if (isset($this->overriddenDefaults['ignored']) === true) {
×
1223
                    break;
×
1224
                }
1225

1226
                // Split the ignore string on commas, unless the comma is escaped
1227
                // using 1 or 3 slashes (\, or \\\,).
1228
                $patterns = preg_split(
×
1229
                    '/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/',
×
1230
                    substr($arg, 7)
×
1231
                );
1232

1233
                $ignored = [];
×
1234
                foreach ($patterns as $pattern) {
×
1235
                    $pattern = trim($pattern);
×
1236
                    if ($pattern === '') {
×
1237
                        continue;
×
1238
                    }
1239

1240
                    $ignored[$pattern] = 'absolute';
×
1241
                }
1242

1243
                $this->ignored = $ignored;
×
1244
                $this->overriddenDefaults['ignored'] = true;
×
1245
            } else if (substr($arg, 0, 10) === 'generator='
30✔
1246
                && PHP_CODESNIFFER_CBF === false
30✔
1247
            ) {
1248
                if (isset($this->overriddenDefaults['generator']) === true) {
30✔
1249
                    break;
3✔
1250
                }
1251

1252
                $generatorName          = substr($arg, 10);
30✔
1253
                $lowerCaseGeneratorName = strtolower($generatorName);
30✔
1254

1255
                if (isset(self::VALID_GENERATORS[$lowerCaseGeneratorName]) === false) {
30✔
1256
                    $validOptions = implode(', ', self::VALID_GENERATORS);
9✔
1257
                    $validOptions = substr_replace($validOptions, ' and', strrpos($validOptions, ','), 1);
9✔
1258
                    $error        = sprintf(
9✔
1259
                        'ERROR: "%s" is not a valid generator. The following generators are supported: %s.'.PHP_EOL.PHP_EOL,
9✔
1260
                        $generatorName,
9✔
1261
                        $validOptions
9✔
1262
                    );
6✔
1263
                    $error       .= $this->printShortUsage(true);
9✔
1264
                    throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
9✔
1265
                }
1266

1267
                $this->generator = self::VALID_GENERATORS[$lowerCaseGeneratorName];
21✔
1268
                $this->overriddenDefaults['generator'] = true;
21✔
1269
            } else if (substr($arg, 0, 9) === 'encoding=') {
×
1270
                if (isset($this->overriddenDefaults['encoding']) === true) {
×
1271
                    break;
×
1272
                }
1273

1274
                $this->encoding = strtolower(substr($arg, 9));
×
1275
                $this->overriddenDefaults['encoding'] = true;
×
1276
            } else if (substr($arg, 0, 10) === 'tab-width=') {
×
1277
                if (isset($this->overriddenDefaults['tabWidth']) === true) {
×
1278
                    break;
×
1279
                }
1280

1281
                $this->tabWidth = (int) substr($arg, 10);
×
1282
                $this->overriddenDefaults['tabWidth'] = true;
×
1283
            } else {
1284
                if ($this->dieOnUnknownArg === false) {
×
1285
                    $eqPos = strpos($arg, '=');
×
1286
                    try {
1287
                        $unknown = $this->unknown;
×
1288

1289
                        if ($eqPos === false) {
×
1290
                            $unknown[$arg] = $arg;
×
1291
                        } else {
1292
                            $value         = substr($arg, ($eqPos + 1));
×
1293
                            $arg           = substr($arg, 0, $eqPos);
×
1294
                            $unknown[$arg] = $value;
×
1295
                        }
1296

1297
                        $this->unknown = $unknown;
×
1298
                    } catch (RuntimeException $e) {
×
1299
                        // Value is not valid, so just ignore it.
1300
                    }
1301
                } else {
1302
                    $this->processUnknownArgument('--'.$arg, $pos);
×
1303
                }
1304
            }//end if
1305
            break;
96✔
1306
        }//end switch
1307

1308
    }//end processLongArgument()
33✔
1309

1310

1311
    /**
1312
     * Parse supplied string into a list of validated sniff codes.
1313
     *
1314
     * @param string $input    Comma-separated string of sniff codes.
1315
     * @param string $argument The name of the argument which is being processed.
1316
     *
1317
     * @return array<string>
1318
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException When any of the provided codes are not valid as sniff codes.
1319
     */
1320
    private function parseSniffCodes($input, $argument)
114✔
1321
    {
1322
        $errors = [];
114✔
1323
        $sniffs = [];
114✔
1324

1325
        $possibleSniffs = array_filter(explode(',', $input));
114✔
1326

1327
        if ($possibleSniffs === []) {
114✔
1328
            $errors[] = 'No codes specified / empty argument';
18✔
1329
        }
1330

1331
        foreach ($possibleSniffs as $sniff) {
114✔
1332
            $sniff = trim($sniff);
96✔
1333

1334
            $partCount = substr_count($sniff, '.');
96✔
1335
            if ($partCount === 2) {
96✔
1336
                // Correct number of parts.
1337
                $sniffs[] = $sniff;
54✔
1338
                continue;
54✔
1339
            }
1340

1341
            if ($partCount === 0) {
54✔
1342
                $errors[] = 'Standard codes are not supported: '.$sniff;
12✔
1343
            } else if ($partCount === 1) {
42✔
1344
                $errors[] = 'Category codes are not supported: '.$sniff;
18✔
1345
            } else if ($partCount === 3) {
24✔
1346
                $errors[] = 'Message codes are not supported: '.$sniff;
18✔
1347
            } else {
1348
                $errors[] = 'Too many parts: '.$sniff;
12✔
1349
            }
1350

1351
            if ($partCount > 2) {
54✔
1352
                $parts    = explode('.', $sniff, 4);
24✔
1353
                $sniffs[] = $parts[0].'.'.$parts[1].'.'.$parts[2];
24✔
1354
            }
1355
        }//end foreach
1356

1357
        $sniffs = array_reduce(
114✔
1358
            $sniffs,
114✔
1359
            static function ($carry, $item) {
76✔
1360
                $lower = strtolower($item);
78✔
1361

1362
                foreach ($carry as $found) {
78✔
1363
                    if ($lower === strtolower($found)) {
36✔
1364
                        // This sniff is already in our list.
1365
                        return $carry;
24✔
1366
                    }
1367
                }
1368

1369
                $carry[] = $item;
78✔
1370

1371
                return $carry;
78✔
1372
            },
114✔
1373
            []
114✔
1374
        );
76✔
1375

1376
        if ($errors !== []) {
114✔
1377
            $error  = 'ERROR: The --'.$argument.' option only supports sniff codes.'.PHP_EOL;
72✔
1378
            $error .= 'Sniff codes are in the form "Standard.Category.Sniff".'.PHP_EOL;
72✔
1379
            $error .= PHP_EOL;
72✔
1380
            $error .= 'The following problems were detected:'.PHP_EOL;
72✔
1381
            $error .= '* '.implode(PHP_EOL.'* ', $errors).PHP_EOL;
72✔
1382

1383
            if ($sniffs !== []) {
72✔
1384
                $error .= PHP_EOL;
36✔
1385
                $error .= 'Perhaps try --'.$argument.'="'.implode(',', $sniffs).'" instead.'.PHP_EOL;
36✔
1386
            }
1387

1388
            $error .= PHP_EOL;
72✔
1389
            $error .= $this->printShortUsage(true);
72✔
1390
            throw new DeepExitException(ltrim($error), ExitCode::PROCESS_ERROR);
72✔
1391
        }
1392

1393
        return $sniffs;
42✔
1394

1395
    }//end parseSniffCodes()
1396

1397

1398
    /**
1399
     * Processes an unknown command line argument.
1400
     *
1401
     * Assumes all unknown arguments are files and folders to check.
1402
     *
1403
     * @param string $arg The command line argument.
1404
     * @param int    $pos The position of the argument on the command line.
1405
     *
1406
     * @return void
1407
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
1408
     */
1409
    public function processUnknownArgument($arg, $pos)
×
1410
    {
1411
        // We don't know about any additional switches; just files.
1412
        if ($arg[0] === '-') {
×
1413
            if ($this->dieOnUnknownArg === false) {
×
1414
                return;
×
1415
            }
1416

1417
            $error  = "ERROR: option \"$arg\" not known".PHP_EOL.PHP_EOL;
×
1418
            $error .= $this->printShortUsage(true);
×
1419
            throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1420
        }
1421

1422
        $this->processFilePath($arg);
×
1423

1424
    }//end processUnknownArgument()
1425

1426

1427
    /**
1428
     * Processes a file path and add it to the file list.
1429
     *
1430
     * @param string $path The path to the file to add.
1431
     *
1432
     * @return void
1433
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
1434
     */
1435
    public function processFilePath($path)
×
1436
    {
1437
        // If we are processing STDIN, don't record any files to check.
1438
        if ($this->stdin === true) {
×
1439
            return;
×
1440
        }
1441

1442
        $file = Common::realpath($path);
×
1443
        if (file_exists($file) === false) {
×
1444
            if ($this->dieOnUnknownArg === false) {
×
1445
                return;
×
1446
            }
1447

1448
            $error  = 'ERROR: The file "'.$path.'" does not exist.'.PHP_EOL.PHP_EOL;
×
1449
            $error .= $this->printShortUsage(true);
×
1450
            throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1451
        } else {
1452
            // Can't modify the files array directly because it's not a real
1453
            // class member, so need to use this little get/modify/set trick.
1454
            $files       = $this->files;
×
1455
            $files[]     = $file;
×
1456
            $this->files = $files;
×
1457
            $this->overriddenDefaults['files'] = true;
×
1458
        }
1459

1460
    }//end processFilePath()
1461

1462

1463
    /**
1464
     * Prints out the usage information for this script.
1465
     *
1466
     * @return void
1467
     */
1468
    public function printUsage()
×
1469
    {
1470
        echo PHP_EOL;
×
1471

1472
        if (PHP_CODESNIFFER_CBF === true) {
×
1473
            $this->printPHPCBFUsage();
×
1474
        } else {
1475
            $this->printPHPCSUsage();
×
1476
        }
1477

1478
        echo PHP_EOL;
×
1479

1480
    }//end printUsage()
1481

1482

1483
    /**
1484
     * Prints out the short usage information for this script.
1485
     *
1486
     * @param bool $return If TRUE, the usage string is returned
1487
     *                     instead of output to screen.
1488
     *
1489
     * @return string|void
1490
     */
1491
    public function printShortUsage($return=false)
×
1492
    {
1493
        if (PHP_CODESNIFFER_CBF === true) {
×
1494
            $usage = 'Run "phpcbf --help" for usage information';
×
1495
        } else {
1496
            $usage = 'Run "phpcs --help" for usage information';
×
1497
        }
1498

1499
        $usage .= PHP_EOL.PHP_EOL;
×
1500

1501
        if ($return === true) {
×
1502
            return $usage;
×
1503
        }
1504

1505
        echo $usage;
×
1506

1507
    }//end printShortUsage()
1508

1509

1510
    /**
1511
     * Prints out the usage information for PHPCS.
1512
     *
1513
     * @return void
1514
     */
1515
    public function printPHPCSUsage()
×
1516
    {
1517
        $longOptions   = explode(',', Help::DEFAULT_LONG_OPTIONS);
×
1518
        $longOptions[] = 'cache';
×
1519
        $longOptions[] = 'no-cache';
×
1520
        $longOptions[] = 'report';
×
1521
        $longOptions[] = 'report-file';
×
1522
        $longOptions[] = 'report-report';
×
1523
        $longOptions[] = 'config-explain';
×
1524
        $longOptions[] = 'config-set';
×
1525
        $longOptions[] = 'config-delete';
×
1526
        $longOptions[] = 'config-show';
×
1527
        $longOptions[] = 'generator';
×
1528

1529
        $shortOptions = Help::DEFAULT_SHORT_OPTIONS.'aems';
×
1530

1531
        (new Help($this, $longOptions, $shortOptions))->display();
×
1532

1533
    }//end printPHPCSUsage()
1534

1535

1536
    /**
1537
     * Prints out the usage information for PHPCBF.
1538
     *
1539
     * @return void
1540
     */
1541
    public function printPHPCBFUsage()
×
1542
    {
1543
        $longOptions   = explode(',', Help::DEFAULT_LONG_OPTIONS);
×
1544
        $longOptions[] = 'suffix';
×
1545
        $shortOptions  = Help::DEFAULT_SHORT_OPTIONS;
×
1546

1547
        (new Help($this, $longOptions, $shortOptions))->display();
×
1548

1549
    }//end printPHPCBFUsage()
1550

1551

1552
    /**
1553
     * Get a single config value.
1554
     *
1555
     * @param string $key The name of the config value.
1556
     *
1557
     * @return string|null
1558
     * @see    setConfigData()
1559
     * @see    getAllConfigData()
1560
     */
1561
    public static function getConfigData($key)
6✔
1562
    {
1563
        $phpCodeSnifferConfig = self::getAllConfigData();
6✔
1564

1565
        if ($phpCodeSnifferConfig === null) {
6✔
1566
            return null;
×
1567
        }
1568

1569
        if (isset($phpCodeSnifferConfig[$key]) === false) {
6✔
1570
            return null;
6✔
1571
        }
1572

1573
        return $phpCodeSnifferConfig[$key];
6✔
1574

1575
    }//end getConfigData()
1576

1577

1578
    /**
1579
     * Get the path to an executable utility.
1580
     *
1581
     * @param string $name The name of the executable utility.
1582
     *
1583
     * @return string|null
1584
     * @see    getConfigData()
1585
     */
1586
    public static function getExecutablePath($name)
×
1587
    {
1588
        $data = self::getConfigData($name.'_path');
×
1589
        if ($data !== null) {
×
1590
            return $data;
×
1591
        }
1592

1593
        if ($name === "php") {
×
1594
            // For php, we know the executable path. There's no need to look it up.
1595
            return PHP_BINARY;
×
1596
        }
1597

1598
        if (array_key_exists($name, self::$executablePaths) === true) {
×
1599
            return self::$executablePaths[$name];
×
1600
        }
1601

1602
        if (PHP_OS_FAMILY === 'Windows') {
×
1603
            $cmd = 'where '.escapeshellarg($name).' 2> nul';
×
1604
        } else {
1605
            $cmd = 'which '.escapeshellarg($name).' 2> /dev/null';
×
1606
        }
1607

1608
        $result = exec($cmd, $output, $retVal);
×
1609
        if ($retVal !== 0) {
×
1610
            $result = null;
×
1611
        }
1612

1613
        self::$executablePaths[$name] = $result;
×
1614
        return $result;
×
1615

1616
    }//end getExecutablePath()
1617

1618

1619
    /**
1620
     * Set a single config value.
1621
     *
1622
     * @param string      $key   The name of the config value.
1623
     * @param string|null $value The value to set. If null, the config
1624
     *                           entry is deleted, reverting it to the
1625
     *                           default value.
1626
     * @param boolean     $temp  Set this config data temporarily for this
1627
     *                           script run. This will not write the config
1628
     *                           data to the config file.
1629
     *
1630
     * @return bool
1631
     * @see    getConfigData()
1632
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the config file can not be written.
1633
     */
1634
    public function setConfigData($key, $value, $temp=false)
×
1635
    {
1636
        if (isset($this->overriddenDefaults['runtime-set']) === true
×
1637
            && isset($this->overriddenDefaults['runtime-set'][$key]) === true
×
1638
        ) {
1639
            return false;
×
1640
        }
1641

1642
        if ($temp === false) {
×
1643
            $path = '';
×
1644
            if (is_callable('\Phar::running') === true) {
×
1645
                $path = Phar::running(false);
×
1646
            }
1647

1648
            if ($path !== '') {
×
1649
                $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1650
            } else {
1651
                $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1652
            }
1653

1654
            if (is_file($configFile) === true
×
1655
                && is_writable($configFile) === false
×
1656
            ) {
1657
                $error = 'ERROR: Config file '.$configFile.' is not writable'.PHP_EOL.PHP_EOL;
×
1658
                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1659
            }
1660
        }//end if
1661

1662
        $phpCodeSnifferConfig = self::getAllConfigData();
×
1663

1664
        if ($value === null) {
×
1665
            if (isset($phpCodeSnifferConfig[$key]) === true) {
×
1666
                unset($phpCodeSnifferConfig[$key]);
×
1667
            }
1668
        } else {
1669
            $phpCodeSnifferConfig[$key] = $value;
×
1670
        }
1671

1672
        if ($temp === false) {
×
1673
            $output  = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
×
1674
            $output .= var_export($phpCodeSnifferConfig, true);
×
1675
            $output .= ";\n?".'>';
×
1676

1677
            if (file_put_contents($configFile, $output) === false) {
×
1678
                $error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL;
×
1679
                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1680
            }
1681

1682
            self::$configDataFile = $configFile;
×
1683
        }
1684

1685
        self::$configData = $phpCodeSnifferConfig;
×
1686

1687
        // If the installed paths are being set, make sure all known
1688
        // standards paths are added to the autoloader.
1689
        if ($key === 'installed_paths') {
×
1690
            $installedStandards = Standards::getInstalledStandardDetails();
×
1691
            foreach ($installedStandards as $details) {
×
1692
                Autoload::addSearchPath($details['path'], $details['namespace']);
×
1693
            }
1694
        }
1695

1696
        return true;
×
1697

1698
    }//end setConfigData()
1699

1700

1701
    /**
1702
     * Get all config data.
1703
     *
1704
     * @return array<string, string>
1705
     * @see    getConfigData()
1706
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the config file could not be read.
1707
     */
1708
    public static function getAllConfigData()
×
1709
    {
1710
        if (self::$configData !== null) {
×
1711
            return self::$configData;
×
1712
        }
1713

1714
        $path = '';
×
1715
        if (is_callable('\Phar::running') === true) {
×
1716
            $path = Phar::running(false);
×
1717
        }
1718

1719
        if ($path !== '') {
×
1720
            $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1721
        } else {
1722
            $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1723
        }
1724

1725
        if (is_file($configFile) === false) {
×
1726
            self::$configData = [];
×
1727
            return [];
×
1728
        }
1729

1730
        if (Common::isReadable($configFile) === false) {
×
1731
            $error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL;
×
1732
            throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1733
        }
1734

1735
        include $configFile;
×
1736
        self::$configDataFile = $configFile;
×
1737
        self::$configData     = $phpCodeSnifferConfig;
×
1738
        return self::$configData;
×
1739

1740
    }//end getAllConfigData()
1741

1742

1743
    /**
1744
     * Prepares the gathered config data for display.
1745
     *
1746
     * @param array<string, string> $data The config data to format for display.
1747
     *
1748
     * @return string
1749
     */
1750
    public function prepareConfigDataForDisplay($data)
18✔
1751
    {
1752
        if (empty($data) === true) {
18✔
1753
            return '';
6✔
1754
        }
1755

1756
        $max  = 0;
12✔
1757
        $keys = array_keys($data);
12✔
1758
        foreach ($keys as $key) {
12✔
1759
            $len = strlen($key);
12✔
1760
            if ($len > $max) {
12✔
1761
                $max = $len;
12✔
1762
            }
1763
        }
1764

1765
        $max += 2;
12✔
1766
        ksort($data);
12✔
1767

1768
        $output = '';
12✔
1769
        foreach ($data as $name => $value) {
12✔
1770
            $output .= str_pad($name.': ', $max).$value.PHP_EOL;
12✔
1771
        }
1772

1773
        return $output;
12✔
1774

1775
    }//end prepareConfigDataForDisplay()
1776

1777

1778
    /**
1779
     * Prints out the gathered config data.
1780
     *
1781
     * @param array<string, string> $data The config data to print.
1782
     *
1783
     * @deprecated 4.0.0 Use `echo Config::prepareConfigDataForDisplay()` instead.
1784
     *
1785
     * @return void
1786
     */
1787
    public function printConfigData($data)
3✔
1788
    {
1789
        echo $this->prepareConfigDataForDisplay($data);
3✔
1790

1791
    }//end printConfigData()
1✔
1792

1793

1794
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc