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

PHPCSStandards / PHP_CodeSniffer / 17663981080

12 Sep 2025 03:49AM UTC coverage: 78.786%. Remained the same
17663981080

push

github

web-flow
Merge pull request #1245 from PHPCSStandards/phpcs-4.x/feature/155-normalize-some-code-style-rules-7

CS: normalize code style rules [7]

677 of 1022 new or added lines in 17 files covered. (66.24%)

7 existing lines in 1 file now uncovered.

19732 of 25045 relevant lines covered (78.79%)

96.47 hits per line

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

38.33
/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|null $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(string $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

280

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

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

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

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

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

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

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

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

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

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

352

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

365

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

378

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

389

390
    /**
391
     * Set the array of all config settings.
392
     *
393
     * @param array<string, mixed> $settings The array of config settings.
394
     *
395
     * @return void
396
     */
397
    public function setSettings(array $settings)
×
398
    {
399
        $this->settings = $settings;
×
400
    }
401

402

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

422
        if (empty($cliArgs) === true) {
×
423
            $cliArgs = $_SERVER['argv'];
×
424
            array_shift($cliArgs);
×
425
        }
426

427
        $this->restoreDefaults();
×
428
        $this->setCommandLineValues($cliArgs);
×
429

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

435
            do {
436
                foreach (self::CONFIG_FILENAMES as $defaultFilename) {
×
437
                    $default = $currentDir . DIRECTORY_SEPARATOR . $defaultFilename;
×
438
                    if (is_file($default) === true) {
×
439
                        $this->standards = [$default];
×
440
                        break(2);
×
441
                    }
442
                }
443

444
                $lastDir    = $currentDir;
×
445
                $currentDir = dirname($currentDir);
×
446
            } while ($currentDir !== '.' && $currentDir !== $lastDir && Common::isReadable($currentDir) === true);
×
447
        }
448

449
        if (defined('STDIN') === false
×
450
            || PHP_OS_FAMILY === 'Windows'
×
451
        ) {
452
            return;
×
453
        }
454

455
        $handle = fopen('php://stdin', 'r');
×
456

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

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

472
                $fileContents .= fgets($handle);
×
473
            }
474

475
            if (trim($fileContents) !== '') {
×
476
                $this->stdin        = true;
×
477
                $this->stdinContent = $fileContents;
×
478
                $this->overriddenDefaults['stdin']        = true;
×
479
                $this->overriddenDefaults['stdinContent'] = true;
×
480
            }
481
        }
482

483
        fclose($handle);
×
484
    }
485

486

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

499
        for ($i = 0; $i < $numArgs; $i++) {
×
500
            $arg = $this->cliArgs[$i];
×
501
            if ($arg === '') {
×
502
                continue;
×
503
            }
504

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

513
                if ($arg === '--') {
×
514
                    // Empty argument, ignore it.
515
                    continue;
×
516
                }
517

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

527
                        $this->processShortArgument($switch, $i);
×
528
                    }
529
                }
530
            } else {
531
                $this->processUnknownArgument($arg, $i);
×
532
            }
533
        }
534
    }
535

536

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

584
        $standard = self::getConfigData('default_standard');
9✔
585
        if ($standard !== null) {
9✔
586
            $this->standards = explode(',', $standard);
6✔
587
        }
588

589
        $reportFormat = self::getConfigData('report_format');
9✔
590
        if ($reportFormat !== null) {
9✔
591
            $this->reports = [$reportFormat => null];
×
592
        }
593

594
        $tabWidth = self::getConfigData('tab_width');
9✔
595
        if ($tabWidth !== null) {
9✔
596
            $this->tabWidth = (int) $tabWidth;
×
597
        }
598

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

604
        $severity = self::getConfigData('severity');
9✔
605
        if ($severity !== null) {
9✔
606
            $this->errorSeverity   = (int) $severity;
×
607
            $this->warningSeverity = (int) $severity;
×
608
        }
609

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

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

620
        $showWarnings = self::getConfigData('show_warnings');
9✔
621
        if ($showWarnings !== null) {
9✔
622
            $showWarnings = (bool) $showWarnings;
3✔
623
            if ($showWarnings === false) {
3✔
624
                $this->warningSeverity = 0;
3✔
625
            }
626
        }
627

628
        $reportWidth = self::getConfigData('report_width');
9✔
629
        if ($reportWidth !== null) {
9✔
630
            $this->reportWidth = $reportWidth;
3✔
631
        }
632

633
        $showProgress = self::getConfigData('show_progress');
9✔
634
        if ($showProgress !== null) {
9✔
635
            $this->showProgress = (bool) $showProgress;
×
636
        }
637

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

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

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

653
        $parallel = self::getConfigData('parallel');
9✔
654
        if ($parallel !== null) {
9✔
655
            $this->parallel = max((int) $parallel, 1);
×
656
        }
657
    }
3✔
658

659

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

NEW
685
                $this->verbosity++;
×
NEW
686
                $this->overriddenDefaults['verbosity'] = true;
×
UNCOV
687
                break;
×
688
            case 'l' :
30✔
NEW
689
                $this->local = true;
×
NEW
690
                $this->overriddenDefaults['local'] = true;
×
NEW
691
                break;
×
692
            case 's' :
30✔
NEW
693
                $this->showSources = true;
×
NEW
694
                $this->overriddenDefaults['showSources'] = true;
×
NEW
695
                break;
×
696
            case 'a' :
30✔
NEW
697
                $this->interactive = true;
×
NEW
698
                $this->overriddenDefaults['interactive'] = true;
×
NEW
699
                break;
×
700
            case 'e':
30✔
NEW
701
                $this->explain = true;
×
NEW
702
                $this->overriddenDefaults['explain'] = true;
×
NEW
703
                break;
×
704
            case 'p' :
30✔
NEW
705
                if ($this->quiet === true) {
×
706
                    // Ignore when quiet mode is enabled.
NEW
707
                    break;
×
708
                }
709

NEW
710
                $this->showProgress = true;
×
NEW
711
                $this->overriddenDefaults['showProgress'] = true;
×
NEW
712
                break;
×
713
            case 'q' :
