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

PHPCSStandards / PHP_CodeSniffer / 14454424553

14 Apr 2025 07:49PM UTC coverage: 77.579% (+2.3%) from 75.291%
14454424553

push

github

web-flow
Merge pull request #983 from PHPCSStandards/phpcs-4.0/feature/sq-2448-remove-support-js-css

Remove CSS/JS support (HUGE PR, reviews welcome!)

119 of 126 new or added lines in 14 files covered. (94.44%)

26 existing lines in 10 files now uncovered.

19384 of 24986 relevant lines covered (77.58%)

78.47 hits per line

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

30.93
/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
    const VERSION = '4.0.0';
92

93
    /**
94
     * Package stability; either stable, beta or alpha.
95
     *
96
     * @var string
97
     */
98
    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
    const DEFAULT_REPORT_WIDTH = 80;
106

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

158
    /**
159
     * Whether or not to kill the process when an unknown command line arg is found.
160
     *
161
     * If FALSE, arguments that are not command line options or file/directory paths
162
     * will be ignored and execution will continue. These values will be stored in
163
     * $this->unknown.
164
     *
165
     * @var boolean
166
     */
167
    public $dieOnUnknownArg;
168

169
    /**
170
     * The current command line arguments we are processing.
171
     *
172
     * @var string[]
173
     */
174
    private $cliArgs = [];
175

176
    /**
177
     * A list of valid generators.
178
     *
179
     * {@internal Once support for PHP < 5.6 is dropped, this property should be refactored into a
180
     * class constant.}
181
     *
182
     * @var array<string, string> Keys are the lowercase version of the generator name, while values
183
     *                            are the associated PHP generator class.
184
     */
185
    private $validGenerators = [
186
        'text'     => 'Text',
187
        'html'     => 'HTML',
188
        'markdown' => 'Markdown',
189
    ];
190

191
    /**
192
     * Command line values that the user has supplied directly.
193
     *
194
     * @var array<string, true|array<string, true>>
195
     */
196
    private static $overriddenDefaults = [];
197

198
    /**
199
     * Config file data that has been loaded for the run.
200
     *
201
     * @var array<string, string>
202
     */
203
    private static $configData = null;
204

205
    /**
206
     * The full path to the config data file that has been loaded.
207
     *
208
     * @var string
209
     */
210
    private static $configDataFile = null;
211

212
    /**
213
     * Automatically discovered executable utility paths.
214
     *
215
     * @var array<string, string>
216
     */
217
    private static $executablePaths = [];
218

219

220
    /**
221
     * Get the value of an inaccessible property.
222
     *
223
     * @param string $name The name of the property.
224
     *
225
     * @return mixed
226
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
227
     */
228
    public function __get($name)
48✔
229
    {
230
        if (array_key_exists($name, $this->settings) === false) {
48✔
231
            throw new RuntimeException("ERROR: unable to get value of property \"$name\"");
×
232
        }
233

234
        // Figure out what the terminal width needs to be for "auto".
235
        if ($name === 'reportWidth' && $this->settings[$name] === 'auto') {
48✔
236
            if (function_exists('shell_exec') === true) {
9✔
237
                $dimensions = shell_exec('stty size 2>&1');
9✔
238
                if (is_string($dimensions) === true && preg_match('|\d+ (\d+)|', $dimensions, $matches) === 1) {
9✔
239
                    $this->settings[$name] = (int) $matches[1];
×
240
                }
241
            }
242

243
            if ($this->settings[$name] === 'auto') {
9✔
244
                // If shell_exec wasn't available or didn't yield a usable value, set to the default.
245
                // This will prevent subsequent retrievals of the reportWidth from making another call to stty.
246
                $this->settings[$name] = self::DEFAULT_REPORT_WIDTH;
9✔
247
            }
248
        }
249

250
        return $this->settings[$name];
48✔
251

252
    }//end __get()
253

254

255
    /**
256
     * Set the value of an inaccessible property.
257
     *
258
     * @param string $name  The name of the property.
259
     * @param mixed  $value The value of the property.
260
     *
261
     * @return void
262
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
263
     */
264
    public function __set($name, $value)
48✔
265
    {
266
        if (array_key_exists($name, $this->settings) === false) {
48✔
267
            throw new RuntimeException("Can't __set() $name; setting doesn't exist");
×
268
        }
269

270
        switch ($name) {
16✔
271
        case 'reportWidth' :
48✔
272
            if (is_string($value) === true && $value === 'auto') {
48✔
273
                // Nothing to do. Leave at 'auto'.
274
                break;
48✔
275
            }
276

277
            if (is_int($value) === true) {
39✔
278
                $value = abs($value);
6✔
279
            } else if (is_string($value) === true && preg_match('`^\d+$`', $value) === 1) {
33✔
280
                $value = (int) $value;
15✔
281
            } else {
282
                $value = self::DEFAULT_REPORT_WIDTH;
18✔
283
            }
284
            break;
39✔
285

286
        case 'standards' :
48✔
287
            $cleaned = [];
48✔
288

289
            // Check if the standard name is valid, or if the case is invalid.
290
            $installedStandards = Standards::getInstalledStandards();
48✔
291
            foreach ($value as $standard) {
48✔
292
                foreach ($installedStandards as $validStandard) {
48✔
293
                    if (strtolower($standard) === strtolower($validStandard)) {
48✔
294
                        $standard = $validStandard;
48✔
295
                        break;
48✔
296
                    }
297
                }
298

299
                $cleaned[] = $standard;
48✔
300
            }
301

302
            $value = $cleaned;
48✔
303
            break;
48✔
304

305
        // Only track time when explicitly needed.
306
        case 'verbosity':
48✔
307
            if ($value > 2) {
48✔
308
                $this->settings['trackTime'] = true;
×
309
            }
310
            break;
48✔
311
        case 'reports':
48✔
312
            $reports = array_change_key_case($value, CASE_LOWER);
48✔
313
            if (array_key_exists('performance', $reports) === true) {
48✔
314
                $this->settings['trackTime'] = true;
×
315
            }
316
            break;
48✔
317

318
        default :
319
            // No validation required.
320
            break;
48✔
321
        }//end switch
322

323
        $this->settings[$name] = $value;
48✔
324

325
    }//end __set()
