• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

PHPCSStandards / PHP_CodeSniffer / 14732870361

29 Apr 2025 01:46PM UTC coverage: 78.466% (+0.07%) from 78.4%
14732870361

push

github

web-flow
Merge pull request #1055 from PHPCSStandards/phpcs-4.0/feature/remove-more-output-buffering-from-config-1

Config: introduce `prepareConfigDataForDisplay()` method

9 of 12 new or added lines in 1 file covered. (75.0%)

1 existing line in 1 file now uncovered.

19564 of 24933 relevant lines covered (78.47%)

86.48 hits per line

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

36.2
/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\Help;
21
use PHP_CodeSniffer\Util\Standards;
22

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

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

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

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

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

124
    /**
125
     * An array of settings that PHPCS and PHPCBF accept.
126
     *
127
     * This array is not meant to be accessed directly. Instead, use the settings
128
     * as if they are class member vars so the __get() and __set() magic methods
129
     * can be used to validate the values. For example, to set the verbosity level to
130
     * level 2, use $this->verbosity = 2; instead of accessing this property directly.
131
     *
132
     * Each of these settings is described in the class comment property list.
133
     *
134
     * @var array<string, mixed>
135
     */
136
    private $settings = [
137
        'files'           => null,
138
        'standards'       => null,
139
        'verbosity'       => null,
140
        'interactive'     => null,
141
        'parallel'        => null,
142
        'cache'           => null,
143
        'cacheFile'       => null,
144
        'colors'          => null,
145
        'explain'         => null,
146
        'local'           => null,
147
        'showSources'     => null,
148
        'showProgress'    => null,
149
        'quiet'           => null,
150
        'annotations'     => null,
151
        'tabWidth'        => null,
152
        'encoding'        => null,
153
        'extensions'      => null,
154
        'sniffs'          => null,
155
        'exclude'         => null,
156
        'ignored'         => null,
157
        'reportFile'      => null,
158
        'generator'       => null,
159
        'filter'          => null,
160
        'bootstrap'       => null,
161
        'reports'         => null,
162
        'basepath'        => null,
163
        'reportWidth'     => null,
164
        'errorSeverity'   => null,
165
        'warningSeverity' => null,
166
        'recordErrors'    => null,
167
        'suffix'          => null,
168
        'stdin'           => null,
169
        'stdinContent'    => null,
170
        'stdinPath'       => null,
171
        'trackTime'       => null,
172
        'unknown'         => null,
173
    ];
174

175
    /**
176
     * Whether or not to kill the process when an unknown command line arg is found.
177
     *
178
     * If FALSE, arguments that are not command line options or file/directory paths
179
     * will be ignored and execution will continue. These values will be stored in
180
     * $this->unknown.
181
     *
182
     * @var boolean
183
     */
184
    public $dieOnUnknownArg;
185

186
    /**
187
     * The current command line arguments we are processing.
188
     *
189
     * @var string[]
190
     */
191
    private $cliArgs = [];
192

193
    /**
194
     * A list of valid generators.
195
     *
196
     * {@internal Once support for PHP < 5.6 is dropped, this property should be refactored into a
197
     * class constant.}
198
     *
199
     * @var array<string, string> Keys are the lowercase version of the generator name, while values
200
     *                            are the associated PHP generator class.
201
     */
202
    private $validGenerators = [
203
        'text'     => 'Text',
204
        'html'     => 'HTML',
205
        'markdown' => 'Markdown',
206
    ];
207

208
    /**
209
     * Command line values that the user has supplied directly.
210
     *
211
     * @var array<string, true|array<string, true>>
212
     */
213
    private $overriddenDefaults = [];
214

215
    /**
216
     * Config file data that has been loaded for the run.
217
     *
218
     * @var array<string, string>
219
     */
220
    private static $configData = null;
221

222
    /**
223
     * The full path to the config data file that has been loaded.
224
     *
225
     * @var string
226
     */
227
    private static $configDataFile = null;
228

229
    /**
230
     * Automatically discovered executable utility paths.
231
     *
232
     * @var array<string, string>
233
     */
234
    private static $executablePaths = [];
235

236

237
    /**
238
     * Get the value of an inaccessible property.
239
     *
240
     * @param string $name The name of the property.
241
     *
242
     * @return mixed
243
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
244
     */
245
    public function __get($name)
48✔
246
    {
247
        if (array_key_exists($name, $this->settings) === false) {
48✔
248
            throw new RuntimeException("ERROR: unable to get value of property \"$name\"");
×
249
        }
250

251
        // Figure out what the terminal width needs to be for "auto".
252
        if ($name === 'reportWidth' && $this->settings[$name] === 'auto') {
48✔
253
            if (function_exists('shell_exec') === true) {
9✔
254
                $dimensions = shell_exec('stty size 2>&1');
9✔
255
                if (is_string($dimensions) === true && preg_match('|\d+ (\d+)|', $dimensions, $matches) === 1) {
9✔
256
                    $this->settings[$name] = (int) $matches[1];
×
257
                }
258
            }
259

260
            if ($this->settings[$name] === 'auto') {
9✔
261
                // If shell_exec wasn't available or didn't yield a usable value, set to the default.
262
                // This will prevent subsequent retrievals of the reportWidth from making another call to stty.
263
                $this->settings[$name] = self::DEFAULT_REPORT_WIDTH;
9✔
264
            }
265
        }
266

267
        return $this->settings[$name];
48✔
268

269
    }//end __get()
270

271

272
    /**
273
     * Set the value of an inaccessible property.
274
     *
275
     * @param string $name  The name of the property.
276
     * @param mixed  $value The value of the property.
277
     *
278
     * @return void
279
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
280
     */
281
    public function __set($name, $value)