30✔
714
                // Quiet mode disables a few other settings as well.
NEW
715
                $this->quiet        = true;
×
NEW
716
                $this->showProgress = false;
×
NEW
717
                $this->verbosity    = 0;
×
718

NEW
719
                $this->overriddenDefaults['quiet'] = true;
×
UNCOV
720
                break;
×
721
            case 'm' :
30✔
NEW
722
                $this->recordErrors = false;
×
NEW
723
                $this->overriddenDefaults['recordErrors'] = true;
×
NEW
724
                break;
×
725
            case 'd' :
30✔
726
                $ini = explode('=', $this->cliArgs[($pos + 1)]);
30✔
727
                $this->cliArgs[($pos + 1)] = '';
30✔
728
                if (isset($ini[1]) === false) {
30✔
729
                    // Set to true.
730
                    $ini[1] = '1';
3✔
731
                }
732

733
                $current = ini_get($ini[0]);
30✔
734
                if ($current === false) {
30✔
735
                    // Ini setting which doesn't exist, or is from an unavailable extension.
736
                    // Silently ignore it.
737
                    break;
4✔
738
                }
739

740
                $changed = ini_set($ini[0], $ini[1]);
26✔
741
                if ($changed === false && ini_get($ini[0]) !== $ini[1]) {
26✔
742
                    $error  = sprintf('ERROR: Ini option "%s" cannot be changed at runtime.', $ini[0]) . PHP_EOL;
12✔
743
                    $error .= $this->printShortUsage(true);
12✔
744
                    throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
12✔
745
                }
746
                break;
14✔
NEW
747
            case 'n' :
×
NEW
748
                if (isset($this->overriddenDefaults['warningSeverity']) === false) {
×
NEW
749
                    $this->warningSeverity = 0;
×
NEW
750
                    $this->overriddenDefaults['warningSeverity'] = true;
×
751
                }
NEW
752
                break;
×
NEW
753
            case 'w' :
×
NEW
754
                if (isset($this->overriddenDefaults['warningSeverity']) === false) {
×
NEW
755
                    $this->warningSeverity = $this->errorSeverity;
×
NEW
756
                    $this->overriddenDefaults['warningSeverity'] = true;
×
757
                }
NEW
758
                break;
×
759
            default:
NEW
760
                if ($this->dieOnUnknownArg === false) {
×
NEW
761
                    $unknown       = $this->unknown;
×
NEW
762
                    $unknown[]     = $arg;
×
NEW
763
                    $this->unknown = $unknown;
×
764
                } else {
NEW
765
                    $this->processUnknownArgument('-' . $arg, $pos);
×
766
                }
767
        }
768
    }
5✔
769

770

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

NEW
795
                $this->colors = true;
×
NEW
796
                $this->overriddenDefaults['colors'] = true;
×
UNCOV
797
                break;
×
798
            case 'no-colors':
189✔
NEW
799
                if (isset($this->overriddenDefaults['colors']) === true) {
×
NEW
800
                    break;
×
801
                }
802

NEW
803
                $this->colors = false;
×
NEW
804
                $this->overriddenDefaults['colors'] = true;
×
UNCOV
805
                break;
×
806
            case 'cache':
189✔
NEW
807
                if (isset($this->overriddenDefaults['cache']) === true) {
×
NEW
808
                    break;
×
809
                }
810

NEW
811
                $this->cache = true;
×
NEW
812
                $this->overriddenDefaults['cache'] = true;
×
UNCOV
813
                break;
×
814
            case 'no-cache':
189✔
NEW
815
                if (isset($this->overriddenDefaults['cache']) === true) {
×
NEW
816
                    break;
×
817
                }
818

NEW
819
                $this->cache = false;
×
NEW
820
                $this->overriddenDefaults['cache'] = true;
×
UNCOV
821
                break;
×
822
            case 'ignore-annotations':
189✔
NEW
823
                if (isset($this->overriddenDefaults['annotations']) === true) {
×
NEW
824
                    break;
×
825
                }
826

NEW
827
                $this->annotations = false;
×
NEW
828
                $this->overriddenDefaults['annotations'] = true;
×
NEW
829
                break;
×
830
            case 'config-set':
189✔
NEW
831
                if (isset($this->cliArgs[($pos + 1)]) === false
×
NEW
832
                    || isset($this->cliArgs[($pos + 2)]) === false
×
833
                ) {
NEW
834
                    $error  = 'ERROR: Setting a config option requires a name and value' . PHP_EOL . PHP_EOL;
×
NEW
835
                    $error .= $this->printShortUsage(true);
×
NEW
836
                    throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
837
                }
838

NEW
839
                $key     = $this->cliArgs[($pos + 1)];
×
NEW
840
                $value   = $this->cliArgs[($pos + 2)];
×
NEW
841
                $current = self::getConfigData($key);
×
842

843
                try {
NEW
844
                    $this->setConfigData($key, $value);
×
845
                } catch (Exception $e) {
×
846
                    throw new DeepExitException($e->getMessage() . PHP_EOL, ExitCode::PROCESS_ERROR);
×
847
                }
848

NEW
849
                $output = 'Using config file: ' . self::$configDataFile . PHP_EOL . PHP_EOL;
×
850

NEW
851
                if ($current === null) {
×
NEW
852
                    $output .= "Config value \"$key\" added successfully" . PHP_EOL;
×
853
                } else {
NEW
854
                    $output .= "Config value \"$key\" updated successfully; old value was \"$current\"" . PHP_EOL;
×
855
                }
NEW
856
                throw new DeepExitException($output, ExitCode::OKAY);
×
857
            case 'config-delete':
189✔
NEW
858
                if (isset($this->cliArgs[($pos + 1)]) === false) {
×
NEW
859
                    $error  = 'ERROR: Deleting a config option requires the name of the option' . PHP_EOL . PHP_EOL;
×
NEW
860
                    $error .= $this->printShortUsage(true);
×
NEW
861
                    throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
862
                }
863

NEW
864
                $output = 'Using config file: ' . self::$configDataFile . PHP_EOL . PHP_EOL;
×
865

NEW
866
                $key     = $this->cliArgs[($pos + 1)];