16✔
326

327

328
    /**
329
     * Check if the value of an inaccessible property is set.
330
     *
331
     * @param string $name The name of the property.
332
     *
333
     * @return bool
334
     */
335
    public function __isset($name)
×
336
    {
337
        return isset($this->settings[$name]);
×
338

339
    }//end __isset()
340

341

342
    /**
343
     * Unset the value of an inaccessible property.
344
     *
345
     * @param string $name The name of the property.
346
     *
347
     * @return void
348
     */
349
    public function __unset($name)
×
350
    {
351
        $this->settings[$name] = null;
×
352

353
    }//end __unset()
354

355

356
    /**
357
     * Get the array of all config settings.
358
     *
359
     * @return array<string, mixed>
360
     */
361
    public function getSettings()
×
362
    {
363
        return $this->settings;
×
364

365
    }//end getSettings()
366

367

368
    /**
369
     * Set the array of all config settings.
370
     *
371
     * @param array<string, mixed> $settings The array of config settings.
372
     *
373
     * @return void
374
     */
375
    public function setSettings($settings)
×
376
    {
377
        return $this->settings = $settings;
×
378

379
    }//end setSettings()
380

381

382
    /**
383
     * Creates a Config object and populates it with command line values.
384
     *
385
     * @param array $cliArgs         An array of values gathered from CLI args.
386
     * @param bool  $dieOnUnknownArg Whether or not to kill the process when an
387
     *                               unknown command line arg is found.
388
     *
389
     * @return void
390
     */
391
    public function __construct(array $cliArgs=[], $dieOnUnknownArg=true)
×
392
    {
393
        if (defined('PHP_CODESNIFFER_IN_TESTS') === true) {
×
394
            // Let everything through during testing so that we can
395
            // make use of PHPUnit command line arguments as well.
396
            $this->dieOnUnknownArg = false;
×
397
        } else {
398
            $this->dieOnUnknownArg = $dieOnUnknownArg;
×
399
        }
400

401
        if (empty($cliArgs) === true) {
×
402
            $cliArgs = $_SERVER['argv'];
×
403
            array_shift($cliArgs);
×
404
        }
405

406
        $this->restoreDefaults();
×
407
        $this->setCommandLineValues($cliArgs);
×
408

409
        if (isset(self::$overriddenDefaults['standards']) === false) {
×
410
            // They did not supply a standard to use.
411
            // Look for a default ruleset in the current directory or higher.
412
            $currentDir = getcwd();
×
413

414
            $defaultFiles = [
415
                '.phpcs.xml',
×
416
                'phpcs.xml',
417
                '.phpcs.xml.dist',
418
                'phpcs.xml.dist',
419
            ];
420

421
            do {
422
                foreach ($defaultFiles as $defaultFilename) {
×
423
                    $default = $currentDir.DIRECTORY_SEPARATOR.$defaultFilename;
×
424
                    if (is_file($default) === true) {
×
425
                        $this->standards = [$default];
×
426
                        break(2);
×
427
                    }
428
                }
429

430
                $lastDir    = $currentDir;
×
431
                $currentDir = dirname($currentDir);
×
432
            } while ($currentDir !== '.' && $currentDir !== $lastDir && Common::isReadable($currentDir) === true);
×
433
        }//end if
434

435
        if (defined('STDIN') === false
×
436
            || stripos(PHP_OS, 'WIN') === 0
×
437
        ) {
438
            return;
×
439
        }
440

441
        $handle = fopen('php://stdin', 'r');
×
442

443
        // Check for content on STDIN.
444
        if ($this->stdin === true
×
445
            || (Common::isStdinATTY() === false
×
446
            && feof($handle) === false)
×
447
        ) {
448
            $readStreams = [$handle];
×
449
            $writeSteams = null;
×
450

451
            $fileContents = '';
×
452
            while (is_resource($handle) === true && feof($handle) === false) {
×
453
                // Set a timeout of 200ms.
454
                if (stream_select($readStreams, $writeSteams, $writeSteams, 0, 200000) === 0) {
×
455
                    break;
×
456
                }
457

458
                $fileContents .= fgets($handle);
×
459
            }
460

461
            if (trim($fileContents) !== '') {
×
462
                $this->stdin        = true;
×
463
                $this->stdinContent = $fileContents;
×
464
                self::$overriddenDefaults['stdin']        = true;
×
465
                self::$overriddenDefaults['stdinContent'] = true;
×
466
            }
467
        }//end if
468

469
        fclose($handle);
×
470

471
    }//end __construct()
472

473

474
    /**
475
     * Set the command line values.
476
     *
477
     * @param array $args An array of command line arguments to set.
478
     *
479
     * @return void
480
     */
481
    public function setCommandLineValues($args)
×
482
    {
483
        $this->cliArgs = $args;
×
484
        $numArgs       = count($args);
×
485

486
        for ($i = 0; $i < $numArgs; $i++) {
×
487
            $arg = $this->cliArgs[$i];
×
488
            if ($arg === '') {
×
489
                continue;
×
490
            }
491

492
            if ($arg[0] === '-') {
×
493
                if ($arg === '-') {
×
494
                    // Asking to read from STDIN.
495
                    $this->stdin = true;
×
496
                    self::$overriddenDefaults['stdin'] = true;
×
497
                    continue;
×
498
                }
499

500
                if ($arg === '--') {
×
501
                    // Empty argument, ignore it.
502
                    continue;
×
503
                }
504

505
                if ($arg[1] === '-') {
×
506
                    $this->processLongArgument(substr($arg, 2), $i);
×
507
                } else {
508
                    $switches = str_split($arg);
×
509
                    foreach ($switches as $switch) {
×
510
                        if ($switch === '-') {
×
511
                            continue;
×
512
                        }
513

514
                        $this->processShortArgument($switch, $i);
×
515
                    }
516
                }
517
            } else {
518
                $this->processUnknownArgument($arg, $i);
×
519
            }//end if
520
        }//end for
521

522
    }//end setCommandLineValues()