48✔
282
    {
283
        if (array_key_exists($name, $this->settings) === false) {
48✔
284
            throw new RuntimeException("Can't __set() $name; setting doesn't exist");
×
285
        }
286

287
        switch ($name) {
16✔
288
        case 'reportWidth' :
48✔
289
            if (is_string($value) === true && $value === 'auto') {
48✔
290
                // Nothing to do. Leave at 'auto'.
291
                break;
48✔
292
            }
293

294
            if (is_int($value) === true) {
39✔
295
                $value = abs($value);
6✔
296
            } else if (is_string($value) === true && preg_match('`^\d+$`', $value) === 1) {
33✔
297
                $value = (int) $value;
15✔
298
            } else {
299
                $value = self::DEFAULT_REPORT_WIDTH;
18✔
300
            }
301
            break;
39✔
302

303
        case 'standards' :
48✔
304
            $cleaned = [];
48✔
305

306
            // Check if the standard name is valid, or if the case is invalid.
307
            $installedStandards = Standards::getInstalledStandards();
48✔
308
            foreach ($value as $standard) {
48✔
309
                foreach ($installedStandards as $validStandard) {
48✔
310
                    if (strtolower($standard) === strtolower($validStandard)) {
48✔
311
                        $standard = $validStandard;
48✔
312
                        break;
48✔
313
                    }
314
                }
315

316
                $cleaned[] = $standard;
48✔
317
            }
318

319
            $value = $cleaned;
48✔
320
            break;
48✔
321

322
        // Only track time when explicitly needed.
323
        case 'verbosity':
48✔
324
            if ($value > 2) {
48✔
325
                $this->settings['trackTime'] = true;
×
326
            }
327
            break;
48✔
328
        case 'reports':
48✔
329
            $reports = array_change_key_case($value, CASE_LOWER);
48✔
330
            if (array_key_exists('performance', $reports) === true) {
48✔
331
                $this->settings['trackTime'] = true;
×
332
            }
333
            break;
48✔
334

335
        default :
336
            // No validation required.
337
            break;
48✔
338
        }//end switch
339

340
        $this->settings[$name] = $value;
48✔
341

342
    }//end __set()
16✔
343

344

345
    /**
346
     * Check if the value of an inaccessible property is set.
347
     *
348
     * @param string $name The name of the property.
349
     *
350
     * @return bool
351
     */
352
    public function __isset($name)
×
353
    {
354
        return isset($this->settings[$name]);
×
355

356
    }//end __isset()
357

358

359
    /**
360
     * Unset the value of an inaccessible property.
361
     *
362
     * @param string $name The name of the property.
363
     *
364
     * @return void
365
     */
366
    public function __unset($name)
×
367
    {
368
        $this->settings[$name] = null;
×
369

370
    }//end __unset()
371

372

373
    /**
374
     * Get the array of all config settings.
375
     *
376
     * @return array<string, mixed>
377
     */
378
    public function getSettings()
×
379
    {
380
        return $this->settings;
×
381

382
    }//end getSettings()
383

384

385
    /**
386
     * Set the array of all config settings.
387
     *
388
     * @param array<string, mixed> $settings The array of config settings.
389
     *
390
     * @return void
391
     */
392
    public function setSettings($settings)
×
393
    {
394
        $this->settings = $settings;
×
395

396
    }//end setSettings()
397

398

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

418
        if (empty($cliArgs) === true) {
×
419
            $cliArgs = $_SERVER['argv'];
×
420
            array_shift($cliArgs);
×
421
        }
422

423
        $this->restoreDefaults();
×
424
        $this->setCommandLineValues($cliArgs);
×
425

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

431
            $defaultFiles = [
432
                '.phpcs.xml',
×
433
                'phpcs.xml',
434
                '.phpcs.xml.dist',
435
                'phpcs.xml.dist',
436
            ];
437

438
            do {
439
                foreach ($defaultFiles as $defaultFilename) {
×
440
                    $default = $currentDir.DIRECTORY_SEPARATOR.$defaultFilename;
×
441
                    if (is_file($default) === true) {
×
442
                        $this->standards = [$default];
×
443
                        break(2);
×
444
                    }
445
                }
446

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

452
        if (defined('STDIN') === false
×
453
            || PHP_OS_FAMILY === 'Windows'
×
454
        ) {
455
            return;
×
456
        }
457

458
        $handle = fopen('php://stdin', 'r');
×
459

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

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

475
                $fileContents .= fgets($handle);
×
476
            }
477

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

486
        fclose($handle);
×
487

488
    }//end __construct()
489

490

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

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

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

517
                if ($arg === '--') {
×
518
                    // Empty argument, ignore it.
519
                    continue;
×
520
                }
521

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

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

539
    }//end setCommandLineValues()
540

541

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

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

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

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

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

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

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

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

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

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

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

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

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

653
        if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
9✔
654
            $cache = self::getConfigData('cache');
×
655
            if ($cache !== null) {
×
656
                $this->cache = (bool) $cache;
×
657
            }
658

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

665
    }//end restoreDefaults()
3✔
666

667

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

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

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

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

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

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

780
    }//end processShortArgument()
5✔
781

782

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

810
            $this->colors = true;
×
811
            $this->overriddenDefaults['colors'] = true;
×
812
            break;
×
813
        case 'no-colors':
183✔
814
            if (isset($this->overriddenDefaults['colors']) === true) {
×
815
                break;
×
816
            }
817

818
            $this->colors = false;
×
819
            $this->overriddenDefaults['colors'] = true;
×
820
            break;
×
821
        case 'cache':
183✔
822
            if (isset($this->overriddenDefaults['cache']) === true) {
×
823
                break;
×
824
            }
825

826
            if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
×
827
                $this->cache = true;
×
828
                $this->overriddenDefaults['cache'] = true;
×
829
            }
830
            break;
×
831
        case 'no-cache':
183✔
832
            if (isset($this->overriddenDefaults['cache']) === true) {
×
833
                break;
×
834
            }
835

836
            $this->cache = false;
×
837
            $this->overriddenDefaults['cache'] = true;
×
838
            break;
×
839
        case 'ignore-annotations':
183✔
840
            if (isset($this->overriddenDefaults['annotations']) === true) {
×
841
                break;
×
842
            }
843

844
            $this->annotations = false;