×
NEW
867
                $current = self::getConfigData($key);
×
NEW
868
                if ($current === null) {
×
NEW
869
                    $output .= "Config value \"$key\" has not been set" . PHP_EOL;
×
870
                } else {
871
                    try {
NEW
872
                        $this->setConfigData($key, null);
×
NEW
873
                    } catch (Exception $e) {
×
NEW
874
                        throw new DeepExitException($e->getMessage() . PHP_EOL, ExitCode::PROCESS_ERROR);
×
875
                    }
876

NEW
877
                    $output .= "Config value \"$key\" removed successfully; old value was \"$current\"" . PHP_EOL;
×
878
                }
NEW
879
                throw new DeepExitException($output, ExitCode::OKAY);
×
880
            case 'config-show':
189✔
NEW
881
                $data    = self::getAllConfigData();
×
NEW
882
                $output  = 'Using config file: ' . self::$configDataFile . PHP_EOL . PHP_EOL;
×
NEW
883
                $output .= $this->prepareConfigDataForDisplay($data);
×
NEW
884
                throw new DeepExitException($output, ExitCode::OKAY);
×
885
            case 'runtime-set':
189✔
NEW
886
                if (isset($this->cliArgs[($pos + 1)]) === false
×
NEW
887
                    || isset($this->cliArgs[($pos + 2)]) === false
×
888
                ) {
NEW
889
                    $error  = 'ERROR: Setting a runtime config option requires a name and value' . PHP_EOL . PHP_EOL;
×
NEW
890
                    $error .= $this->printShortUsage(true);
×
NEW
891
                    throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
892
                }
893

NEW
894
                $key   = $this->cliArgs[($pos + 1)];
×
NEW
895
                $value = $this->cliArgs[($pos + 2)];
×
NEW
896
                $this->cliArgs[($pos + 1)] = '';
×
NEW
897
                $this->cliArgs[($pos + 2)] = '';
×
NEW
898
                $this->setConfigData($key, $value, true);
×
NEW
899
                if (isset($this->overriddenDefaults['runtime-set']) === false) {
×
NEW
900
                    $this->overriddenDefaults['runtime-set'] = [];
×
901
                }
902

NEW
903
                $this->overriddenDefaults['runtime-set'][$key] = true;
×
NEW
904
                break;
×
905
            default:
906
                if (substr($arg, 0, 7) === 'sniffs=') {
189✔
907
                    if (isset($this->overriddenDefaults['sniffs']) === true) {
57✔
908
                        break;
3✔
909
                    }
910

911
                    $this->sniffs = $this->parseSniffCodes(substr($arg, 7), 'sniffs');
57✔
912
                    $this->overriddenDefaults['sniffs'] = true;
21✔
913
                } elseif (substr($arg, 0, 8) === 'exclude=') {
132✔
914
                    if (isset($this->overriddenDefaults['exclude']) === true) {
57✔
915
                        break;
3✔
916
                    }
917

918
                    $this->exclude = $this->parseSniffCodes(substr($arg, 8), 'exclude');
57✔
919
                    $this->overriddenDefaults['exclude'] = true;
21✔
920
                } elseif (substr($arg, 0, 6) === 'cache=') {
75✔
NEW
921
                    if ((isset($this->overriddenDefaults['cache']) === true
×
NEW
922
                        && $this->cache === false)
×
NEW
923
                        || isset($this->overriddenDefaults['cacheFile']) === true
×
924
                    ) {
NEW
925
                        break;
×
926
                    }
927

928
                    // Turn caching on.
NEW
929
                    $this->cache = true;
×
NEW
930
                    $this->overriddenDefaults['cache'] = true;
×
931

NEW
932
                    $this->cacheFile = Common::realpath(substr($arg, 6));
×
933

934
                    // It may not exist and return false instead.
NEW
935
                    if ($this->cacheFile === false) {
×
NEW
936
                        $this->cacheFile = substr($arg, 6);
×
937

NEW
938
                        $dir = dirname($this->cacheFile);
×
NEW
939
                        if (is_dir($dir) === false) {
×
NEW
940
                            $error  = 'ERROR: The specified cache file path "' . $this->cacheFile . '" points to a non-existent directory' . PHP_EOL . PHP_EOL;
×
NEW
941
                            $error .= $this->printShortUsage(true);
×
NEW
942
                            throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
943
                        }
944

NEW
945
                        if ($dir === '.') {
×
946
                            // Passed cache file is a file in the current directory.
NEW
947
                            $this->cacheFile = getcwd() . '/' . basename($this->cacheFile);
×
948
                        } else {
NEW
949
                            if ($dir[0] === '/') {
×
950
                                // An absolute path.
NEW
951
                                $dir = Common::realpath($dir);
×
952
                            } else {
NEW
953
                                $dir = Common::realpath(getcwd() . '/' . $dir);
×
954
                            }
955

NEW
956
                            if ($dir !== false) {
×
957
                                // Cache file path is relative.
NEW
958
                                $this->cacheFile = $dir . '/' . basename($this->cacheFile);
×
959
                            }
960
                        }
961
                    }
962

NEW
963
                    $this->overriddenDefaults['cacheFile'] = true;
×
964

NEW
965
                    if (is_dir($this->cacheFile) === true) {
×
NEW
966
                        $error  = 'ERROR: The specified cache file path "' . $this->cacheFile . '" is a directory' . PHP_EOL . PHP_EOL;
×
NEW
967
                        $error .= $this->printShortUsage(true);
×
NEW
968
                        throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
969
                    }
970
                } elseif (substr($arg, 0, 10) === 'bootstrap=') {
75✔
NEW
971
                    $files     = explode(',', substr($arg, 10));
×
NEW
972
                    $bootstrap = [];
×
NEW
973
                    foreach ($files as $file) {
×
NEW
974
                        $path = Common::realpath($file);
×
NEW
975
                        if ($path === false) {
×
NEW
976
                            $error  = 'ERROR: The specified bootstrap file "' . $file . '" does not exist' . PHP_EOL . PHP_EOL;
×
NEW
977
                            $error .= $this->printShortUsage(true);
×
NEW
978
                            throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
979
                        }
980

NEW
981
                        $bootstrap[] = $path;
×
982
                    }
983

NEW
984
                    $this->bootstrap = array_merge($this->bootstrap, $bootstrap);
×
NEW
985
                    $this->overriddenDefaults['bootstrap'] = true;
×
986
                } elseif (substr($arg, 0, 10) === 'file-list=') {
75✔
NEW
987
                    $fileList = substr($arg, 10);
×
NEW
988
                    $path     = Common::realpath($fileList);
×
989
                    if ($path === false) {
×
NEW
990
                        $error  = 'ERROR: The specified file list "' . $fileList . '" does not exist' . PHP_EOL . PHP_EOL;
×
991
                        $error .= $this->printShortUsage(true);
×
992
                        throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
993
                    }
994

NEW
995
                    $files = file($path);
×
NEW
996
                    foreach ($files as $inputFile) {
×
NEW
997
                        $inputFile = trim($inputFile);
×
998

999
                        // Skip empty lines.
NEW
1000
                        if ($inputFile === '') {
×
NEW
1001
                            continue;
×
1002
                        }
1003

NEW
1004
                        $this->processFilePath($inputFile);
×
1005
                    }
1006
                } elseif (substr($arg, 0, 11) === 'stdin-path=') {
75✔
NEW
1007
                    if (isset($this->overriddenDefaults['stdinPath']) === true) {
×
NEW
1008
                        break;
×
1009
                    }
1010

NEW
1011
                    $this->stdinPath = Common::realpath(substr($arg, 11));
×
1012

1013
                    // It may not exist and return false instead, so use whatever they gave us.
NEW
1014
                    if ($this->stdinPath === false) {
×
NEW
1015
                        $this->stdinPath = trim(substr($arg, 11));
×
1016
                    }
1017

NEW
1018
                    $this->overriddenDefaults['stdinPath'] = true;
×
1019
                } elseif (substr($arg, 0, 12) === 'report-file=') {
75✔
1020
                    if (PHP_CODESNIFFER_CBF === true || isset($this->overriddenDefaults['reportFile']) === true) {
6✔
1021
                        break;
3✔
1022
                    }
1023

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

1026
                    // It may not exist and return false instead.
1027
                    if ($this->reportFile === false) {
3✔
1028
                        $this->reportFile = substr($arg, 12);
3✔
1029

1030
                        $dir = Common::realpath(dirname($this->reportFile));
3✔
1031
                        if (is_dir($dir) === false) {
3✔
NEW
1032
                            $error  = 'ERROR: The specified report file path "' . $this->reportFile . '" points to a non-existent directory' . PHP_EOL . PHP_EOL;
×
NEW
1033
                            $error .= $this->printShortUsage(true);
×
NEW
1034
                            throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1035
                        }
1036

1037
                        $this->reportFile = $dir . '/' . basename($this->reportFile);
3✔
1038
                    }
1039

1040
                    $this->overriddenDefaults['reportFile'] = true;
3✔
1041

1042
                    if (is_dir($this->reportFile) === true) {
3✔
NEW
1043
                        $error  = 'ERROR: The specified report file path "' . $this->reportFile . '" is a directory' . PHP_EOL . PHP_EOL;
×
1044
                        $error .= $this->printShortUsage(true);
×
1045
                        throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
1✔
1046
                    }
1047
                } elseif (substr($arg, 0, 13) === 'report-width=') {
69✔
1048
                    if (isset($this->overriddenDefaults['reportWidth']) === true) {
9✔
1049
                        break;
3✔
1050
                    }
1051

1052
                    $this->reportWidth = substr($arg, 13);
9✔
1053
                    $this->overriddenDefaults['reportWidth'] = true;
9✔
1054
                } elseif (substr($arg, 0, 9) === 'basepath=') {
69✔
NEW
1055
                    if (isset($this->overriddenDefaults['basepath']) === true) {
×
NEW
1056
                        break;
×
1057
                    }
1058

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

NEW
1061
                    if (substr($arg, 9) === '') {
×
NEW
1062
                        $this->basepath = null;
×
NEW
1063
                        break;
×
1064
                    }
1065

NEW
1066
                    $basepath = Common::realpath(substr($arg, 9));
×
1067

1068
                    // It may not exist and return false instead.
NEW
1069
                    if ($basepath === false) {
×
NEW
1070
                        $this->basepath = substr($arg, 9);
×
1071
                    } else {
NEW
1072
                        $this->basepath = $basepath;
×
1073
                    }
1074

NEW
1075
                    if (is_dir($this->basepath) === false) {
×
NEW
1076
                        $error  = 'ERROR: The specified basepath "' . $this->basepath . '" points to a non-existent directory' . PHP_EOL . PHP_EOL;
×
NEW
1077
                        $error .= $this->printShortUsage(true);
×
NEW
1078
                        throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1079
                    }
1080
                } elseif ((substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-')) {
69✔
NEW
1081
                    $reports = [];
×
1082

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

NEW
1102
                                $output = $dir . '/' . basename($output);
×
1103

NEW
1104
                                if (is_dir($output) === true) {
×
NEW
1105
                                    $error  = 'ERROR: The specified ' . $report . ' report file path "' . $output . '" is a directory' . PHP_EOL . PHP_EOL;
×
NEW
1106
                                    $error .= $this->printShortUsage(true);
×
NEW
1107
                                    throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1108
                                }
1109
                            }
1110
                        }
1111

NEW
1112
                        $reports[$report] = $output;
×
1113
                    } else {
1114
                        // This is a single report.
NEW
1115
                        if (isset($this->overriddenDefaults['reports']) === true) {
×
NEW
1116
                            break;
×
1117
                        }
1118

NEW
1119
                        $reportNames = explode(',', substr($arg, 7));
×
NEW
1120
                        foreach ($reportNames as $report) {
×
NEW
1121
                            $reports[$report] = null;
×
1122
                        }
1123
                    }
1124

1125
                    // Remove the default value so the CLI value overrides it.
NEW
1126
                    if (isset($this->overriddenDefaults['reports']) === false) {
×
NEW
1127
                        $this->reports = $reports;
×
1128
                    } else {
NEW
1129
                        $this->reports = array_merge($this->reports, $reports);
×
1130
                    }