523

524

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

572
        $standard = self::getConfigData('default_standard');
9✔
573
        if ($standard !== null) {
9✔
574
            $this->standards = explode(',', $standard);
6✔
575
        }
576

577
        $reportFormat = self::getConfigData('report_format');
9✔
578
        if ($reportFormat !== null) {
9✔
579
            $this->reports = [$reportFormat => null];
×
580
        }
581

582
        $tabWidth = self::getConfigData('tab_width');
9✔
583
        if ($tabWidth !== null) {
9✔
584
            $this->tabWidth = (int) $tabWidth;
×
585
        }
586

587
        $encoding = self::getConfigData('encoding');
9✔
588
        if ($encoding !== null) {
9✔
589
            $this->encoding = strtolower($encoding);
×
590
        }
591

592
        $severity = self::getConfigData('severity');
9✔
593
        if ($severity !== null) {
9✔
594
            $this->errorSeverity   = (int) $severity;
×
595
            $this->warningSeverity = (int) $severity;
×
596
        }
597

598
        $severity = self::getConfigData('error_severity');
9✔
599
        if ($severity !== null) {
9✔
600
            $this->errorSeverity = (int) $severity;
×
601
        }
602

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

608
        $showWarnings = self::getConfigData('show_warnings');
9✔
609
        if ($showWarnings !== null) {
9✔
610
            $showWarnings = (bool) $showWarnings;
3✔
611
            if ($showWarnings === false) {
3✔
612
                $this->warningSeverity = 0;
3✔
613
            }
614
        }
615

616
        $reportWidth = self::getConfigData('report_width');
9✔
617
        if ($reportWidth !== null) {
9✔
618
            $this->reportWidth = $reportWidth;
3✔
619
        }
620

621
        $showProgress = self::getConfigData('show_progress');
9✔
622
        if ($showProgress !== null) {
9✔
623
            $this->showProgress = (bool) $showProgress;
×
624
        }
625

626
        $quiet = self::getConfigData('quiet');
9✔
627
        if ($quiet !== null) {
9✔
628
            $this->quiet = (bool) $quiet;
×
629
        }
630

631
        $colors = self::getConfigData('colors');
9✔
632
        if ($colors !== null) {
9✔
633
            $this->colors = (bool) $colors;
×
634
        }
635

636
        if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
9✔
637
            $cache = self::getConfigData('cache');
×
638
            if ($cache !== null) {
×
639
                $this->cache = (bool) $cache;
×
640
            }
641

642
            $parallel = self::getConfigData('parallel');
×
643
            if ($parallel !== null) {
×
644
                $this->parallel = max((int) $parallel, 1);
×
645
            }
646
        }
647

648
    }//end restoreDefaults()
3✔
649

650

651
    /**
652
     * Processes a short (-e) command line argument.
653
     *
654
     * @param string $arg The command line argument.
655
     * @param int    $pos The position of the argument on the command line.
656
     *
657
     * @return void
658
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
659
     */
660
    public function processShortArgument($arg, $pos)
×
661
    {
662
        switch ($arg) {
663
        case 'h':
×
664
        case '?':
×
665
            ob_start();
×
666
            $this->printUsage();
×
667
            $output = ob_get_contents();
×
668
            ob_end_clean();
×
669
            throw new DeepExitException($output, 0);
×
670
        case 'i' :
×
671
            ob_start();
×
672
            Standards::printInstalledStandards();
×
673
            $output = ob_get_contents();
×
674
            ob_end_clean();
×
675
            throw new DeepExitException($output, 0);
×
676
        case 'v' :
×
677
            if ($this->quiet === true) {
×
678
                // Ignore when quiet mode is enabled.
679
                break;
×
680
            }
681

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

707
            $this->showProgress = true;
×
708
            self::$overriddenDefaults['showProgress'] = true;
×
709
            break;
×
710
        case 'q' :
×
711
            // Quiet mode disables a few other settings as well.
712
            $this->quiet        = true;
×
713
            $this->showProgress = false;
×
714
            $this->verbosity    = 0;
×
715

716
            self::$overriddenDefaults['quiet'] = true;
×
717
            break;
×
718
        case 'm' :
×
719
            $this->recordErrors = false;
×
720
            self::$overriddenDefaults['recordErrors'] = true;
×
721
            break;
×
722
        case 'd' :
×
723
            $ini = explode('=', $this->cliArgs[($pos + 1)]);
×
724
            $this->cliArgs[($pos + 1)] = '';
×
725
            if (isset($ini[1]) === true) {
×
726
                ini_set($ini[0], $ini[1]);
×
727
            } else {
728
                ini_set($ini[0], true);
×
729
            }
730
            break;
×
731
        case 'n' :
×
732
            if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
×
733
                $this->warningSeverity = 0;
×
734
                self::$overriddenDefaults['warningSeverity'] = true;
×
735
            }
736
            break;
×
737
        case 'w' :
×
738
            if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
×
739
                $this->warningSeverity = $this->errorSeverity;
×
740
                self::$overriddenDefaults['warningSeverity'] = true;
×
741
            }
742
            break;
×
743
        default:
744
            if ($this->dieOnUnknownArg === false) {
×
745
                $unknown       = $this->unknown;
×
746
                $unknown[]     = $arg;
×
747
                $this->unknown = $unknown;
×
748
            } else {
749
                $this->processUnknownArgument('-'.$arg, $pos);
×
750
            }
751
        }//end switch
752

753
    }//end processShortArgument()
754

755

756
    /**
757
     * Processes a long (--example) command-line argument.
758
     *
759
     * @param string $arg The command line argument.
760
     * @param int    $pos The position of the argument on the command line.
761
     *
762
     * @return void
763
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
764
     */
765
    public function processLongArgument($arg, $pos)