×
845
            $this->overriddenDefaults['annotations'] = true;
×
846
            break;
×
847
        case 'config-set':
183✔
848
            if (isset($this->cliArgs[($pos + 1)]) === false
×
849
                || isset($this->cliArgs[($pos + 2)]) === false
×
850
            ) {
851
                $error  = 'ERROR: Setting a config option requires a name and value'.PHP_EOL.PHP_EOL;
×
852
                $error .= $this->printShortUsage(true);
×
853
                throw new DeepExitException($error, 3);
×
854
            }
855

856
            $key     = $this->cliArgs[($pos + 1)];
×
857
            $value   = $this->cliArgs[($pos + 2)];
×
858
            $current = self::getConfigData($key);
×
859

860
            try {
861
                $this->setConfigData($key, $value);
×
862
            } catch (Exception $e) {
×
863
                throw new DeepExitException($e->getMessage().PHP_EOL, 3);
×
864
            }
865

866
            $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
867

868
            if ($current === null) {
×
869
                $output .= "Config value \"$key\" added successfully".PHP_EOL;
×
870
            } else {
871
                $output .= "Config value \"$key\" updated successfully; old value was \"$current\"".PHP_EOL;
×
872
            }
873
            throw new DeepExitException($output, 0);
×
874
        case 'config-delete':
183✔
875
            if (isset($this->cliArgs[($pos + 1)]) === false) {
×
876
                $error  = 'ERROR: Deleting a config option requires the name of the option'.PHP_EOL.PHP_EOL;
×
877
                $error .= $this->printShortUsage(true);
×
878
                throw new DeepExitException($error, 3);
×
879
            }
880

881
            $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
882

883
            $key     = $this->cliArgs[($pos + 1)];
×
884
            $current = self::getConfigData($key);
×
885
            if ($current === null) {
×
886
                $output .= "Config value \"$key\" has not been set".PHP_EOL;
×
887
            } else {
888
                try {
889
                    $this->setConfigData($key, null);
×
890
                } catch (Exception $e) {
×
891
                    throw new DeepExitException($e->getMessage().PHP_EOL, 3);
×
892
                }
893

894
                $output .= "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL;
×
895
            }
896
            throw new DeepExitException($output, 0);
×
897
        case 'config-show':
183✔
NEW
898
            $data    = self::getAllConfigData();
×
NEW
899
            $output  = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
NEW
900
            $output .= $this->prepareConfigDataForDisplay($data);
×
UNCOV
901
            throw new DeepExitException($output, 0);
×
902
        case 'runtime-set':
183✔
903
            if (isset($this->cliArgs[($pos + 1)]) === false
×
904
                || isset($this->cliArgs[($pos + 2)]) === false
×
905
            ) {
906
                $error  = 'ERROR: Setting a runtime config option requires a name and value'.PHP_EOL.PHP_EOL;
×
907
                $error .= $this->printShortUsage(true);
×
908
                throw new DeepExitException($error, 3);
×
909
            }
910

911
            $key   = $this->cliArgs[($pos + 1)];
×
912
            $value = $this->cliArgs[($pos + 2)];
×
913
            $this->cliArgs[($pos + 1)] = '';
×
914
            $this->cliArgs[($pos + 2)] = '';
×
915
            $this->setConfigData($key, $value, true);
×
916
            if (isset($this->overriddenDefaults['runtime-set']) === false) {
×
917
                $this->overriddenDefaults['runtime-set'] = [];
×
918
            }
919

920
            $this->overriddenDefaults['runtime-set'][$key] = true;
×
921
            break;
×
922
        default:
923
            if (substr($arg, 0, 7) === 'sniffs=') {
183✔
924
                if (isset($this->overriddenDefaults['sniffs']) === true) {
57✔
925
                    break;
3✔
926
                }
927

928
                $this->sniffs = $this->parseSniffCodes(substr($arg, 7), 'sniffs');
57✔
929
                $this->overriddenDefaults['sniffs'] = true;
21✔
930
            } else if (substr($arg, 0, 8) === 'exclude=') {
126✔
931
                if (isset($this->overriddenDefaults['exclude']) === true) {
57✔
932
                    break;
3✔
933
                }
934

935
                $this->exclude = $this->parseSniffCodes(substr($arg, 8), 'exclude');
57✔
936
                $this->overriddenDefaults['exclude'] = true;
21✔
937
            } else if (defined('PHP_CODESNIFFER_IN_TESTS') === false
69✔
938
                && substr($arg, 0, 6) === 'cache='
69✔
939
            ) {
940
                if ((isset($this->overriddenDefaults['cache']) === true
×
941
                    && $this->cache === false)
×
942
                    || isset($this->overriddenDefaults['cacheFile']) === true
×
943
                ) {
944
                    break;
×
945
                }
946

947
                // Turn caching on.
948
                $this->cache = true;
×
949
                $this->overriddenDefaults['cache'] = true;
×
950

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

953
                // It may not exist and return false instead.
954
                if ($this->cacheFile === false) {
×
955
                    $this->cacheFile = substr($arg, 6);
×
956

957
                    $dir = dirname($this->cacheFile);
×
958
                    if (is_dir($dir) === false) {
×
959
                        $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
960
                        $error .= $this->printShortUsage(true);
×
961
                        throw new DeepExitException($error, 3);
×
962
                    }
963

964
                    if ($dir === '.') {
×
965
                        // Passed cache file is a file in the current directory.
966
                        $this->cacheFile = getcwd().'/'.basename($this->cacheFile);
×
967
                    } else {
968
                        if ($dir[0] === '/') {
×
969
                            // An absolute path.
970
                            $dir = Common::realpath($dir);
×
971
                        } else {
972
                            $dir = Common::realpath(getcwd().'/'.$dir);
×
973
                        }
974

975
                        if ($dir !== false) {
×
976
                            // Cache file path is relative.
977
                            $this->cacheFile = $dir.'/'.basename($this->cacheFile);
×
978
                        }
979
                    }
980
                }//end if
981

982
                $this->overriddenDefaults['cacheFile'] = true;
×
983

984
                if (is_dir($this->cacheFile) === true) {
×
985
                    $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" is a directory'.PHP_EOL.PHP_EOL;
×
986
                    $error .= $this->printShortUsage(true);
×
987
                    throw new DeepExitException($error, 3);
×
988
                }
989
            } else if (substr($arg, 0, 10) === 'bootstrap=') {
69✔
990
                $files     = explode(',', substr($arg, 10));
×
991
                $bootstrap = [];
×
992
                foreach ($files as $file) {
×
993
                    $path = Common::realpath($file);
×
994
                    if ($path === false) {
×
995
                        $error  = 'ERROR: The specified bootstrap file "'.$file.'" does not exist'.PHP_EOL.PHP_EOL;
×
996
                        $error .= $this->printShortUsage(true);
×
997
                        throw new DeepExitException($error, 3);
×
998
                    }
999

1000
                    $bootstrap[] = $path;
×
1001
                }
1002

1003
                $this->bootstrap = array_merge($this->bootstrap, $bootstrap);
×
1004
                $this->overriddenDefaults['bootstrap'] = true;
×
1005
            } else if (substr($arg, 0, 10) === 'file-list=') {
69✔
1006
                $fileList = substr($arg, 10);
×
1007
                $path     = Common::realpath($fileList);
×
1008
                if ($path === false) {
×
1009
                    $error  = 'ERROR: The specified file list "'.$fileList.'" does not exist'.PHP_EOL.PHP_EOL;
×
1010
                    $error .= $this->printShortUsage(true);
×
1011
                    throw new DeepExitException($error, 3);
×
1012
                }
1013

1014
                $files = file($path);
×
1015
                foreach ($files as $inputFile) {
×
1016
                    $inputFile = trim($inputFile);
×
1017

1018
                    // Skip empty lines.
1019
                    if ($inputFile === '') {
×
1020
                        continue;
×
1021
                    }
1022

1023
                    $this->processFilePath($inputFile);
×
1024
                }
1025
            } else if (substr($arg, 0, 11) === 'stdin-path=') {
69✔
1026
                if (isset($this->overriddenDefaults['stdinPath']) === true) {
×
1027
                    break;
×
1028
                }
1029

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

1032
                // It may not exist and return false instead, so use whatever they gave us.
1033
                if ($this->stdinPath === false) {
×
1034
                    $this->stdinPath = trim(substr($arg, 11));
×
1035
                }
1036

1037
                $this->overriddenDefaults['stdinPath'] = true;
×
1038
            } else if (PHP_CODESNIFFER_CBF === false && substr($arg, 0, 12) === 'report-file=') {
69✔
1039
                if (isset($this->overriddenDefaults['reportFile']) === true) {
×
1040
                    break;
×
1041
                }
1042

1043
                $this->reportFile = Common::realpath(substr($arg, 12));
×
1044

1045
                // It may not exist and return false instead.
1046
                if ($this->reportFile === false) {
×
1047
                    $this->reportFile = substr($arg, 12);
×
1048

1049
                    $dir = Common::realpath(dirname($this->reportFile));
×
1050
                    if (is_dir($dir) === false) {
×
1051
                        $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1052
                        $error .= $this->printShortUsage(true);
×
1053
                        throw new DeepExitException($error, 3);
×
1054
                    }
1055

1056
                    $this->reportFile = $dir.'/'.basename($this->reportFile);
×
1057
                }//end if
1058

1059
                $this->overriddenDefaults['reportFile'] = true;
×
1060

1061
                if (is_dir($this->reportFile) === true) {
×
1062
                    $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" is a directory'.PHP_EOL.PHP_EOL;
×
1063
                    $error .= $this->printShortUsage(true);
×
1064
                    throw new DeepExitException($error, 3);
×
1065
                }
1066
            } else if (substr($arg, 0, 13) === 'report-width=') {
69✔
1067
                if (isset($this->overriddenDefaults['reportWidth']) === true) {
9✔
1068
                    break;
3✔
1069
                }
1070

1071
                $this->reportWidth = substr($arg, 13);
9✔
1072
                $this->overriddenDefaults['reportWidth'] = true;
9✔
1073
            } else if (substr($arg, 0, 9) === 'basepath=') {
69✔
1074
                if (isset($this->overriddenDefaults['basepath']) === true) {
×
1075
                    break;
×
1076
                }
1077

1078
                $this->overriddenDefaults['basepath'] = true;
×
1079

1080
                if (substr($arg, 9) === '') {
×
1081
                    $this->basepath = null;
×
1082
                    break;
×
1083
                }
1084

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

1087
                // It may not exist and return false instead.
1088
                if ($this->basepath === false) {
×
1089
                    $this->basepath = substr($arg, 9);
×
1090
                }
1091

1092
                if (is_dir($this->basepath) === false) {
×
1093
                    $error  = 'ERROR: The specified basepath "'.$this->basepath.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1094
                    $error .= $this->printShortUsage(true);
×
1095
                    throw new DeepExitException($error, 3);
×
1096
                }
1097
            } else if ((substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-')) {
69✔
1098
                $reports = [];
×
1099

1100
                if ($arg[6] === '-') {
×
1101
                    // This is a report with file output.
1102
                    $split = strpos($arg, '=');
×
1103
                    if ($split === false) {
×
1104
                        $report = substr($arg, 7);
×
1105
                        $output = null;
×
1106
                    } else {
1107
                        $report = substr($arg, 7, ($split - 7));
×
1108
                        $output = substr($arg, ($split + 1));
×
1109
                        if ($output === false) {
×
1110
                            $output = null;
×
1111
                        } else {
1112
                            $dir = Common::realpath(dirname($output));
×
1113
                            if (is_dir($dir) === false) {
×
1114
                                $error  = 'ERROR: The specified '.$report.' report file path "'.$output.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1115
                                $error .= $this->printShortUsage(true);
×
1116
                                throw new DeepExitException($error, 3);
×
1117
                            }
1118

1119
                            $output = $dir.'/'.basename($output);
×
1120

1121
                            if (is_dir($output) === true) {
×
1122
                                $error  = 'ERROR: The specified '.$report.' report file path "'.$output.'" is a directory'.PHP_EOL.PHP_EOL;
×
1123
                                $error .= $this->printShortUsage(true);
×
1124
                                throw new DeepExitException($error, 3);
×
1125
                            }
1126
                        }//end if
1127
                    }//end if
1128

1129
                    $reports[$report] = $output;
×
1130
                } else {
1131
                    // This is a single report.
1132
                    if (isset($this->overriddenDefaults['reports']) === true) {
×
1133
                        break;
×
1134
                    }
1135

1136
                    $reportNames = explode(',', substr($arg, 7));
×
1137
                    foreach ($reportNames as $report) {
×
1138
                        $reports[$report] = null;
×
1139
                    }
1140
                }//end if
1141

1142
                // Remove the default value so the CLI value overrides it.
1143
                if (isset($this->overriddenDefaults['reports']) === false) {
×
1144
                    $this->reports = $reports;
×
1145
                } else {
1146
                    $this->reports = array_merge($this->reports, $reports);
×
1147
                }
1148

1149
                $this->overriddenDefaults['reports'] = true;
×
1150
            } else if (substr($arg, 0, 7) === 'filter=') {
69✔
1151
                if (isset($this->overriddenDefaults['filter']) === true) {
×
1152
                    break;
×
1153
                }
1154

1155
                $this->filter = substr($arg, 7);
×
1156
                $this->overriddenDefaults['filter'] = true;
×
1157
            } else if (substr($arg, 0, 9) === 'standard=') {
69✔
1158
                $standards = trim(substr($arg, 9));
9✔
1159
                if ($standards !== '') {
9✔
1160
                    $this->standards = explode(',', $standards);
9✔
1161
                }
1162

1163
                $this->overriddenDefaults['standards'] = true;
9✔
1164
            } else if (substr($arg, 0, 11) === 'extensions=') {
60✔
1165
                if (isset($this->overriddenDefaults['extensions']) === true) {
30✔
1166
                    break;
3✔
1167
                }
1168

1169
                $extensionsString = substr($arg, 11);
30✔
1170
                $newExtensions    = [];
30✔
1171
                if (empty($extensionsString) === false) {
30✔
1172
                    $extensions = explode(',', $extensionsString);
27✔
1173
                    foreach ($extensions as $ext) {
27✔
1174
                        if (strpos($ext, '/') !== false) {
27✔
1175
                            // They specified the tokenizer too.
1176
                            list($ext, $tokenizer) = explode('/', $ext);
12✔
1177
                            if (strtoupper($tokenizer) !== 'PHP') {
12✔
1178
                                $error  = 'ERROR: Specifying the tokenizer to use for an extension is no longer supported.'.PHP_EOL;
9✔
1179
                                $error .= 'PHP_CodeSniffer >= 4.0 only supports scanning PHP files.'.PHP_EOL;
9✔
1180
                                $error .= 'Received: '.substr($arg, 11).PHP_EOL.PHP_EOL;
9✔
1181
                                $error .= $this->printShortUsage(true);
9✔
1182
                                throw new DeepExitException($error, 3);
9✔
1183
                            }
1184
                        }
1185

1186
                        $newExtensions[$ext] = 'PHP';
21✔
1187
                    }
1188
                }
1189

1190
                $this->extensions = $newExtensions;
21✔
1191
                $this->overriddenDefaults['extensions'] = true;
21✔
1192
            } else if (substr($arg, 0, 7) === 'suffix=') {
30✔
1193
                if (isset($this->overriddenDefaults['suffix']) === true) {
×
1194
                    break;
×
1195
                }
1196

1197
                $this->suffix = substr($arg, 7);
×
1198
                $this->overriddenDefaults['suffix'] = true;
×
1199
            } else if (substr($arg, 0, 9) === 'parallel=') {
30✔
1200
                if (isset($this->overriddenDefaults['parallel']) === true) {
×
1201
                    break;
×
1202
                }
1203

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

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

1221
                $this->errorSeverity = (int) substr($arg, 15);
×
1222
                $this->overriddenDefaults['errorSeverity'] = true;
×
1223
            } else if (substr($arg, 0, 17) === 'warning-severity=') {
30✔
1224
                if (isset($this->overriddenDefaults['warningSeverity']) === true) {
×
1225
                    break;
×
1226
                }
1227

1228
                $this->warningSeverity = (int) substr($arg, 17);
×
1229
                $this->overriddenDefaults['warningSeverity'] = true;
×
1230
            } else if (substr($arg, 0, 7) === 'ignore=') {
30✔
1231
                if (isset($this->overriddenDefaults['ignored']) === true) {
×
1232
                    break;
×
1233
                }
1234

1235
                // Split the ignore string on commas, unless the comma is escaped
1236
                // using 1 or 3 slashes (\, or \\\,).
1237
                $patterns = preg_split(
×
1238
                    '/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/',
×
1239
                    substr($arg, 7)
×
1240
                );
1241

1242
                $ignored = [];
×
1243
                foreach ($patterns as $pattern) {
×
1244
                    $pattern = trim($pattern);
×
1245
                    if ($pattern === '') {
×
1246
                        continue;
×
1247
                    }
1248

1249
                    $ignored[$pattern] = 'absolute';
×
1250
                }
1251

1252
                $this->ignored = $ignored;
×
1253
                $this->overriddenDefaults['ignored'] = true;
×
1254
            } else if (substr($arg, 0, 10) === 'generator='
30✔
1255
                && PHP_CODESNIFFER_CBF === false
30✔
1256
            ) {
1257
                if (isset($this->overriddenDefaults['generator']) === true) {
30✔
1258
                    break;
3✔
1259
                }
1260

1261
                $generatorName          = substr($arg, 10);
30✔
1262
                $lowerCaseGeneratorName = strtolower($generatorName);
30✔
1263

1264
                if (isset($this->validGenerators[$lowerCaseGeneratorName]) === false) {
30✔
1265
                    $validOptions = implode(', ', $this->validGenerators);
9✔
1266
                    $validOptions = substr_replace($validOptions, ' and', strrpos($validOptions, ','), 1);
9✔
1267
                    $error        = sprintf(
9✔
1268
                        'ERROR: "%s" is not a valid generator. The following generators are supported: %s.'.PHP_EOL.PHP_EOL,
9✔
1269
                        $generatorName,
9✔
1270
                        $validOptions
9✔
1271
                    );
6✔
1272
                    $error       .= $this->printShortUsage(true);
9✔
1273
                    throw new DeepExitException($error, 3);
9✔
1274
                }
1275

1276
                $this->generator = $this->validGenerators[$lowerCaseGeneratorName];
21✔
1277
                $this->overriddenDefaults['generator'] = true;
21✔
1278
            } else if (substr($arg, 0, 9) === 'encoding=') {
×
1279
                if (isset($this->overriddenDefaults['encoding']) === true) {
×
1280
                    break;
×
1281
                }
1282

1283
                $this->encoding = strtolower(substr($arg, 9));
×
1284
                $this->overriddenDefaults['encoding'] = true;
×
1285
            } else if (substr($arg, 0, 10) === 'tab-width=') {
×
1286
                if (isset($this->overriddenDefaults['tabWidth']) === true) {
×
1287
                    break;
×
1288
                }
1289

1290
                $this->tabWidth = (int) substr($arg, 10);
×
1291
                $this->overriddenDefaults['tabWidth'] = true;
×
1292
            } else {
1293
                if ($this->dieOnUnknownArg === false) {
×
1294
                    $eqPos = strpos($arg, '=');
×
1295
                    try {
1296
                        $unknown = $this->unknown;
×
1297

1298
                        if ($eqPos === false) {
×
1299
                            $unknown[$arg] = $arg;
×
1300
                        } else {
1301
                            $value         = substr($arg, ($eqPos + 1));
×
1302
                            $arg           = substr($arg, 0, $eqPos);
×
1303
                            $unknown[$arg] = $value;
×
1304
                        }
1305

1306
                        $this->unknown = $unknown;
×
1307
                    } catch (RuntimeException $e) {
×
1308
                        // Value is not valid, so just ignore it.
1309
                    }
1310
                } else {
1311
                    $this->processUnknownArgument('--'.$arg, $pos);
×
1312
                }
1313
            }//end if
1314
            break;
93✔
1315
        }//end switch