1131

NEW
1132
                    $this->overriddenDefaults['reports'] = true;
×
1133
                } elseif (substr($arg, 0, 7) === 'filter=') {
69✔
NEW
1134
                    if (isset($this->overriddenDefaults['filter']) === true) {
×
NEW
1135
                        break;
×
1136
                    }
1137

NEW
1138
                    $this->filter = substr($arg, 7);
×
NEW
1139
                    $this->overriddenDefaults['filter'] = true;
×
1140
                } elseif (substr($arg, 0, 9) === 'standard=') {
69✔
1141
                    $standards = trim(substr($arg, 9));
9✔
1142
                    if ($standards !== '') {
9✔
1143
                        $this->standards = explode(',', $standards);
9✔
1144
                    }
1145

1146
                    $this->overriddenDefaults['standards'] = true;
9✔
1147
                } elseif (substr($arg, 0, 11) === 'extensions=') {
60✔
1148
                    if (isset($this->overriddenDefaults['extensions']) === true) {
30✔
1149
                        break;
3✔
1150
                    }
1151

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

1169
                            $newExtensions[$ext] = 'PHP';
21✔
1170
                        }
1171
                    }
1172

1173
                    $this->extensions = $newExtensions;
21✔
1174
                    $this->overriddenDefaults['extensions'] = true;
21✔
1175
                } elseif (substr($arg, 0, 7) === 'suffix=') {
30✔
NEW
1176
                    if (isset($this->overriddenDefaults['suffix']) === true) {
×
NEW
1177
                        break;
×
1178
                    }
1179

NEW
1180
                    $this->suffix = substr($arg, 7);
×
NEW
1181
                    $this->overriddenDefaults['suffix'] = true;
×
1182
                } elseif (substr($arg, 0, 9) === 'parallel=') {
30✔
NEW
1183
                    if (isset($this->overriddenDefaults['parallel']) === true) {
×
NEW
1184
                        break;
×
1185
                    }
1186

NEW
1187
                    $this->parallel = max((int) substr($arg, 9), 1);
×
NEW
1188
                    $this->overriddenDefaults['parallel'] = true;
×
1189
                } elseif (substr($arg, 0, 9) === 'severity=') {
30✔
NEW
1190
                    $this->errorSeverity   = (int) substr($arg, 9);
×
NEW
1191
                    $this->warningSeverity = $this->errorSeverity;
×
NEW
1192
                    if (isset($this->overriddenDefaults['errorSeverity']) === false) {
×
NEW
1193
                        $this->overriddenDefaults['errorSeverity'] = true;
×
1194
                    }
1195

NEW
1196
                    if (isset($this->overriddenDefaults['warningSeverity']) === false) {
×
NEW
1197
                        $this->overriddenDefaults['warningSeverity'] = true;
×
1198
                    }
1199
                } elseif (substr($arg, 0, 15) === 'error-severity=') {
30✔
NEW
1200
                    if (isset($this->overriddenDefaults['errorSeverity']) === true) {
×
NEW
1201
                        break;
×
1202
                    }
1203

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

NEW
1211
                    $this->warningSeverity = (int) substr($arg, 17);
×
1212
                    $this->overriddenDefaults['warningSeverity'] = true;
×
1213
                } elseif (substr($arg, 0, 7) === 'ignore=') {
30✔
NEW
1214
                    if (isset($this->overriddenDefaults['ignored']) === true) {
×
NEW
1215
                        break;
×
1216
                    }
1217

1218
                    // Split the ignore string on commas, unless the comma is escaped
1219
                    // using 1 or 3 slashes (\, or \\\,).
NEW
1220
                    $patterns = preg_split(
×
NEW
1221
                        '/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/',
×
NEW
1222
                        substr($arg, 7)
×
1223
                    );
1224

NEW
1225
                    $ignored = [];
×
NEW
1226
                    foreach ($patterns as $pattern) {
×
NEW
1227
                        $pattern = trim($pattern);
×
NEW
1228
                        if ($pattern === '') {
×
NEW
1229
                            continue;
×
1230
                        }
1231

NEW
1232
                        $ignored[$pattern] = 'absolute';
×
1233
                    }
1234

NEW
1235
                    $this->ignored = $ignored;
×
NEW
1236
                    $this->overriddenDefaults['ignored'] = true;
×
1237
                } elseif (substr($arg, 0, 10) === 'generator='
30✔
1238
                    && PHP_CODESNIFFER_CBF === false
30✔
1239
                ) {
1240
                    if (isset($this->overriddenDefaults['generator']) === true) {
30✔
1241
                        break;
3✔
1242
                    }
1243

1244
                    $generatorName          = substr($arg, 10);
30✔
1245
                    $lowerCaseGeneratorName = strtolower($generatorName);
30✔
1246

1247
                    if (isset(self::VALID_GENERATORS[$lowerCaseGeneratorName]) === false) {
30✔
1248
                        $validOptions = implode(', ', self::VALID_GENERATORS);
9✔
1249
                        $validOptions = substr_replace($validOptions, ' and', strrpos($validOptions, ','), 1);
9✔
1250
                        $error        = sprintf(
9✔
1251
                            'ERROR: "%s" is not a valid generator. The following generators are supported: %s.' . PHP_EOL . PHP_EOL,
9✔
1252
                            $generatorName,
9✔
1253
                            $validOptions
9✔
1254
                        );
6✔
1255
                        $error       .= $this->printShortUsage(true);
9✔
1256
                        throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
9✔
1257
                    }
1258

1259
                    $this->generator = self::VALID_GENERATORS[$lowerCaseGeneratorName];
21✔
1260
                    $this->overriddenDefaults['generator'] = true;
21✔
NEW
1261
                } elseif (substr($arg, 0, 9) === 'encoding=') {
×
NEW
1262
                    if (isset($this->overriddenDefaults['encoding']) === true) {
×
NEW
1263
                        break;
×
1264
                    }
1265

NEW
1266
                    $this->encoding = strtolower(substr($arg, 9));
×
NEW
1267
                    $this->overriddenDefaults['encoding'] = true;
×
NEW
1268
                } elseif (substr($arg, 0, 10) === 'tab-width=') {
×
NEW
1269
                    if (isset($this->overriddenDefaults['tabWidth']) === true) {
×
NEW
1270
                        break;
×
1271
                    }
1272

NEW
1273
                    $this->tabWidth = (int) substr($arg, 10);
×
NEW
1274
                    $this->overriddenDefaults['tabWidth'] = true;
×
1275
                } else {
NEW
1276
                    if ($this->dieOnUnknownArg === false) {
×
NEW
1277
                        $eqPos = strpos($arg, '=');
×
1278
                        try {
NEW
1279
                            $unknown = $this->unknown;
×
1280

NEW
1281
                            if ($eqPos === false) {
×
NEW
1282
                                $unknown[$arg] = $arg;
×
1283
                            } else {
NEW
1284
                                $value         = substr($arg, ($eqPos + 1));
×
NEW
1285
                                $arg           = substr($arg, 0, $eqPos);
×
NEW
1286
                                $unknown[$arg] = $value;
×
1287
                            }
1288

NEW
1289
                            $this->unknown = $unknown;
×
NEW
1290
                        } catch (RuntimeException $e) {
×
1291
                            // Value is not valid, so just ignore it.
1292
                        }
1293
                    } else {
NEW
1294
                        $this->processUnknownArgument('--' . $arg, $pos);
×
1295
                    }
1296
                }