183✔
766
    {
767
        switch ($arg) {
61✔
768
        case 'help':
183✔
769
            ob_start();
×
770
            $this->printUsage();
×
771
            $output = ob_get_contents();
×
772
            ob_end_clean();
×
773
            throw new DeepExitException($output, 0);
×
774
        case 'version':
183✔
775
            $output  = 'PHP_CodeSniffer version '.self::VERSION.' ('.self::STABILITY.') ';
×
776
            $output .= 'by Squiz and PHPCSStandards'.PHP_EOL;
×
777
            throw new DeepExitException($output, 0);
×
778
        case 'colors':
183✔
779
            if (isset(self::$overriddenDefaults['colors']) === true) {
×
780
                break;
×
781
            }
782

783
            $this->colors = true;
×
784
            self::$overriddenDefaults['colors'] = true;
×
785
            break;
×
786
        case 'no-colors':
183✔
787
            if (isset(self::$overriddenDefaults['colors']) === true) {
×
788
                break;
×
789
            }
790

791
            $this->colors = false;
×
792
            self::$overriddenDefaults['colors'] = true;
×
793
            break;
×
794
        case 'cache':
183✔
795
            if (isset(self::$overriddenDefaults['cache']) === true) {
×
796
                break;
×
797
            }
798

799
            if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
×
800
                $this->cache = true;
×
801
                self::$overriddenDefaults['cache'] = true;
×
802
            }
803
            break;
×
804
        case 'no-cache':
183✔
805
            if (isset(self::$overriddenDefaults['cache']) === true) {
×
806
                break;
×
807
            }
808

809
            $this->cache = false;
×
810
            self::$overriddenDefaults['cache'] = true;
×
811
            break;
×
812
        case 'ignore-annotations':
183✔
813
            if (isset(self::$overriddenDefaults['annotations']) === true) {
×
814
                break;
×
815
            }
816

817
            $this->annotations = false;
×
818
            self::$overriddenDefaults['annotations'] = true;
×
819
            break;
×
820
        case 'config-set':
183✔
821
            if (isset($this->cliArgs[($pos + 1)]) === false
×
822
                || isset($this->cliArgs[($pos + 2)]) === false
×
823
            ) {
824
                $error  = 'ERROR: Setting a config option requires a name and value'.PHP_EOL.PHP_EOL;
×
825
                $error .= $this->printShortUsage(true);
×
826
                throw new DeepExitException($error, 3);
×
827
            }
828

829
            $key     = $this->cliArgs[($pos + 1)];
×
830
            $value   = $this->cliArgs[($pos + 2)];
×
831
            $current = self::getConfigData($key);
×
832

833
            try {
834
                $this->setConfigData($key, $value);
×
835
            } catch (Exception $e) {
×
836
                throw new DeepExitException($e->getMessage().PHP_EOL, 3);
×
837
            }
838

839
            $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
840

841
            if ($current === null) {
×
842
                $output .= "Config value \"$key\" added successfully".PHP_EOL;
×
843
            } else {
844
                $output .= "Config value \"$key\" updated successfully; old value was \"$current\"".PHP_EOL;
×
845
            }
846
            throw new DeepExitException($output, 0);
×
847
        case 'config-delete':
183✔
848
            if (isset($this->cliArgs[($pos + 1)]) === false) {
×
849
                $error  = 'ERROR: Deleting a config option requires the name of the option'.PHP_EOL.PHP_EOL;
×
850
                $error .= $this->printShortUsage(true);
×
851
                throw new DeepExitException($error, 3);
×
852
            }
853

854
            $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
855

856
            $key     = $this->cliArgs[($pos + 1)];
×
857
            $current = self::getConfigData($key);
×
858
            if ($current === null) {
×
859
                $output .= "Config value \"$key\" has not been set".PHP_EOL;
×
860
            } else {
861
                try {
862
                    $this->setConfigData($key, null);
×
863
                } catch (Exception $e) {
×
864
                    throw new DeepExitException($e->getMessage().PHP_EOL, 3);
×
865
                }
866

867
                $output .= "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL;
×
868
            }
869
            throw new DeepExitException($output, 0);
×
870
        case 'config-show':
183✔
871
            ob_start();
×
872
            $data = self::getAllConfigData();
×
873
            echo 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
874
            $this->printConfigData($data);
×
875
            $output = ob_get_contents();
×
876
            ob_end_clean();
×
877
            throw new DeepExitException($output, 0);
×
878
        case 'runtime-set':
183✔
879
            if (isset($this->cliArgs[($pos + 1)]) === false
×
880
                || isset($this->cliArgs[($pos + 2)]) === false
×
881
            ) {
882
                $error  = 'ERROR: Setting a runtime config option requires a name and value'.PHP_EOL.PHP_EOL;
×
883
                $error .= $this->printShortUsage(true);
×
884
                throw new DeepExitException($error, 3);
×
885
            }
886

887
            $key   = $this->cliArgs[($pos + 1)];
×
888
            $value = $this->cliArgs[($pos + 2)];
×
889
            $this->cliArgs[($pos + 1)] = '';
×
890
            $this->cliArgs[($pos + 2)] = '';
×
891
            self::setConfigData($key, $value, true);
×
892
            if (isset(self::$overriddenDefaults['runtime-set']) === false) {
×
893
                self::$overriddenDefaults['runtime-set'] = [];
×
894
            }
895

896
            self::$overriddenDefaults['runtime-set'][$key] = true;
×
897
            break;
×
898
        default:
899
            if (substr($arg, 0, 7) === 'sniffs=') {
183✔
900
                if (isset(self::$overriddenDefaults['sniffs']) === true) {
57✔
901
                    break;
3✔
902
                }
903

904
                $this->sniffs = $this->parseSniffCodes(substr($arg, 7), 'sniffs');
57✔
905
                self::$overriddenDefaults['sniffs'] = true;
21✔
906
            } else if (substr($arg, 0, 8) === 'exclude=') {
126✔
907
                if (isset(self::$overriddenDefaults['exclude']) === true) {
57✔
908
                    break;
3✔
909
                }
910

911
                $this->exclude = $this->parseSniffCodes(substr($arg, 8), 'exclude');
57✔
912
                self::$overriddenDefaults['exclude'] = true;
21✔
913
            } else if (defined('PHP_CODESNIFFER_IN_TESTS') === false
69✔
914
                && substr($arg, 0, 6) === 'cache='
69✔
915
            ) {
916
                if ((isset(self::$overriddenDefaults['cache']) === true
×
917
                    && $this->cache === false)
×
918
                    || isset(self::$overriddenDefaults['cacheFile']) === true
×
919
                ) {
920
                    break;
×
921
                }
922

923
                // Turn caching on.
924
                $this->cache = true;
×
925
                self::$overriddenDefaults['cache'] = true;
×
926

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

929
                // It may not exist and return false instead.
930
                if ($this->cacheFile === false) {
×
931
                    $this->cacheFile = substr($arg, 6);
×
932

933
                    $dir = dirname($this->cacheFile);
×
934
                    if (is_dir($dir) === false) {
×
935
                        $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
936
                        $error .= $this->printShortUsage(true);
×
937
                        throw new DeepExitException($error, 3);
×
938
                    }
939

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

951
                        if ($dir !== false) {
×
952
                            // Cache file path is relative.
953
                            $this->cacheFile = $dir.'/'.basename($this->cacheFile);
×
954
                        }
955
                    }
956
                }//end if
957

958
                self::$overriddenDefaults['cacheFile'] = true;
×
959

960
                if (is_dir($this->cacheFile) === true) {
×
961
                    $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" is a directory'.PHP_EOL.PHP_EOL;
×
962
                    $error .= $this->printShortUsage(true);
×
963
                    throw new DeepExitException($error, 3);
×
964
                }
965
            } else if (substr($arg, 0, 10) === 'bootstrap=') {
69✔
966
                $files     = explode(',', substr($arg, 10));
×
967
                $bootstrap = [];
×
968
                foreach ($files as $file) {
×
969
                    $path = Common::realpath($file);
×
970
                    if ($path === false) {
×
971
                        $error  = 'ERROR: The specified bootstrap file "'.$file.'" does not exist'.PHP_EOL.PHP_EOL;
×
972
                        $error .= $this->printShortUsage(true);
×
973
                        throw new DeepExitException($error, 3);
×
974
                    }
975

976
                    $bootstrap[] = $path;
×
977
                }
978

979
                $this->bootstrap = array_merge($this->bootstrap, $bootstrap);
×
980
                self::$overriddenDefaults['bootstrap'] = true;
×
981
            } else if (substr($arg, 0, 10) === 'file-list=') {
69✔
982
                $fileList = substr($arg, 10);
×
983
                $path     = Common::realpath($fileList);
×
984
                if ($path === false) {
×
985
                    $error  = 'ERROR: The specified file list "'.$fileList.'" does not exist'.PHP_EOL.PHP_EOL;
×
986
                    $error .= $this->printShortUsage(true);
×
987
                    throw new DeepExitException($error, 3);
×
988
                }
989

990
                $files = file($path);
×
991
                foreach ($files as $inputFile) {
×
992
                    $inputFile = trim($inputFile);
×
993

994
                    // Skip empty lines.
995
                    if ($inputFile === '') {
×
996
                        continue;
×
997
                    }
998

999
                    $this->processFilePath($inputFile);
×
1000
                }
1001
            } else if (substr($arg, 0, 11) === 'stdin-path=') {
69✔
1002
                if (isset(self::$overriddenDefaults['stdinPath']) === true) {
×
1003
                    break;
×
1004
                }
1005

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

1008
                // It may not exist and return false instead, so use whatever they gave us.
1009
                if ($this->stdinPath === false) {
×
1010
                    $this->stdinPath = trim(substr($arg, 11));
×
1011
                }
1012

1013
                self::$overriddenDefaults['stdinPath'] = true;
×
1014
            } else if (PHP_CODESNIFFER_CBF === false && substr($arg, 0, 12) === 'report-file=') {
69✔
1015
                if (isset(self::$overriddenDefaults['reportFile']) === true) {
×
1016
                    break;
×
1017
                }
1018

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

1021
                // It may not exist and return false instead.
1022
                if ($this->reportFile === false) {
×
1023
                    $this->reportFile = substr($arg, 12);
×
1024

1025
                    $dir = Common::realpath(dirname($this->reportFile));
×
1026
                    if (is_dir($dir) === false) {
×
1027
                        $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1028
                        $error .= $this->printShortUsage(true);
×
1029
                        throw new DeepExitException($error, 3);
×
1030
                    }
1031

1032
                    $this->reportFile = $dir.'/'.basename($this->reportFile);
×
1033
                }//end if
1034

1035
                self::$overriddenDefaults['reportFile'] = true;
×
1036

1037
                if (is_dir($this->reportFile) === true) {
×
1038
                    $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" is a directory'.PHP_EOL.PHP_EOL;
×
1039
                    $error .= $this->printShortUsage(true);
×
1040
                    throw new DeepExitException($error, 3);
×
1041
                }
1042
            } else if (substr($arg, 0, 13) === 'report-width=') {
69✔
1043
                if (isset(self::$overriddenDefaults['reportWidth']) === true) {
9✔
1044
                    break;
3✔
1045
                }
1046

1047
                $this->reportWidth = substr($arg, 13);
9✔
1048
                self::$overriddenDefaults['reportWidth'] = true;
9✔
1049
            } else if (substr($arg, 0, 9) === 'basepath=') {
69✔
1050
                if (isset(self::$overriddenDefaults['basepath']) === true) {
×
1051
                    break;
×
1052
                }
1053

1054
                self::$overriddenDefaults['basepath'] = true;
×
1055

1056
                if (substr($arg, 9) === '') {
×
1057
                    $this->basepath = null;
×
1058
                    break;
×
1059
                }
1060

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

1063
                // It may not exist and return false instead.
1064
                if ($this->basepath === false) {
×
1065
                    $this->basepath = substr($arg, 9);
×
1066
                }
1067

1068
                if (is_dir($this->basepath) === false) {
×
1069
                    $error  = 'ERROR: The specified basepath "'.$this->basepath.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1070
                    $error .= $this->printShortUsage(true);
×
1071
                    throw new DeepExitException($error, 3);
×
1072
                }
1073
            } else if ((substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-')) {
69✔
1074
                $reports = [];
×
1075

1076
                if ($arg[6] === '-') {
×
1077
                    // This is a report with file output.
1078
                    $split = strpos($arg, '=');
×
1079
                    if ($split === false) {
×
1080
                        $report = substr($arg, 7);
×
1081
                        $output = null;
×
1082
                    } else {
1083
                        $report = substr($arg, 7, ($split - 7));
×
1084
                        $output = substr($arg, ($split + 1));
×
1085
                        if ($output === false) {
×
1086
                            $output = null;
×
1087
                        } else {
1088
                            $dir = Common::realpath(dirname($output));
×
1089
                            if (is_dir($dir) === false) {
×
1090
                                $error  = 'ERROR: The specified '.$report.' report file path "'.$output.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1091
                                $error .= $this->printShortUsage(true);
×
1092
                                throw new DeepExitException($error, 3);
×
1093
                            }
1094

1095
                            $output = $dir.'/'.basename($output);
×
1096

1097
                            if (is_dir($output) === true) {
×
1098
                                $error  = 'ERROR: The specified '.$report.' report file path "'.$output.'" is a directory'.PHP_EOL.PHP_EOL;
×
1099
                                $error .= $this->printShortUsage(true);
×
1100
                                throw new DeepExitException($error, 3);
×
1101
                            }
1102
                        }//end if
1103
                    }//end if
1104

1105
                    $reports[$report] = $output;
×
1106
                } else {
1107
                    // This is a single report.
1108
                    if (isset(self::$overriddenDefaults['reports']) === true) {
×
1109
                        break;
×
1110
                    }
1111

1112
                    $reportNames = explode(',', substr($arg, 7));
×
1113
                    foreach ($reportNames as $report) {
×
1114
                        $reports[$report] = null;
×
1115
                    }
1116
                }//end if
1117

1118
                // Remove the default value so the CLI value overrides it.
1119
                if (isset(self::$overriddenDefaults['reports']) === false) {
×
1120
                    $this->reports = $reports;
×
1121
                } else {
1122
                    $this->reports = array_merge($this->reports, $reports);
×
1123
                }
1124

1125
                self::$overriddenDefaults['reports'] = true;
×
1126
            } else if (substr($arg, 0, 7) === 'filter=') {
69✔
1127
                if (isset(self::$overriddenDefaults['filter']) === true) {
×
1128
                    break;
×
1129
                }
1130

1131
                $this->filter = substr($arg, 7);
×
1132
                self::$overriddenDefaults['filter'] = true;
×
1133
            } else if (substr($arg, 0, 9) === 'standard=') {
69✔
1134
                $standards = trim(substr($arg, 9));
9✔
1135
                if ($standards !== '') {
9✔
1136
                    $this->standards = explode(',', $standards);
9✔
1137
                }
1138

1139
                self::$overriddenDefaults['standards'] = true;
9✔
1140
            } else if (substr($arg, 0, 11) === 'extensions=') {
60✔
1141
                if (isset(self::$overriddenDefaults['extensions']) === true) {
30✔
1142
                    break;
3✔
1143
                }
1144

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

1162
                        $newExtensions[$ext] = 'PHP';
21✔
1163
                    }
1164
                }