1316

1317
    }//end processLongArgument()
31✔
1318

1319

1320
    /**
1321
     * Parse supplied string into a list of validated sniff codes.
1322
     *
1323
     * @param string $input    Comma-separated string of sniff codes.
1324
     * @param string $argument The name of the argument which is being processed.
1325
     *
1326
     * @return array<string>
1327
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException When any of the provided codes are not valid as sniff codes.
1328
     */
1329
    private function parseSniffCodes($input, $argument)
114✔
1330
    {
1331
        $errors = [];
114✔
1332
        $sniffs = [];
114✔
1333

1334
        $possibleSniffs = array_filter(explode(',', $input));
114✔
1335

1336
        if ($possibleSniffs === []) {
114✔
1337
            $errors[] = 'No codes specified / empty argument';
18✔
1338
        }
1339

1340
        foreach ($possibleSniffs as $sniff) {
114✔
1341
            $sniff = trim($sniff);
96✔
1342

1343
            $partCount = substr_count($sniff, '.');
96✔
1344
            if ($partCount === 2) {
96✔
1345
                // Correct number of parts.
1346
                $sniffs[] = $sniff;
54✔
1347
                continue;
54✔
1348
            }
1349

1350
            if ($partCount === 0) {
54✔
1351
                $errors[] = 'Standard codes are not supported: '.$sniff;
12✔
1352
            } else if ($partCount === 1) {
42✔
1353
                $errors[] = 'Category codes are not supported: '.$sniff;
18✔
1354
            } else if ($partCount === 3) {
24✔
1355
                $errors[] = 'Message codes are not supported: '.$sniff;
18✔
1356
            } else {
1357
                $errors[] = 'Too many parts: '.$sniff;
12✔
1358
            }
1359

1360
            if ($partCount > 2) {
54✔
1361
                $parts    = explode('.', $sniff, 4);
24✔
1362
                $sniffs[] = $parts[0].'.'.$parts[1].'.'.$parts[2];
24✔
1363
            }
1364
        }//end foreach
1365

1366
        $sniffs = array_reduce(
114✔
1367
            $sniffs,
114✔
1368
            static function ($carry, $item) {
76✔
1369
                $lower = strtolower($item);
78✔
1370

1371
                foreach ($carry as $found) {
78✔
1372
                    if ($lower === strtolower($found)) {
36✔
1373
                        // This sniff is already in our list.
1374
                        return $carry;
24✔
1375
                    }
1376
                }
1377

1378
                $carry[] = $item;
78✔
1379

1380
                return $carry;
78✔
1381
            },
114✔
1382
            []
114✔
1383
        );
76✔
1384

1385
        if ($errors !== []) {
114✔
1386
            $error  = 'ERROR: The --'.$argument.' option only supports sniff codes.'.PHP_EOL;
72✔
1387
            $error .= 'Sniff codes are in the form "Standard.Category.Sniff".'.PHP_EOL;
72✔
1388
            $error .= PHP_EOL;
72✔
1389
            $error .= 'The following problems were detected:'.PHP_EOL;
72✔
1390
            $error .= '* '.implode(PHP_EOL.'* ', $errors).PHP_EOL;
72✔
1391

1392
            if ($sniffs !== []) {
72✔
1393
                $error .= PHP_EOL;
36✔
1394
                $error .= 'Perhaps try --'.$argument.'="'.implode(',', $sniffs).'" instead.'.PHP_EOL;
36✔
1395
            }
1396

1397
            $error .= PHP_EOL;
72✔
1398
            $error .= $this->printShortUsage(true);
72✔
1399
            throw new DeepExitException(ltrim($error), 3);
72✔
1400
        }
1401

1402
        return $sniffs;
42✔
1403

1404
    }//end parseSniffCodes()