1297
                break;
96✔
1298
        }
1299
    }
33✔
1300

1301

1302
    /**
1303
     * Parse supplied string into a list of validated sniff codes.
1304
     *
1305
     * @param string $input    Comma-separated string of sniff codes.
1306
     * @param string $argument The name of the argument which is being processed.
1307
     *
1308
     * @return array<string>
1309
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException When any of the provided codes are not valid as sniff codes.
1310
     */
1311
    private function parseSniffCodes(string $input, string $argument)
114✔
1312
    {
1313
        $errors = [];
114✔
1314
        $sniffs = [];
114✔
1315

1316
        $possibleSniffs = array_filter(explode(',', $input));
114✔
1317

1318
        if ($possibleSniffs === []) {
114✔
1319
            $errors[] = 'No codes specified / empty argument';
18✔
1320
        }
1321

1322
        foreach ($possibleSniffs as $sniff) {
114✔
1323
            $sniff = trim($sniff);
96✔
1324

1325
            $partCount = substr_count($sniff, '.');
96✔
1326
            if ($partCount === 2) {
96✔
1327
                // Correct number of parts.
1328
                $sniffs[] = $sniff;
54✔
1329
                continue;
54✔
1330
            }
1331

1332
            if ($partCount === 0) {
54✔
1333
                $errors[] = 'Standard codes are not supported: ' . $sniff;
12✔
1334
            } elseif ($partCount === 1) {
42✔
1335
                $errors[] = 'Category codes are not supported: ' . $sniff;
18✔
1336
            } elseif ($partCount === 3) {
24✔
1337
                $errors[] = 'Message codes are not supported: ' . $sniff;
18✔
1338
            } else {
1339
                $errors[] = 'Too many parts: ' . $sniff;
12✔
1340
            }
1341

1342
            if ($partCount > 2) {
54✔
1343
                $parts    = explode('.', $sniff, 4);
24✔
1344
                $sniffs[] = $parts[0] . '.' . $parts[1] . '.' . $parts[2];
24✔
1345
            }
1346
        }
1347

1348
        $sniffs = array_reduce(
114✔
1349
            $sniffs,
114✔
1350
            static function ($carry, $item) {
76✔
1351
                $lower = strtolower($item);
78✔
1352

1353
                foreach ($carry as $found) {
78✔
1354
                    if ($lower === strtolower($found)) {
36✔
1355
                        // This sniff is already in our list.
1356
                        return $carry;
24✔
1357
                    }
1358
                }
1359

1360
                $carry[] = $item;
78✔
1361

1362
                return $carry;
78✔
1363
            },
114✔
1364
            []
114✔
1365
        );
76✔
1366

1367
        if ($errors !== []) {
114✔
1368
            $error  = 'ERROR: The --' . $argument . ' option only supports sniff codes.' . PHP_EOL;
72✔
1369
            $error .= 'Sniff codes are in the form "Standard.Category.Sniff".' . PHP_EOL;
72✔
1370
            $error .= PHP_EOL;
72✔
1371
            $error .= 'The following problems were detected:' . PHP_EOL;
72✔
1372
            $error .= '* ' . implode(PHP_EOL . '* ', $errors) . PHP_EOL;
72✔
1373

1374
            if ($sniffs !== []) {
72✔
1375
                $error .= PHP_EOL;
36✔
1376
                $error .= 'Perhaps try --' . $argument . '="' . implode(',', $sniffs) . '" instead.' . PHP_EOL;
36✔
1377
            }
1378

1379
            $error .= PHP_EOL;
72✔
1380
            $error .= $this->printShortUsage(true);
72✔
1381
            throw new DeepExitException(ltrim($error), ExitCode::PROCESS_ERROR);
72✔
1382
        }
1383

1384
        return $sniffs;
42✔
1385
    }
1386

1387

1388
    /**
1389
     * Processes an unknown command line argument.
1390
     *
1391
     * Assumes all unknown arguments are files and folders to check.
1392
     *
1393
     * @param string $arg The command line argument.
1394
     * @param int    $pos The position of the argument on the command line.
1395
     *
1396
     * @return void
1397
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
1398
     */
1399
    public function processUnknownArgument(string $arg, int $pos)
×
1400
    {
1401
        // We don't know about any additional switches; just files.
1402
        if ($arg[0] === '-') {
×
1403
            if ($this->dieOnUnknownArg === false) {
×
1404
                return;
×
1405
            }
1406

1407
            $error  = "ERROR: option \"$arg\" not known" . PHP_EOL . PHP_EOL;
×
1408
            $error .= $this->printShortUsage(true);
×
1409
            throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1410
        }
1411

1412
        $this->processFilePath($arg);
×
1413
    }