1165

1166
                $this->extensions = $newExtensions;
21✔
1167
                self::$overriddenDefaults['extensions'] = true;
21✔
1168
            } else if (substr($arg, 0, 7) === 'suffix=') {
30✔
1169
                if (isset(self::$overriddenDefaults['suffix']) === true) {
×
1170
                    break;
×
1171
                }
1172

1173
                $this->suffix = substr($arg, 7);
×
1174
                self::$overriddenDefaults['suffix'] = true;
×
1175
            } else if (substr($arg, 0, 9) === 'parallel=') {
30✔
1176
                if (isset(self::$overriddenDefaults['parallel']) === true) {
×
1177
                    break;
×
1178
                }
1179

1180
                $this->parallel = max((int) substr($arg, 9), 1);
×
1181
                self::$overriddenDefaults['parallel'] = true;
×
1182
            } else if (substr($arg, 0, 9) === 'severity=') {
30✔
1183
                $this->errorSeverity   = (int) substr($arg, 9);
×
1184
                $this->warningSeverity = $this->errorSeverity;
×
1185
                if (isset(self::$overriddenDefaults['errorSeverity']) === false) {
×
1186
                    self::$overriddenDefaults['errorSeverity'] = true;
×
1187
                }
1188

1189
                if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
×
1190
                    self::$overriddenDefaults['warningSeverity'] = true;
×
1191
                }