1405

1406

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

1426
            $error  = "ERROR: option \"$arg\" not known".PHP_EOL.PHP_EOL;
×
1427
            $error .= $this->printShortUsage(true);
×
1428
            throw new DeepExitException($error, 3);
×
1429
        }
1430

1431
        $this->processFilePath($arg);
×
1432

1433
    }//end processUnknownArgument()
1434

1435

1436
    /**
1437
     * Processes a file path and add it to the file list.
1438
     *
1439
     * @param string $path The path to the file to add.
1440
     *
1441
     * @return void
1442
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
1443
     */
1444
    public function processFilePath($path)
×
1445
    {
1446
        // If we are processing STDIN, don't record any files to check.
1447
        if ($this->stdin === true) {
×
1448
            return;
×
1449
        }
1450

1451
        $file = Common::realpath($path);
×
1452
        if (file_exists($file) === false) {
×
1453
            if ($this->dieOnUnknownArg === false) {
×
1454
                return;
×
1455
            }
1456

1457
            $error  = 'ERROR: The file "'.$path.'" does not exist.'.PHP_EOL.PHP_EOL;
×
1458
            $error .= $this->printShortUsage(true);
×
1459
            throw new DeepExitException($error, 3);
×
1460
        } else {
1461
            // Can't modify the files array directly because it's not a real
1462
            // class member, so need to use this little get/modify/set trick.
1463
            $files       = $this->files;
×
1464
            $files[]     = $file;
×
1465
            $this->files = $files;
×
1466
            $this->overriddenDefaults['files'] = true;
×
1467
        }
1468

1469
    }//end processFilePath()