1414

1415

1416
    /**
1417
     * Processes a file path and add it to the file list.
1418
     *
1419
     * @param string $path The path to the file to add.
1420
     *
1421
     * @return void
1422
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
1423
     */
1424
    public function processFilePath(string $path)
×
1425
    {
1426
        // If we are processing STDIN, don't record any files to check.
1427
        if ($this->stdin === true) {
×
1428
            return;
×
1429
        }
1430

1431
        $file = Common::realpath($path);
×
1432
        if (file_exists($file) === false) {
×
1433
            if ($this->dieOnUnknownArg === false) {
×
1434
                return;
×
1435
            }
1436

1437
            $error  = 'ERROR: The file "' . $path . '" does not exist.' . PHP_EOL . PHP_EOL;
×
1438
            $error .= $this->printShortUsage(true);
×
1439
            throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1440
        } else {
1441
            // Can't modify the files array directly because it's not a real
1442
            // class member, so need to use this little get/modify/set trick.
1443
            $files       = $this->files;
×
1444
            $files[]     = $file;
×
1445
            $this->files = $files;
×
1446
            $this->overriddenDefaults['files'] = true;
×
1447
        }
1448
    }
1449

1450

1451
    /**
1452
     * Prints out the usage information for this script.
1453
     *
1454
     * @return void
1455
     */
1456
    public function printUsage()
×
1457
    {
1458
        echo PHP_EOL;
×
1459

1460
        if (PHP_CODESNIFFER_CBF === true) {
×
1461
            $this->printPHPCBFUsage();
×
1462
        } else {
1463
            $this->printPHPCSUsage();
×
1464
        }
1465

1466
        echo PHP_EOL;
×
1467
    }
1468

1469

1470
    /**
1471
     * Prints out the short usage information for this script.
1472
     *
1473
     * @param bool $returnOutput If TRUE, the usage string is returned
1474
     *                           instead of output to screen.
1475
     *
1476
     * @return string|void
1477
     */
1478
    public function printShortUsage(bool $returnOutput = false)
×
1479
    {
1480
        if (PHP_CODESNIFFER_CBF === true) {
×
1481
            $usage = 'Run "phpcbf --help" for usage information';
×
1482
        } else {
1483
            $usage = 'Run "phpcs --help" for usage information';
×
1484
        }
1485

1486
        $usage .= PHP_EOL . PHP_EOL;
×
1487

1488
        if ($returnOutput === true) {
×
1489
            return $usage;
×
1490
        }
1491

1492
        echo $usage;
×
1493
    }
1494

1495

1496
    /**
1497
     * Prints out the usage information for PHPCS.
1498
     *
1499
     * @return void
1500
     */
1501
    public function printPHPCSUsage()
×
1502
    {
1503
        $longOptions   = Help::DEFAULT_LONG_OPTIONS;
×
1504
        $longOptions[] = 'cache';
×
1505
        $longOptions[] = 'no-cache';
×
1506
        $longOptions[] = 'report';
×
1507
        $longOptions[] = 'report-file';
×
1508
        $longOptions[] = 'report-report';
×
1509
        $longOptions[] = 'config-explain';
×
1510
        $longOptions[] = 'config-set';
×
1511
        $longOptions[] = 'config-delete';
×
1512
        $longOptions[] = 'config-show';
×
1513
        $longOptions[] = 'generator';
×
1514

1515
        $shortOptions = Help::DEFAULT_SHORT_OPTIONS . 'aems';
×
1516

1517
        (new Help($this, $longOptions, $shortOptions))->display();
×
1518
    }
1519

1520

1521
    /**
1522
     * Prints out the usage information for PHPCBF.
1523
     *
1524
     * @return void
1525
     */
1526
    public function printPHPCBFUsage()
×
1527
    {
1528
        $longOptions   = Help::DEFAULT_LONG_OPTIONS;
×
1529
        $longOptions[] = 'suffix';
×
1530
        $shortOptions  = Help::DEFAULT_SHORT_OPTIONS;
×
1531

1532
        (new Help($this, $longOptions, $shortOptions))->display();
×
1533
    }
1534

1535

1536
    /**
1537
     * Get a single config value.
1538
     *
1539
     * @param string $key The name of the config value.
1540
     *
1541
     * @return string|null
1542
     * @see    setConfigData()
1543
     * @see    getAllConfigData()
1544
     */
1545
    public static function getConfigData(string $key)
6✔
1546
    {
1547
        $phpCodeSnifferConfig = self::getAllConfigData();
6✔
1548

1549
        if ($phpCodeSnifferConfig === null) {
6✔
1550
            return null;
×
1551
        }
1552

1553
        if (isset($phpCodeSnifferConfig[$key]) === false) {
6✔
1554
            return null;
6✔
1555
        }
1556

1557
        return $phpCodeSnifferConfig[$key];
6✔
1558
    }
1559

1560

1561
    /**
1562
     * Get the path to an executable utility.
1563
     *
1564
     * @param string $name The name of the executable utility.
1565
     *
1566
     * @return string|null
1567
     * @see    getConfigData()
1568
     */
1569
    public static function getExecutablePath(string $name)
×
1570
    {
1571
        $data = self::getConfigData($name . '_path');
×
1572
        if ($data !== null) {
×
1573
            return $data;
×
1574
        }
1575

1576
        if ($name === 'php') {
×
1577
            // For php, we know the executable path. There's no need to look it up.
1578
            return PHP_BINARY;
×
1579
        }
1580

1581
        if (array_key_exists($name, self::$executablePaths) === true) {
×
1582
            return self::$executablePaths[$name];
×
1583
        }
1584

1585
        if (PHP_OS_FAMILY === 'Windows') {
×
1586
            $cmd = 'where ' . escapeshellarg($name) . ' 2> nul';
×
1587
        } else {
1588
            $cmd = 'which ' . escapeshellarg($name) . ' 2> /dev/null';
×
1589
        }
1590

1591
        $result = exec($cmd, $output, $retVal);
×
1592
        if ($retVal !== 0) {
×
1593
            $result = null;
×
1594
        }
1595

1596
        self::$executablePaths[$name] = $result;
×
1597
        return $result;
×
1598
    }