1192
            } else if (substr($arg, 0, 15) === 'error-severity=') {
30✔
1193
                if (isset(self::$overriddenDefaults['errorSeverity']) === true) {
×
1194
                    break;
×
1195
                }
1196

1197
                $this->errorSeverity = (int) substr($arg, 15);
×
1198
                self::$overriddenDefaults['errorSeverity'] = true;
×
1199
            } else if (substr($arg, 0, 17) === 'warning-severity=') {
30✔
1200
                if (isset(self::$overriddenDefaults['warningSeverity']) === true) {
×
1201
                    break;
×
1202
                }
1203

1204
                $this->warningSeverity = (int) substr($arg, 17);
×
1205
                self::$overriddenDefaults['warningSeverity'] = true;
×
1206
            } else if (substr($arg, 0, 7) === 'ignore=') {
30✔
1207
                if (isset(self::$overriddenDefaults['ignored']) === true) {
×
1208
                    break;
×
1209
                }
1210

1211
                // Split the ignore string on commas, unless the comma is escaped
1212
                // using 1 or 3 slashes (\, or \\\,).
1213
                $patterns = preg_split(
×
1214
                    '/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/',
×
1215
                    substr($arg, 7)
×
1216
                );
1217

1218
                $ignored = [];
×
1219
                foreach ($patterns as $pattern) {
×
1220
                    $pattern = trim($pattern);
×
1221
                    if ($pattern === '') {
×
1222
                        continue;
×
1223
                    }
1224

1225
                    $ignored[$pattern] = 'absolute';
×
1226
                }
1227

1228
                $this->ignored = $ignored;
×
1229
                self::$overriddenDefaults['ignored'] = true;
×
1230
            } else if (substr($arg, 0, 10) === 'generator='
30✔
1231
                && PHP_CODESNIFFER_CBF === false
30✔
1232
            ) {
1233
                if (isset(self::$overriddenDefaults['generator']) === true) {
30✔
1234
                    break;
3✔
1235
                }
1236

1237
                $generatorName          = substr($arg, 10);
30✔
1238
                $lowerCaseGeneratorName = strtolower($generatorName);
30✔
1239

1240
                if (isset($this->validGenerators[$lowerCaseGeneratorName]) === false) {
30✔
1241
                    $validOptions = implode(', ', $this->validGenerators);
9✔
1242
                    $validOptions = substr_replace($validOptions, ' and', strrpos($validOptions, ','), 1);
9✔
1243
                    $error        = sprintf(
9✔
1244
                        'ERROR: "%s" is not a valid generator. The following generators are supported: %s.'.PHP_EOL.PHP_EOL,
9✔
1245
                        $generatorName,
9✔
1246
                        $validOptions
9✔
1247
                    );
6✔
1248
                    $error       .= $this->printShortUsage(true);
9✔
1249
                    throw new DeepExitException($error, 3);
9✔
1250
                }
1251

1252
                $this->generator = $this->validGenerators[$lowerCaseGeneratorName];
21✔
1253
                self::$overriddenDefaults['generator'] = true;
21✔
1254
            } else if (substr($arg, 0, 9) === 'encoding=') {
×
1255
                if (isset(self::$overriddenDefaults['encoding']) === true) {
×
1256
                    break;
×
1257
                }
1258

1259
                $this->encoding = strtolower(substr($arg, 9));
×
1260
                self::$overriddenDefaults['encoding'] = true;
×
1261
            } else if (substr($arg, 0, 10) === 'tab-width=') {
×
1262
                if (isset(self::$overriddenDefaults['tabWidth']) === true) {
×
1263
                    break;
×
1264
                }
1265

1266
                $this->tabWidth = (int) substr($arg, 10);
×
1267
                self::$overriddenDefaults['tabWidth'] = true;
×
1268
            } else {
1269
                if ($this->dieOnUnknownArg === false) {
×
1270
                    $eqPos = strpos($arg, '=');
×
1271
                    try {
1272
                        $unknown = $this->unknown;
×
1273

1274
                        if ($eqPos === false) {
×
1275
                            $unknown[$arg] = $arg;
×
1276
                        } else {
1277
                            $value         = substr($arg, ($eqPos + 1));
×
1278
                            $arg           = substr($arg, 0, $eqPos);
×
1279
                            $unknown[$arg] = $value;
×
1280
                        }
1281

1282
                        $this->unknown = $unknown;
×
1283
                    } catch (RuntimeException $e) {
×
1284
                        // Value is not valid, so just ignore it.
1285
                    }
1286
                } else {
1287
                    $this->processUnknownArgument('--'.$arg, $pos);
×
1288
                }
1289
            }//end if