1470

1471

1472
    /**
1473
     * Prints out the usage information for this script.
1474
     *
1475
     * @return void
1476
     */
1477
    public function printUsage()
×
1478
    {
1479
        echo PHP_EOL;
×
1480

1481
        if (PHP_CODESNIFFER_CBF === true) {
×
1482
            $this->printPHPCBFUsage();
×
1483
        } else {
1484
            $this->printPHPCSUsage();
×
1485
        }
1486

1487
        echo PHP_EOL;
×
1488

1489
    }//end printUsage()
1490

1491

1492
    /**
1493
     * Prints out the short usage information for this script.
1494
     *
1495
     * @param bool $return If TRUE, the usage string is returned
1496
     *                     instead of output to screen.
1497
     *
1498
     * @return string|void
1499
     */
1500
    public function printShortUsage($return=false)
×
1501
    {
1502
        if (PHP_CODESNIFFER_CBF === true) {
×
1503
            $usage = 'Run "phpcbf --help" for usage information';
×
1504
        } else {
1505
            $usage = 'Run "phpcs --help" for usage information';
×
1506
        }
1507

1508
        $usage .= PHP_EOL.PHP_EOL;
×
1509

1510
        if ($return === true) {
×
1511
            return $usage;
×
1512
        }
1513

1514
        echo $usage;
×
1515

1516
    }//end printShortUsage()
1517

1518

1519
    /**
1520
     * Prints out the usage information for PHPCS.
1521
     *
1522
     * @return void
1523
     */
1524
    public function printPHPCSUsage()
×
1525
    {
1526
        $longOptions   = explode(',', Help::DEFAULT_LONG_OPTIONS);
×
1527
        $longOptions[] = 'cache';
×
1528
        $longOptions[] = 'no-cache';
×
1529
        $longOptions[] = 'report';
×
1530
        $longOptions[] = 'report-file';
×
1531
        $longOptions[] = 'report-report';
×
1532
        $longOptions[] = 'config-explain';
×
1533
        $longOptions[] = 'config-set';
×
1534
        $longOptions[] = 'config-delete';
×
1535
        $longOptions[] = 'config-show';
×
1536
        $longOptions[] = 'generator';
×
1537

1538
        $shortOptions = Help::DEFAULT_SHORT_OPTIONS.'aems';
×
1539

1540
        (new Help($this, $longOptions, $shortOptions))->display();
×
1541

1542
    }//end printPHPCSUsage()
1543

1544

1545
    /**
1546
     * Prints out the usage information for PHPCBF.
1547
     *
1548
     * @return void
1549
     */
1550
    public function printPHPCBFUsage()
×
1551
    {
1552
        $longOptions   = explode(',', Help::DEFAULT_LONG_OPTIONS);
×
1553
        $longOptions[] = 'suffix';
×
1554
        $shortOptions  = Help::DEFAULT_SHORT_OPTIONS;
×
1555

1556
        (new Help($this, $longOptions, $shortOptions))->display();
×
1557

1558
    }//end printPHPCBFUsage()
1559

1560

1561
    /**
1562
     * Get a single config value.
1563
     *
1564
     * @param string $key The name of the config value.
1565
     *
1566
     * @return string|null
1567
     * @see    setConfigData()
1568
     * @see    getAllConfigData()
1569
     */
1570
    public static function getConfigData($key)
6✔
1571
    {
1572
        $phpCodeSnifferConfig = self::getAllConfigData();
6✔
1573

1574
        if ($phpCodeSnifferConfig === null) {
6✔
1575
            return null;
×
1576
        }
1577

1578
        if (isset($phpCodeSnifferConfig[$key]) === false) {
6✔
1579
            return null;
6✔
1580
        }
1581

1582
        return $phpCodeSnifferConfig[$key];
6✔
1583

1584
    }//end getConfigData()
1585

1586

1587
    /**
1588
     * Get the path to an executable utility.
1589
     *
1590
     * @param string $name The name of the executable utility.
1591
     *
1592
     * @return string|null
1593
     * @see    getConfigData()
1594
     */
1595
    public static function getExecutablePath($name)