1599

1600

1601
    /**
1602
     * Set a single config value.
1603
     *
1604
     * @param string      $key   The name of the config value.
1605
     * @param string|null $value The value to set. If null, the config
1606
     *                           entry is deleted, reverting it to the
1607
     *                           default value.
1608
     * @param boolean     $temp  Set this config data temporarily for this
1609
     *                           script run. This will not write the config
1610
     *                           data to the config file.
1611
     *
1612
     * @return bool
1613
     * @see    getConfigData()
1614
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the config file can not be written.
1615
     */
1616
    public function setConfigData(string $key, ?string $value, bool $temp = false)
×
1617
    {
1618
        if (isset($this->overriddenDefaults['runtime-set']) === true
×
1619
            && isset($this->overriddenDefaults['runtime-set'][$key]) === true
×
1620
        ) {
1621
            return false;
×
1622
        }
1623

1624
        if ($temp === false) {
×
1625
            $path = '';
×
1626
            if (is_callable('\Phar::running') === true) {
×
1627
                $path = Phar::running(false);
×
1628
            }
1629

1630
            if ($path !== '') {
×
1631
                $configFile = dirname($path) . DIRECTORY_SEPARATOR . 'CodeSniffer.conf';
×
1632
            } else {
1633
                $configFile = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'CodeSniffer.conf';
×
1634
            }
1635

1636
            if (is_file($configFile) === true
×
1637
                && is_writable($configFile) === false
×
1638
            ) {
1639
                $error = 'ERROR: Config file ' . $configFile . ' is not writable' . PHP_EOL . PHP_EOL;
×
1640
                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1641
            }
1642
        }
1643

1644
        $phpCodeSnifferConfig = self::getAllConfigData();
×
1645

1646
        if ($value === null) {
×
1647
            if (isset($phpCodeSnifferConfig[$key]) === true) {
×
1648
                unset($phpCodeSnifferConfig[$key]);
×
1649
            }
1650
        } else {
1651
            $phpCodeSnifferConfig[$key] = $value;
×
1652
        }
1653

1654
        if ($temp === false) {
×
1655
            $output  = '<' . '?php' . "\n" . ' $phpCodeSnifferConfig = ';
×
1656
            $output .= var_export($phpCodeSnifferConfig, true);
×
1657
            $output .= ";\n?" . '>';
×
1658

1659
            if (file_put_contents($configFile, $output) === false) {
×
1660
                $error = 'ERROR: Config file ' . $configFile . ' could not be written' . PHP_EOL . PHP_EOL;
×
1661
                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1662
            }
1663

1664
            self::$configDataFile = $configFile;
×
1665
        }
1666

1667
        self::$configData = $phpCodeSnifferConfig;
×
1668

1669
        // If the installed paths are being set, make sure all known
1670
        // standards paths are added to the autoloader.
1671
        if ($key === 'installed_paths') {
×
1672
            $installedStandards = Standards::getInstalledStandardDetails();
×
1673
            foreach ($installedStandards as $details) {
×
1674
                Autoload::addSearchPath($details['path'], $details['namespace']);
×
1675
            }
1676
        }
1677

1678
        return true;
×
1679
    }
1680

1681

1682
    /**
1683
     * Get all config data.
1684
     *
1685
     * @return array<string, string>
1686
     * @see    getConfigData()
1687
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the config file could not be read.
1688
     */
1689
    public static function getAllConfigData()
×
1690
    {
1691
        if (self::$configData !== null) {
×
1692
            return self::$configData;
×
1693
        }
1694

1695
        $path = '';
×
1696
        if (is_callable('\Phar::running') === true) {
×
1697
            $path = Phar::running(false);
×
1698
        }
1699

1700
        if ($path !== '') {
×
1701
            $configFile = dirname($path) . DIRECTORY_SEPARATOR . 'CodeSniffer.conf';
×
1702
        } else {
1703
            $configFile = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'CodeSniffer.conf';
×
1704
        }
1705

1706
        if (is_file($configFile) === false) {
×
1707
            self::$configData = [];
×
1708
            return [];
×
1709
        }
1710

1711
        if (Common::isReadable($configFile) === false) {
×
1712
            $error = 'ERROR: Config file ' . $configFile . ' is not readable' . PHP_EOL . PHP_EOL;
×
1713
            throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
1714
        }
1715

1716
        include $configFile;
×
1717
        self::$configDataFile = $configFile;
×
1718
        self::$configData     = $phpCodeSnifferConfig;
×
1719
        return self::$configData;
×
1720
    }
1721

1722

1723
    /**
1724
     * Prepares the gathered config data for display.
1725
     *
1726
     * @param array<string, string> $data The config data to format for display.
1727
     *
1728
     * @return string
1729
     */
1730
    public function prepareConfigDataForDisplay(array $data)
18✔
1731
    {
1732
        if (empty($data) === true) {
18✔
1733
            return '';
6✔
1734
        }
1735

1736
        $max  = 0;
12✔
1737
        $keys = array_keys($data);
12✔
1738
        foreach ($keys as $key) {
12✔
1739
            $len = strlen($key);
12✔
1740
            if ($len > $max) {
12✔
1741
                $max = $len;
12✔
1742
            }
1743
        }
1744

1745
        $max += 2;
12✔
1746
        ksort($data);
12✔
1747

1748
        $output = '';
12✔
1749
        foreach ($data as $name => $value) {
12✔
1750
            $output .= str_pad($name . ': ', $max) . $value . PHP_EOL;
12✔
1751
        }
1752

1753
        return $output;
12✔
1754
    }
1755

1756

1757
    /**
1758
     * Prints out the gathered config data.
1759
     *
1760
     * @param array<string, string> $data The config data to print.
1761
     *
1762
     * @deprecated 4.0.0 Use `echo Config::prepareConfigDataForDisplay()` instead.
1763
     *
1764
     * @return void
1765
     */
1766
    public function printConfigData(array $data)
3✔
1767
    {
1768
        echo $this->prepareConfigDataForDisplay($data);
3✔
1769
    }
1✔
1770
}
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

© 2025 Coveralls, Inc