1290
            break;
93✔
1291
        }//end switch
1292

1293
    }//end processLongArgument()
31✔
1294

1295

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

1310
        $possibleSniffs = array_filter(explode(',', $input));
114✔
1311

1312
        if ($possibleSniffs === []) {
114✔
1313
            $errors[] = 'No codes specified / empty argument';
18✔
1314
        }
1315

1316
        foreach ($possibleSniffs as $sniff) {
114✔
1317
            $sniff = trim($sniff);
96✔
1318

1319
            $partCount = substr_count($sniff, '.');
96✔
1320
            if ($partCount === 2) {
96✔
1321
                // Correct number of parts.
1322
                $sniffs[] = $sniff;
54✔
1323
                continue;
54✔
1324
            }
1325

1326
            if ($partCount === 0) {
54✔
1327
                $errors[] = 'Standard codes are not supported: '.$sniff;
12✔
1328
            } else if ($partCount === 1) {
42✔
1329
                $errors[] = 'Category codes are not supported: '.$sniff;
18✔
1330
            } else if ($partCount === 3) {
24✔
1331
                $errors[] = 'Message codes are not supported: '.$sniff;
18✔
1332
            } else {
1333
                $errors[] = 'Too many parts: '.$sniff;
12✔
1334
            }
1335

1336
            if ($partCount > 2) {
54✔
1337
                $parts    = explode('.', $sniff, 4);
24✔
1338
                $sniffs[] = $parts[0].'.'.$parts[1].'.'.$parts[2];
24✔
1339
            }
1340
        }//end foreach
1341

1342
        $sniffs = array_reduce(
114✔
1343
            $sniffs,
114✔
1344
            static function ($carry, $item) {
76✔
1345
                $lower = strtolower($item);
78✔
1346

1347
                foreach ($carry as $found) {
78✔
1348
                    if ($lower === strtolower($found)) {
36✔
1349
                        // This sniff is already in our list.
1350
                        return $carry;
24✔
1351
                    }
1352
                }
1353

1354
                $carry[] = $item;
78✔
1355

1356
                return $carry;
78✔
1357
            },
114✔
1358
            []
114✔
1359
        );
76✔
1360