×
1596
    {
1597
        $data = self::getConfigData($name.'_path');
×
1598
        if ($data !== null) {
×
1599
            return $data;
×
1600
        }
1601

1602
        if ($name === "php") {
×
1603
            // For php, we know the executable path. There's no need to look it up.
1604
            return PHP_BINARY;
×
1605
        }
1606

1607
        if (array_key_exists($name, self::$executablePaths) === true) {
×
1608
            return self::$executablePaths[$name];
×
1609
        }
1610

1611
        if (PHP_OS_FAMILY === 'Windows') {
×
1612
            $cmd = 'where '.escapeshellarg($name).' 2> nul';
×
1613
        } else {
1614
            $cmd = 'which '.escapeshellarg($name).' 2> /dev/null';
×
1615
        }
1616

1617
        $result = exec($cmd, $output, $retVal);
×
1618
        if ($retVal !== 0) {
×
1619
            $result = null;
×
1620
        }
1621

1622
        self::$executablePaths[$name] = $result;
×
1623
        return $result;
×
1624

1625
    }//end getExecutablePath()
1626

1627

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

1651
        if ($temp === false) {
×
1652
            $path = '';
×
1653
            if (is_callable('\Phar::running') === true) {
×
1654
                $path = Phar::running(false);
×
1655
            }
1656

1657
            if ($path !== '') {
×
1658
                $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1659
            } else {
1660
                $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1661
            }
1662

1663
            if (is_file($configFile) === true
×
1664
                && is_writable($configFile) === false
×
1665
            ) {
1666
                $error = 'ERROR: Config file '.$configFile.' is not writable'.PHP_EOL.PHP_EOL;
×
1667
                throw new DeepExitException($error, 3);
×
1668
            }
1669
        }//end if
1670

1671
        $phpCodeSnifferConfig = self::getAllConfigData();
×
1672

1673
        if ($value === null) {
×
1674
            if (isset($phpCodeSnifferConfig[$key]) === true) {
×
1675
                unset($phpCodeSnifferConfig[$key]);
×
1676
            }
1677
        } else {
1678
            $phpCodeSnifferConfig[$key] = $value;
×
1679
        }
1680

1681
        if ($temp === false) {
×
1682
            $output  = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
×
1683
            $output .= var_export($phpCodeSnifferConfig, true);
×
1684
            $output .= ";\n?".'>';
×
1685

1686
            if (file_put_contents($configFile, $output) === false) {
×
1687
                $error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL;
×
1688
                throw new DeepExitException($error, 3);
×
1689
            }
1690

1691
            self::$configDataFile = $configFile;
×
1692
        }
1693

1694
        self::$configData = $phpCodeSnifferConfig;
×
1695

1696
        // If the installed paths are being set, make sure all known
1697
        // standards paths are added to the autoloader.
1698
        if ($key === 'installed_paths') {
×
1699
            $installedStandards = Standards::getInstalledStandardDetails();
×
1700
            foreach ($installedStandards as $details) {
×
1701
                Autoload::addSearchPath($details['path'], $details['namespace']);
×
1702
            }
1703
        }
1704

1705
        return true;
×
1706

1707
    }//end setConfigData()
1708

1709

1710
    /**
1711
     * Get all config data.
1712
     *
1713
     * @return array<string, string>
1714
     * @see    getConfigData()
1715
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the config file could not be read.
1716
     */
1717
    public static function getAllConfigData()
×
1718
    {
1719
        if (self::$configData !== null) {
×
1720
            return self::$configData;
×
1721
        }
1722

1723
        $path = '';
×
1724
        if (is_callable('\Phar::running') === true) {
×
1725
            $path = Phar::running(false);
×
1726
        }
1727

1728
        if ($path !== '') {
×
1729
            $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1730
        } else {
1731
            $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1732
            if (is_file($configFile) === false
×
1733
                && strpos('@data_dir@', '@data_dir') === false
×
1734
            ) {
1735
                $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
×
1736
            }
1737
        }
1738

1739
        if (is_file($configFile) === false) {
×
1740
            self::$configData = [];
×
1741
            return [];
×
1742
        }
1743

1744
        if (Common::isReadable($configFile) === false) {
×
1745
            $error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL;
×
1746
            throw new DeepExitException($error, 3);
×
1747
        }
1748

1749
        include $configFile;
×
1750
        self::$configDataFile = $configFile;
×
1751
        self::$configData     = $phpCodeSnifferConfig;
×
1752
        return self::$configData;
×
1753

1754
    }//end getAllConfigData()
1755

1756

1757
    /**
1758
     * Prepares the gathered config data for display.
1759
     *
1760
     * @param array<string, string> $data The config data to format for display.
1761
     *
1762
     * @return string
1763
     */
1764
    public function prepareConfigDataForDisplay($data)
18✔
1765
    {
1766
        if (empty($data) === true) {
18✔
1767
            return '';
6✔
1768
        }
1769

1770
        $max  = 0;
12✔
1771
        $keys = array_keys($data);
12✔
1772
        foreach ($keys as $key) {
12✔
1773
            $len = strlen($key);
12✔
1774
            if ($len > $max) {
12✔
1775
                $max = $len;
12✔
1776
            }
1777
        }
1778

1779
        $max += 2;
12✔
1780
        ksort($data);
12✔
1781

1782
        $output = '';
12✔
1783
        foreach ($data as $name => $value) {
12✔
1784
            $output .= str_pad($name.': ', $max).$value.PHP_EOL;
12✔
1785
        }
1786

1787
        return $output;
12✔
1788

1789
    }//end prepareConfigDataForDisplay()
1790

1791

1792
    /**
1793
     * Prints out the gathered config data.
1794
     *
1795
     * @param array<string, string> $data The config data to print.
1796
     *
1797
     * @deprecated 4.0.0 Use `echo Config::prepareConfigDataForDisplay()` instead.
1798
     *
1799
     * @return void
1800
     */
1801
    public function printConfigData($data)
3✔
1802
    {
1803
        echo $this->prepareConfigDataForDisplay($data);
3✔
1804

1805
    }//end printConfigData()
1✔
1806

1807

1808
}//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