1361
        if ($errors !== []) {
114✔
1362
            $error  = 'ERROR: The --'.$argument.' option only supports sniff codes.'.PHP_EOL;
72✔
1363
            $error .= 'Sniff codes are in the form "Standard.Category.Sniff".'.PHP_EOL;
72✔
1364
            $error .= PHP_EOL;
72✔
1365
            $error .= 'The following problems were detected:'.PHP_EOL;
72✔
1366
            $error .= '* '.implode(PHP_EOL.'* ', $errors).PHP_EOL;
72✔
1367

1368
            if ($sniffs !== []) {
72✔
1369
                $error .= PHP_EOL;
36✔
1370
                $error .= 'Perhaps try --'.$argument.'="'.implode(',', $sniffs).'" instead.'.PHP_EOL;
36✔
1371
            }
1372

1373
            $error .= PHP_EOL;
72✔
1374
            $error .= $this->printShortUsage(true);
72✔
1375
            throw new DeepExitException(ltrim($error), 3);
72✔
1376
        }
1377

1378
        return $sniffs;
42✔
1379

1380
    }//end parseSniffCodes()
1381

1382

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

1402
            $error  = "ERROR: option \"$arg\" not known".PHP_EOL.PHP_EOL;
×
1403
            $error .= $this->printShortUsage(true);
×
1404
            throw new DeepExitException($error, 3);
×
1405
        }
1406

1407
        $this->processFilePath($arg);
×
1408

1409
    }//end processUnknownArgument()
1410

1411

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

1427
        $file = Common::realpath($path);
×
1428
        if (file_exists($file) === false) {
×
1429
            if ($this->dieOnUnknownArg === false) {
×
1430
                return;
×
1431
            }
1432

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

1445
    }//end processFilePath()
1446

1447

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

1457
        if (PHP_CODESNIFFER_CBF === true) {
×
1458
            $this->printPHPCBFUsage();
×
1459
        } else {
1460
            $this->printPHPCSUsage();
×
1461
        }
1462

1463
        echo PHP_EOL;
×
1464

1465
    }//end printUsage()
1466

1467

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

1484
        $usage .= PHP_EOL.PHP_EOL;
×
1485

1486
        if ($return === true) {
×
1487
            return $usage;
×
1488
        }
1489

1490
        echo $usage;
×
1491

1492
    }//end printShortUsage()
1493

1494

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

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

1516
        (new Help($this, $longOptions, $shortOptions))->display();
×
1517

1518
    }//end printPHPCSUsage()
1519

1520

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

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

1534
    }//end printPHPCBFUsage()
1535

1536

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

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

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

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

1560
    }//end getConfigData()
1561

1562

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

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

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

UNCOV
1587
        if (stripos(PHP_OS, 'WIN') === 0) {
×
UNCOV
1588
            $cmd = 'where '.escapeshellarg($name).' 2> nul';
×
1589
        } else {
UNCOV
1590
            $cmd = 'which '.escapeshellarg($name).' 2> /dev/null';
×
1591
        }
1592

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

UNCOV
1598
        self::$executablePaths[$name] = $result;
×
UNCOV
1599
        return $result;
×
1600

1601
    }//end getExecutablePath()
1602

1603

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

1627
        if ($temp === false) {
×
1628
            $path = '';
×
1629
            if (is_callable('\Phar::running') === true) {
×
1630
                $path = Phar::running(false);
×
1631
            }
1632

1633
            if ($path !== '') {
×
1634
                $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1635
            } else {
1636
                $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1637
            }
1638

1639
            if (is_file($configFile) === true
×
1640
                && is_writable($configFile) === false
×
1641
            ) {
1642
                $error = 'ERROR: Config file '.$configFile.' is not writable'.PHP_EOL.PHP_EOL;
×
1643
                throw new DeepExitException($error, 3);
×
1644
            }
1645
        }//end if
1646

1647
        $phpCodeSnifferConfig = self::getAllConfigData();
×
1648

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

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

1662
            if (file_put_contents($configFile, $output) === false) {
×
1663
                $error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL;
×
1664
                throw new DeepExitException($error, 3);
×
1665
            }
1666

1667
            self::$configDataFile = $configFile;
×
1668
        }
1669

1670
        self::$configData = $phpCodeSnifferConfig;
×
1671

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

1681
        return true;
×
1682

1683
    }//end setConfigData()
1684

1685

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

1699
        $path = '';
×
1700
        if (is_callable('\Phar::running') === true) {
×
1701
            $path = Phar::running(false);
×
1702
        }
1703

1704
        if ($path !== '') {
×
1705
            $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1706
        } else {
1707
            $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1708
            if (is_file($configFile) === false
×
1709
                && strpos('@data_dir@', '@data_dir') === false
×
1710
            ) {
1711
                $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
×
1712
            }
1713
        }
1714

1715
        if (is_file($configFile) === false) {
×
1716
            self::$configData = [];
×
1717
            return [];
×
1718
        }
1719

1720
        if (Common::isReadable($configFile) === false) {
×
1721
            $error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL;
×
1722
            throw new DeepExitException($error, 3);
×
1723
        }
1724

1725
        include $configFile;
×
1726
        self::$configDataFile = $configFile;
×
1727
        self::$configData     = $phpCodeSnifferConfig;
×
1728
        return self::$configData;
×
1729

1730
    }//end getAllConfigData()
1731

1732

1733
    /**
1734
     * Prints out the gathered config data.
1735
     *
1736
     * @param array $data The config data to print.
1737
     *
1738
     * @return void
1739
     */
1740
    public function printConfigData($data)
×
1741
    {
1742
        $max  = 0;
×
1743
        $keys = array_keys($data);
×
1744
        foreach ($keys as $key) {
×
1745
            $len = strlen($key);
×
1746
            if (strlen($key) > $max) {
×
1747
                $max = $len;
×
1748
            }
1749
        }
1750

1751
        if ($max === 0) {
×
1752
            return;
×
1753
        }
1754

1755
        $max += 2;
×
1756
        ksort($data);
×
1757
        foreach ($data as $name => $value) {
×
1758
            echo str_pad($name.': ', $max).$value.PHP_EOL;
×
1759
        }
1760

1761
    }//end printConfigData()
1762

1763

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

© 2025 Coveralls, Inc