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

PHPCSStandards / PHP_CodeSniffer / 12716176126

10 Jan 2025 07:43PM UTC coverage: 78.345% (+0.03%) from 78.318%
12716176126

Pull #344

github

web-flow
Merge d1bfb0a2c into b43e1d8e8
Pull Request #344: Improve error message for an invalid sniff code

2 of 30 new or added lines in 1 file covered. (6.67%)

68 existing lines in 1 file now uncovered.

24507 of 31281 relevant lines covered (78.34%)

66.0 hits per line

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

31.16
/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 to use.
68
 *                                                  E.g., array('inc' => 'PHP');
69
 * @property array<string, string|null> $reports    The reports to use for printing output after the run.
70
 *                                                  The format of the array is:
71
 *                                                      array(
72
 *                                                          'reportName1' => 'outputFile',
73
 *                                                          'reportName2' => null,
74
 *                                                      );
75
 *                                                  If the array value is NULL, the report will be written to the screen.
76
 *
77
 * @property string[] $unknown Any arguments gathered on the command line that are unknown to us.
78
 *                             E.g., using `phpcs -c` will give array('c');
79
 */
80
class Config
81
{
82

83
    /**
84
     * The current version.
85
     *
86
     * @var string
87
     */
88
    const VERSION = '3.11.3';
89

90
    /**
91
     * Package stability; either stable, beta or alpha.
92
     *
93
     * @var string
94
     */
95
    const STABILITY = 'stable';
96

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

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

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

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

173
    /**
174
     * Command line values that the user has supplied directly.
175
     *
176
     * @var array<string, true|array<string, true>>
177
     */
178
    private static $overriddenDefaults = [];
179

180
    /**
181
     * Config file data that has been loaded for the run.
182
     *
183
     * @var array<string, string>
184
     */
185
    private static $configData = null;
186

187
    /**
188
     * The full path to the config data file that has been loaded.
189
     *
190
     * @var string
191
     */
192
    private static $configDataFile = null;
193

194
    /**
195
     * Automatically discovered executable utility paths.
196
     *
197
     * @var array<string, string>
198
     */
199
    private static $executablePaths = [];
200

201

202
    /**
203
     * Get the value of an inaccessible property.
204
     *
205
     * @param string $name The name of the property.
206
     *
207
     * @return mixed
208
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
209
     */
210
    public function __get($name)
48✔
211
    {
212
        if (array_key_exists($name, $this->settings) === false) {
48✔
213
            throw new RuntimeException("ERROR: unable to get value of property \"$name\"");
×
214
        }
215

216
        // Figure out what the terminal width needs to be for "auto".
217
        if ($name === 'reportWidth' && $this->settings[$name] === 'auto') {
48✔
218
            if (function_exists('shell_exec') === true) {
9✔
219
                $dimensions = shell_exec('stty size 2>&1');
9✔
220
                if (is_string($dimensions) === true && preg_match('|\d+ (\d+)|', $dimensions, $matches) === 1) {
9✔
221
                    $this->settings[$name] = (int) $matches[1];
×
222
                }
223
            }
3✔
224

225
            if ($this->settings[$name] === 'auto') {
9✔
226
                // If shell_exec wasn't available or didn't yield a usable value, set to the default.
227
                // This will prevent subsequent retrievals of the reportWidth from making another call to stty.
228
                $this->settings[$name] = self::DEFAULT_REPORT_WIDTH;
9✔
229
            }
3✔
230
        }
3✔
231

232
        return $this->settings[$name];
48✔
233

234
    }//end __get()
235

236

237
    /**
238
     * Set the value of an inaccessible property.
239
     *
240
     * @param string $name  The name of the property.
241
     * @param mixed  $value The value of the property.
242
     *
243
     * @return void
244
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
245
     */
246
    public function __set($name, $value)
48✔
247
    {
248
        if (array_key_exists($name, $this->settings) === false) {
48✔
249
            throw new RuntimeException("Can't __set() $name; setting doesn't exist");
×
250
        }
251

252
        switch ($name) {
16✔
253
        case 'reportWidth' :
48✔
254
            if (is_string($value) === true && $value === 'auto') {
48✔
255
                // Nothing to do. Leave at 'auto'.
256
                break;
48✔
257
            }
258

259
            if (is_int($value) === true) {
39✔
260
                $value = abs($value);
6✔
261
            } else if (is_string($value) === true && preg_match('`^\d+$`', $value) === 1) {
35✔
262
                $value = (int) $value;
15✔
263
            } else {
5✔
264
                $value = self::DEFAULT_REPORT_WIDTH;
18✔
265
            }
266
            break;
39✔
267

268
        case 'standards' :
48✔
269
            $cleaned = [];
48✔
270

271
            // Check if the standard name is valid, or if the case is invalid.
272
            $installedStandards = Standards::getInstalledStandards();
48✔
273
            foreach ($value as $standard) {
48✔
274
                foreach ($installedStandards as $validStandard) {
48✔
275
                    if (strtolower($standard) === strtolower($validStandard)) {
48✔
276
                        $standard = $validStandard;
48✔
277
                        break;
48✔
278
                    }
279
                }
16✔
280

281
                $cleaned[] = $standard;
48✔
282
            }
16✔
283

284
            $value = $cleaned;
48✔
285
            break;
48✔
286

287
        // Only track time when explicitly needed.
288
        case 'verbosity':
48✔
289
            if ($value > 2) {
48✔
290
                $this->settings['trackTime'] = true;
×
291
            }
292
            break;
48✔
293
        case 'reports':
48✔
294
            $reports = array_change_key_case($value, CASE_LOWER);
48✔
295
            if (array_key_exists('performance', $reports) === true) {
48✔
296
                $this->settings['trackTime'] = true;
×
297
            }
298
            break;
48✔
299

300
        default :
16✔
301
            // No validation required.
302
            break;
48✔
303
        }//end switch
16✔
304

305
        $this->settings[$name] = $value;
48✔
306

307
    }//end __set()
32✔
308

309

310
    /**
311
     * Check if the value of an inaccessible property is set.
312
     *
313
     * @param string $name The name of the property.
314
     *
315
     * @return bool
316
     */
317
    public function __isset($name)
×
318
    {
319
        return isset($this->settings[$name]);
×
320

321
    }//end __isset()
322

323

324
    /**
325
     * Unset the value of an inaccessible property.
326
     *
327
     * @param string $name The name of the property.
328
     *
329
     * @return void
330
     */
331
    public function __unset($name)
×
332
    {
333
        $this->settings[$name] = null;
×
334

335
    }//end __unset()
336

337

338
    /**
339
     * Get the array of all config settings.
340
     *
341
     * @return array<string, mixed>
342
     */
343
    public function getSettings()
×
344
    {
345
        return $this->settings;
×
346

347
    }//end getSettings()
348

349

350
    /**
351
     * Set the array of all config settings.
352
     *
353
     * @param array<string, mixed> $settings The array of config settings.
354
     *
355
     * @return void
356
     */
357
    public function setSettings($settings)
×
358
    {
359
        return $this->settings = $settings;
×
360

361
    }//end setSettings()
362

363

364
    /**
365
     * Creates a Config object and populates it with command line values.
366
     *
367
     * @param array $cliArgs         An array of values gathered from CLI args.
368
     * @param bool  $dieOnUnknownArg Whether or not to kill the process when an
369
     *                               unknown command line arg is found.
370
     *
371
     * @return void
372
     */
373
    public function __construct(array $cliArgs=[], $dieOnUnknownArg=true)
×
374
    {
375
        if (defined('PHP_CODESNIFFER_IN_TESTS') === true) {
×
376
            // Let everything through during testing so that we can
377
            // make use of PHPUnit command line arguments as well.
378
            $this->dieOnUnknownArg = false;
×
379
        } else {
380
            $this->dieOnUnknownArg = $dieOnUnknownArg;
×
381
        }
382

383
        if (empty($cliArgs) === true) {
×
384
            $cliArgs = $_SERVER['argv'];
×
385
            array_shift($cliArgs);
×
386
        }
387

388
        $this->restoreDefaults();
×
389
        $this->setCommandLineValues($cliArgs);
×
390

391
        if (isset(self::$overriddenDefaults['standards']) === false) {
×
392
            // They did not supply a standard to use.
393
            // Look for a default ruleset in the current directory or higher.
394
            $currentDir = getcwd();
×
395

396
            $defaultFiles = [
397
                '.phpcs.xml',
×
398
                'phpcs.xml',
399
                '.phpcs.xml.dist',
400
                'phpcs.xml.dist',
401
            ];
402

403
            do {
404
                foreach ($defaultFiles as $defaultFilename) {
×
405
                    $default = $currentDir.DIRECTORY_SEPARATOR.$defaultFilename;
×
406
                    if (is_file($default) === true) {
×
407
                        $this->standards = [$default];
×
408
                        break(2);
×
409
                    }
410
                }
411

412
                $lastDir    = $currentDir;
×
413
                $currentDir = dirname($currentDir);
×
414
            } while ($currentDir !== '.' && $currentDir !== $lastDir && Common::isReadable($currentDir) === true);
×
415
        }//end if
416

417
        if (defined('STDIN') === false
×
418
            || stripos(PHP_OS, 'WIN') === 0
×
419
        ) {
420
            return;
×
421
        }
422

423
        $handle = fopen('php://stdin', 'r');
×
424

425
        // Check for content on STDIN.
426
        if ($this->stdin === true
×
427
            || (Common::isStdinATTY() === false
×
428
            && feof($handle) === false)
×
429
        ) {
430
            $readStreams = [$handle];
×
431
            $writeSteams = null;
×
432

433
            $fileContents = '';
×
434
            while (is_resource($handle) === true && feof($handle) === false) {
×
435
                // Set a timeout of 200ms.
436
                if (stream_select($readStreams, $writeSteams, $writeSteams, 0, 200000) === 0) {
×
437
                    break;
×
438
                }
439

440
                $fileContents .= fgets($handle);
×
441
            }
442

443
            if (trim($fileContents) !== '') {
×
444
                $this->stdin        = true;
×
445
                $this->stdinContent = $fileContents;
×
446
                self::$overriddenDefaults['stdin']        = true;
×
447
                self::$overriddenDefaults['stdinContent'] = true;
×
448
            }
449
        }//end if
450

451
        fclose($handle);
×
452

453
    }//end __construct()
454

455

456
    /**
457
     * Set the command line values.
458
     *
459
     * @param array $args An array of command line arguments to set.
460
     *
461
     * @return void
462
     */
463
    public function setCommandLineValues($args)
×
464
    {
465
        $this->cliArgs = $args;
×
466
        $numArgs       = count($args);
×
467

468
        for ($i = 0; $i < $numArgs; $i++) {
×
469
            $arg = $this->cliArgs[$i];
×
470
            if ($arg === '') {
×
471
                continue;
×
472
            }
473

474
            if ($arg[0] === '-') {
×
475
                if ($arg === '-') {
×
476
                    // Asking to read from STDIN.
477
                    $this->stdin = true;
×
478
                    self::$overriddenDefaults['stdin'] = true;
×
479
                    continue;
×
480
                }
481

482
                if ($arg === '--') {
×
483
                    // Empty argument, ignore it.
484
                    continue;
×
485
                }
486

487
                if ($arg[1] === '-') {
×
488
                    $this->processLongArgument(substr($arg, 2), $i);
×
489
                } else {
490
                    $switches = str_split($arg);
×
491
                    foreach ($switches as $switch) {
×
492
                        if ($switch === '-') {
×
493
                            continue;
×
494
                        }
495

496
                        $this->processShortArgument($switch, $i);
×
497
                    }
498
                }
499
            } else {
500
                $this->processUnknownArgument($arg, $i);
×
501
            }//end if
502
        }//end for
503

504
    }//end setCommandLineValues()
505

506

507
    /**
508
     * Restore default values for all possible command line arguments.
509
     *
510
     * @return void
511
     */
512
    public function restoreDefaults()
9✔
513
    {
514
        $this->files           = [];
9✔
515
        $this->standards       = ['PEAR'];
9✔
516
        $this->verbosity       = 0;
9✔
517
        $this->interactive     = false;
9✔
518
        $this->cache           = false;
9✔
519
        $this->cacheFile       = null;
9✔
520
        $this->colors          = false;
9✔
521
        $this->explain         = false;
9✔
522
        $this->local           = false;
9✔
523
        $this->showSources     = false;
9✔
524
        $this->showProgress    = false;
9✔
525
        $this->quiet           = false;
9✔
526
        $this->annotations     = true;
9✔
527
        $this->parallel        = 1;
9✔
528
        $this->tabWidth        = 0;
9✔
529
        $this->encoding        = 'utf-8';
9✔
530
        $this->extensions      = [
9✔
531
            'php' => 'PHP',
6✔
532
            'inc' => 'PHP',
6✔
533
            'js'  => 'JS',
6✔
534
            'css' => 'CSS',
6✔
535
        ];
3✔
536
        $this->sniffs          = [];
9✔
537
        $this->exclude         = [];
9✔
538
        $this->ignored         = [];
9✔
539
        $this->reportFile      = null;
9✔
540
        $this->generator       = null;
9✔
541
        $this->filter          = null;
9✔
542
        $this->bootstrap       = [];
9✔
543
        $this->basepath        = null;
9✔
544
        $this->reports         = ['full' => null];
9✔
545
        $this->reportWidth     = 'auto';
9✔
546
        $this->errorSeverity   = 5;
9✔
547
        $this->warningSeverity = 5;
9✔
548
        $this->recordErrors    = true;
9✔
549
        $this->suffix          = '';
9✔
550
        $this->stdin           = false;
9✔
551
        $this->stdinContent    = null;
9✔
552
        $this->stdinPath       = null;
9✔
553
        $this->trackTime       = false;
9✔
554
        $this->unknown         = [];
9✔
555

556
        $standard = self::getConfigData('default_standard');
9✔
557
        if ($standard !== null) {
9✔
558
            $this->standards = explode(',', $standard);
6✔
559
        }
2✔
560

561
        $reportFormat = self::getConfigData('report_format');
9✔
562
        if ($reportFormat !== null) {
9✔
563
            $this->reports = [$reportFormat => null];
×
564
        }
565

566
        $tabWidth = self::getConfigData('tab_width');
9✔
567
        if ($tabWidth !== null) {
9✔
568
            $this->tabWidth = (int) $tabWidth;
×
569
        }
570

571
        $encoding = self::getConfigData('encoding');
9✔
572
        if ($encoding !== null) {
9✔
573
            $this->encoding = strtolower($encoding);
×
574
        }
575

576
        $severity = self::getConfigData('severity');
9✔
577
        if ($severity !== null) {
9✔
578
            $this->errorSeverity   = (int) $severity;
×
579
            $this->warningSeverity = (int) $severity;
×
580
        }
581

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

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

592
        $showWarnings = self::getConfigData('show_warnings');
9✔
593
        if ($showWarnings !== null) {
9✔
594
            $showWarnings = (bool) $showWarnings;
3✔
595
            if ($showWarnings === false) {
3✔
596
                $this->warningSeverity = 0;
3✔
597
            }
1✔
598
        }
1✔
599

600
        $reportWidth = self::getConfigData('report_width');
9✔
601
        if ($reportWidth !== null) {
9✔
602
            $this->reportWidth = $reportWidth;
3✔
603
        }
1✔
604

605
        $showProgress = self::getConfigData('show_progress');
9✔
606
        if ($showProgress !== null) {
9✔
607
            $this->showProgress = (bool) $showProgress;
×
608
        }
609

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

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

620
        if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
9✔
621
            $cache = self::getConfigData('cache');
×
622
            if ($cache !== null) {
×
623
                $this->cache = (bool) $cache;
×
624
            }
625

626
            $parallel = self::getConfigData('parallel');
×
627
            if ($parallel !== null) {
×
628
                $this->parallel = max((int) $parallel, 1);
×
629
            }
630
        }
631

632
    }//end restoreDefaults()
6✔
633

634

635
    /**
636
     * Processes a short (-e) command line argument.
637
     *
638
     * @param string $arg The command line argument.
639
     * @param int    $pos The position of the argument on the command line.
640
     *
641
     * @return void
642
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
643
     */
644
    public function processShortArgument($arg, $pos)
×
645
    {
646
        switch ($arg) {
647
        case 'h':
×
648
        case '?':
×
649
            ob_start();
×
650
            $this->printUsage();
×
651
            $output = ob_get_contents();
×
652
            ob_end_clean();
×
653
            throw new DeepExitException($output, 0);
×
654
        case 'i' :
×
655
            ob_start();
×
656
            Standards::printInstalledStandards();
×
657
            $output = ob_get_contents();
×
658
            ob_end_clean();
×
659
            throw new DeepExitException($output, 0);
×
660
        case 'v' :
×
661
            if ($this->quiet === true) {
×
662
                // Ignore when quiet mode is enabled.
663
                break;
×
664
            }
665

666
            $this->verbosity++;
×
667
            self::$overriddenDefaults['verbosity'] = true;
×
668
            break;
×
669
        case 'l' :
×
670
            $this->local = true;
×
671
            self::$overriddenDefaults['local'] = true;
×
672
            break;
×
673
        case 's' :
×
674
            $this->showSources = true;
×
675
            self::$overriddenDefaults['showSources'] = true;
×
676
            break;
×
677
        case 'a' :
×
678
            $this->interactive = true;
×
679
            self::$overriddenDefaults['interactive'] = true;
×
680
            break;
×
681
        case 'e':
×
682
            $this->explain = true;
×
683
            self::$overriddenDefaults['explain'] = true;
×
684
            break;
×
685
        case 'p' :
×
686
            if ($this->quiet === true) {
×
687
                // Ignore when quiet mode is enabled.
688
                break;
×
689
            }
690

691
            $this->showProgress = true;
×
692
            self::$overriddenDefaults['showProgress'] = true;
×
693
            break;
×
694
        case 'q' :
×
695
            // Quiet mode disables a few other settings as well.
696
            $this->quiet        = true;
×
697
            $this->showProgress = false;
×
698
            $this->verbosity    = 0;
×
699

700
            self::$overriddenDefaults['quiet'] = true;
×
701
            break;
×
702
        case 'm' :
×
703
            $this->recordErrors = false;
×
704
            self::$overriddenDefaults['recordErrors'] = true;
×
705
            break;
×
706
        case 'd' :
×
707
            $ini = explode('=', $this->cliArgs[($pos + 1)]);
×
708
            $this->cliArgs[($pos + 1)] = '';
×
709
            if (isset($ini[1]) === true) {
×
710
                ini_set($ini[0], $ini[1]);
×
711
            } else {
712
                ini_set($ini[0], true);
×
713
            }
714
            break;
×
715
        case 'n' :
×
716
            if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
×
717
                $this->warningSeverity = 0;
×
718
                self::$overriddenDefaults['warningSeverity'] = true;
×
719
            }
720
            break;
×
721
        case 'w' :
×
722
            if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
×
723
                $this->warningSeverity = $this->errorSeverity;
×
724
                self::$overriddenDefaults['warningSeverity'] = true;
×
725
            }
726
            break;
×
727
        default:
728
            if ($this->dieOnUnknownArg === false) {
×
729
                $unknown       = $this->unknown;
×
730
                $unknown[]     = $arg;
×
731
                $this->unknown = $unknown;
×
732
            } else {
733
                $this->processUnknownArgument('-'.$arg, $pos);
×
734
            }
735
        }//end switch
736

737
    }//end processShortArgument()
738

739

740
    /**
741
     * Processes a long (--example) command-line argument.
742
     *
743
     * @param string $arg The command line argument.
744
     * @param int    $pos The position of the argument on the command line.
745
     *
746
     * @return void
747
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
748
     */
749
    public function processLongArgument($arg, $pos)
135✔
750
    {
751
        switch ($arg) {
45✔
752
        case 'help':
135✔
753
            ob_start();
×
754
            $this->printUsage();
×
755
            $output = ob_get_contents();
×
756
            ob_end_clean();
×
757
            throw new DeepExitException($output, 0);
×
758
        case 'version':
135✔
759
            $output  = 'PHP_CodeSniffer version '.self::VERSION.' ('.self::STABILITY.') ';
×
760
            $output .= 'by Squiz and PHPCSStandards'.PHP_EOL;
×
761
            throw new DeepExitException($output, 0);
×
762
        case 'colors':
135✔
763
            if (isset(self::$overriddenDefaults['colors']) === true) {
×
764
                break;
×
765
            }
766

767
            $this->colors = true;
×
768
            self::$overriddenDefaults['colors'] = true;
×
769
            break;
×
770
        case 'no-colors':
135✔
771
            if (isset(self::$overriddenDefaults['colors']) === true) {
×
772
                break;
×
773
            }
774

775
            $this->colors = false;
×
776
            self::$overriddenDefaults['colors'] = true;
×
777
            break;
×
778
        case 'cache':
135✔
779
            if (isset(self::$overriddenDefaults['cache']) === true) {
×
780
                break;
×
781
            }
782

783
            if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
×
784
                $this->cache = true;
×
785
                self::$overriddenDefaults['cache'] = true;
×
786
            }
787
            break;
×
788
        case 'no-cache':
135✔
789
            if (isset(self::$overriddenDefaults['cache']) === true) {
×
790
                break;
×
791
            }
792

793
            $this->cache = false;
×
794
            self::$overriddenDefaults['cache'] = true;
×
795
            break;
×
796
        case 'ignore-annotations':
135✔
797
            if (isset(self::$overriddenDefaults['annotations']) === true) {
×
798
                break;
×
799
            }
800

801
            $this->annotations = false;
×
802
            self::$overriddenDefaults['annotations'] = true;
×
803
            break;
×
804
        case 'config-set':
135✔
805
            if (isset($this->cliArgs[($pos + 1)]) === false
×
806
                || isset($this->cliArgs[($pos + 2)]) === false
×
807
            ) {
808
                $error  = 'ERROR: Setting a config option requires a name and value'.PHP_EOL.PHP_EOL;
×
809
                $error .= $this->printShortUsage(true);
×
810
                throw new DeepExitException($error, 3);
×
811
            }
812

813
            $key     = $this->cliArgs[($pos + 1)];
×
814
            $value   = $this->cliArgs[($pos + 2)];
×
815
            $current = self::getConfigData($key);
×
816

817
            try {
818
                $this->setConfigData($key, $value);
×
819
            } catch (Exception $e) {
×
820
                throw new DeepExitException($e->getMessage().PHP_EOL, 3);
×
821
            }
822

823
            $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
824

825
            if ($current === null) {
×
826
                $output .= "Config value \"$key\" added successfully".PHP_EOL;
×
827
            } else {
828
                $output .= "Config value \"$key\" updated successfully; old value was \"$current\"".PHP_EOL;
×
829
            }
830
            throw new DeepExitException($output, 0);
×
831
        case 'config-delete':
135✔
832
            if (isset($this->cliArgs[($pos + 1)]) === false) {
×
833
                $error  = 'ERROR: Deleting a config option requires the name of the option'.PHP_EOL.PHP_EOL;
×
834
                $error .= $this->printShortUsage(true);
×
835
                throw new DeepExitException($error, 3);
×
836
            }
837

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

840
            $key     = $this->cliArgs[($pos + 1)];
×
841
            $current = self::getConfigData($key);
×
842
            if ($current === null) {
×
843
                $output .= "Config value \"$key\" has not been set".PHP_EOL;
×
844
            } else {
845
                try {
846
                    $this->setConfigData($key, null);
×
847
                } catch (Exception $e) {
×
848
                    throw new DeepExitException($e->getMessage().PHP_EOL, 3);
×
849
                }
850

851
                $output .= "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL;
×
852
            }
853
            throw new DeepExitException($output, 0);
×
854
        case 'config-show':
135✔
855
            ob_start();
×
856
            $data = self::getAllConfigData();
×
857
            echo 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
858
            $this->printConfigData($data);
×
859
            $output = ob_get_contents();
×
860
            ob_end_clean();
×
861
            throw new DeepExitException($output, 0);
×
862
        case 'runtime-set':
135✔
863
            if (isset($this->cliArgs[($pos + 1)]) === false
×
864
                || isset($this->cliArgs[($pos + 2)]) === false
×
865
            ) {
866
                $error  = 'ERROR: Setting a runtime config option requires a name and value'.PHP_EOL.PHP_EOL;
×
867
                $error .= $this->printShortUsage(true);
×
868
                throw new DeepExitException($error, 3);
×
869
            }
870

871
            $key   = $this->cliArgs[($pos + 1)];
×
872
            $value = $this->cliArgs[($pos + 2)];
×
873
            $this->cliArgs[($pos + 1)] = '';
×
874
            $this->cliArgs[($pos + 2)] = '';
×
875
            self::setConfigData($key, $value, true);
×
876
            if (isset(self::$overriddenDefaults['runtime-set']) === false) {
×
877
                self::$overriddenDefaults['runtime-set'] = [];
×
878
            }
879

880
            self::$overriddenDefaults['runtime-set'][$key] = true;
×
881
            break;
×
882
        default:
45✔
883
            if (substr($arg, 0, 7) === 'sniffs=') {
135✔
884
                if (isset(self::$overriddenDefaults['sniffs']) === true) {
57✔
885
                    break;
3✔
886
                }
887

888
                $this->sniffs = $this->parseSniffCodes(substr($arg, 7), 'sniffs');
57✔
889
                self::$overriddenDefaults['sniffs'] = true;
21✔
890
            } else if (substr($arg, 0, 8) === 'exclude=') {
85✔
891
                if (isset(self::$overriddenDefaults['exclude']) === true) {
57✔
892
                    break;
3✔
893
                }
894

895
                $this->exclude = $this->parseSniffCodes(substr($arg, 8), 'exclude');
57✔
896
                self::$overriddenDefaults['exclude'] = true;
21✔
897
            } else if (defined('PHP_CODESNIFFER_IN_TESTS') === false
28✔
898
                && substr($arg, 0, 6) === 'cache='
21✔
899
            ) {
7✔
900
                if ((isset(self::$overriddenDefaults['cache']) === true
×
901
                    && $this->cache === false)
×
902
                    || isset(self::$overriddenDefaults['cacheFile']) === true
×
903
                ) {
904
                    break;
×
905
                }
906

907
                // Turn caching on.
908
                $this->cache = true;
×
909
                self::$overriddenDefaults['cache'] = true;
×
910

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

913
                // It may not exist and return false instead.
914
                if ($this->cacheFile === false) {
×
915
                    $this->cacheFile = substr($arg, 6);
×
916

917
                    $dir = dirname($this->cacheFile);
×
918
                    if (is_dir($dir) === false) {
×
919
                        $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
920
                        $error .= $this->printShortUsage(true);
×
921
                        throw new DeepExitException($error, 3);
×
922
                    }
923

924
                    if ($dir === '.') {
×
925
                        // Passed cache file is a file in the current directory.
926
                        $this->cacheFile = getcwd().'/'.basename($this->cacheFile);
×
927
                    } else {
928
                        if ($dir[0] === '/') {
×
929
                            // An absolute path.
930
                            $dir = Common::realpath($dir);
×
931
                        } else {
932
                            $dir = Common::realpath(getcwd().'/'.$dir);
×
933
                        }
934

935
                        if ($dir !== false) {
×
936
                            // Cache file path is relative.
937
                            $this->cacheFile = $dir.'/'.basename($this->cacheFile);
×
938
                        }
939
                    }
940
                }//end if
941

942
                self::$overriddenDefaults['cacheFile'] = true;
×
943

944
                if (is_dir($this->cacheFile) === true) {
×
945
                    $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" is a directory'.PHP_EOL.PHP_EOL;
×
946
                    $error .= $this->printShortUsage(true);
×
947
                    throw new DeepExitException($error, 3);
×
948
                }
949
            } else if (substr($arg, 0, 10) === 'bootstrap=') {
21✔
950
                $files     = explode(',', substr($arg, 10));
×
951
                $bootstrap = [];
×
952
                foreach ($files as $file) {
×
953
                    $path = Common::realpath($file);
×
954
                    if ($path === false) {
×
955
                        $error  = 'ERROR: The specified bootstrap file "'.$file.'" does not exist'.PHP_EOL.PHP_EOL;
×
956
                        $error .= $this->printShortUsage(true);
×
957
                        throw new DeepExitException($error, 3);
×
958
                    }
959

960
                    $bootstrap[] = $path;
×
961
                }
962

963
                $this->bootstrap = array_merge($this->bootstrap, $bootstrap);
×
964
                self::$overriddenDefaults['bootstrap'] = true;
×
965
            } else if (substr($arg, 0, 10) === 'file-list=') {
21✔
966
                $fileList = substr($arg, 10);
×
967
                $path     = Common::realpath($fileList);
×
968
                if ($path === false) {
×
969
                    $error  = 'ERROR: The specified file list "'.$fileList.'" does not exist'.PHP_EOL.PHP_EOL;
×
970
                    $error .= $this->printShortUsage(true);
×
971
                    throw new DeepExitException($error, 3);
×
972
                }
973

974
                $files = file($path);
×
975
                foreach ($files as $inputFile) {
×
976
                    $inputFile = trim($inputFile);
×
977

978
                    // Skip empty lines.
979
                    if ($inputFile === '') {
×
980
                        continue;
×
981
                    }
982

983
                    $this->processFilePath($inputFile);
×
984
                }
985
            } else if (substr($arg, 0, 11) === 'stdin-path=') {
21✔
986
                if (isset(self::$overriddenDefaults['stdinPath']) === true) {
×
987
                    break;
×
988
                }
989

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

992
                // It may not exist and return false instead, so use whatever they gave us.
993
                if ($this->stdinPath === false) {
×
994
                    $this->stdinPath = trim(substr($arg, 11));
×
995
                }
996

997
                self::$overriddenDefaults['stdinPath'] = true;
×
998
            } else if (PHP_CODESNIFFER_CBF === false && substr($arg, 0, 12) === 'report-file=') {
21✔
999
                if (isset(self::$overriddenDefaults['reportFile']) === true) {
×
1000
                    break;
×
1001
                }
1002

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

1005
                // It may not exist and return false instead.
1006
                if ($this->reportFile === false) {
×
1007
                    $this->reportFile = substr($arg, 12);
×
1008

1009
                    $dir = Common::realpath(dirname($this->reportFile));
×
1010
                    if (is_dir($dir) === false) {
×
1011
                        $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1012
                        $error .= $this->printShortUsage(true);
×
1013
                        throw new DeepExitException($error, 3);
×
1014
                    }
1015

1016
                    $this->reportFile = $dir.'/'.basename($this->reportFile);
×
1017
                }//end if
1018

1019
                self::$overriddenDefaults['reportFile'] = true;
×
1020

1021
                if (is_dir($this->reportFile) === true) {
×
1022
                    $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" is a directory'.PHP_EOL.PHP_EOL;
×
1023
                    $error .= $this->printShortUsage(true);
×
1024
                    throw new DeepExitException($error, 3);
×
1025
                }
1026
            } else if (substr($arg, 0, 13) === 'report-width=') {
21✔
1027
                if (isset(self::$overriddenDefaults['reportWidth']) === true) {
9✔
1028
                    break;
3✔
1029
                }
1030

1031
                $this->reportWidth = substr($arg, 13);
9✔
1032
                self::$overriddenDefaults['reportWidth'] = true;
9✔
1033
            } else if (substr($arg, 0, 9) === 'basepath=') {
15✔
1034
                if (isset(self::$overriddenDefaults['basepath']) === true) {
×
1035
                    break;
×
1036
                }
1037

1038
                self::$overriddenDefaults['basepath'] = true;
×
1039

1040
                if (substr($arg, 9) === '') {
×
1041
                    $this->basepath = null;
×
1042
                    break;
×
1043
                }
1044

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

1047
                // It may not exist and return false instead.
1048
                if ($this->basepath === false) {
×
1049
                    $this->basepath = substr($arg, 9);
×
1050
                }
1051

1052
                if (is_dir($this->basepath) === false) {
×
1053
                    $error  = 'ERROR: The specified basepath "'.$this->basepath.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1054
                    $error .= $this->printShortUsage(true);
×
1055
                    throw new DeepExitException($error, 3);
×
1056
                }
1057
            } else if ((substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-')) {
12✔
1058
                $reports = [];
×
1059

1060
                if ($arg[6] === '-') {
×
1061
                    // This is a report with file output.
1062
                    $split = strpos($arg, '=');
×
1063
                    if ($split === false) {
×
1064
                        $report = substr($arg, 7);
×
1065
                        $output = null;
×
1066
                    } else {
1067
                        $report = substr($arg, 7, ($split - 7));
×
1068
                        $output = substr($arg, ($split + 1));
×
1069
                        if ($output === false) {
×
1070
                            $output = null;
×
1071
                        } else {
1072
                            $dir = Common::realpath(dirname($output));
×
1073
                            if (is_dir($dir) === false) {
×
1074
                                $error  = 'ERROR: The specified '.$report.' report file path "'.$output.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
×
1075
                                $error .= $this->printShortUsage(true);
×
1076
                                throw new DeepExitException($error, 3);
×
1077
                            }
1078

1079
                            $output = $dir.'/'.basename($output);
×
1080

1081
                            if (is_dir($output) === true) {
×
1082
                                $error  = 'ERROR: The specified '.$report.' report file path "'.$output.'" is a directory'.PHP_EOL.PHP_EOL;
×
1083
                                $error .= $this->printShortUsage(true);
×
1084
                                throw new DeepExitException($error, 3);
×
1085
                            }
1086
                        }//end if
1087
                    }//end if
1088

1089
                    $reports[$report] = $output;
×
1090
                } else {
1091
                    // This is a single report.
1092
                    if (isset(self::$overriddenDefaults['reports']) === true) {
×
1093
                        break;
×
1094
                    }
1095

1096
                    $reportNames = explode(',', substr($arg, 7));
×
1097
                    foreach ($reportNames as $report) {
×
1098
                        $reports[$report] = null;
×
1099
                    }
1100
                }//end if
1101

1102
                // Remove the default value so the CLI value overrides it.
1103
                if (isset(self::$overriddenDefaults['reports']) === false) {
×
1104
                    $this->reports = $reports;
×
1105
                } else {
1106
                    $this->reports = array_merge($this->reports, $reports);
×
1107
                }
1108

1109
                self::$overriddenDefaults['reports'] = true;
×
1110
            } else if (substr($arg, 0, 7) === 'filter=') {
12✔
1111
                if (isset(self::$overriddenDefaults['filter']) === true) {
×
1112
                    break;
×
1113
                }
1114

1115
                $this->filter = substr($arg, 7);
×
1116
                self::$overriddenDefaults['filter'] = true;
×
1117
            } else if (substr($arg, 0, 9) === 'standard=') {
12✔
1118
                $standards = trim(substr($arg, 9));
×
1119
                if ($standards !== '') {
×
1120
                    $this->standards = explode(',', $standards);
×
1121
                }
1122

1123
                self::$overriddenDefaults['standards'] = true;
×
1124
            } else if (substr($arg, 0, 11) === 'extensions=') {
12✔
1125
                if (isset(self::$overriddenDefaults['extensions']) === true) {
×
1126
                    break;
×
1127
                }
1128

1129
                $extensions    = explode(',', substr($arg, 11));
×
1130
                $newExtensions = [];
×
1131
                foreach ($extensions as $ext) {
×
1132
                    $slash = strpos($ext, '/');
×
1133
                    if ($slash !== false) {
×
1134
                        // They specified the tokenizer too.
1135
                        list($ext, $tokenizer) = explode('/', $ext);
×
1136
                        $newExtensions[$ext]   = strtoupper($tokenizer);
×
1137
                        continue;
×
1138
                    }
1139

1140
                    if (isset($this->extensions[$ext]) === true) {
×
1141
                        $newExtensions[$ext] = $this->extensions[$ext];
×
1142
                    } else {
1143
                        $newExtensions[$ext] = 'PHP';
×
1144
                    }
1145
                }
1146

1147
                $this->extensions = $newExtensions;
×
1148
                self::$overriddenDefaults['extensions'] = true;
×
1149
            } else if (substr($arg, 0, 7) === 'suffix=') {
12✔
1150
                if (isset(self::$overriddenDefaults['suffix']) === true) {
×
1151
                    break;
×
1152
                }
1153

1154
                $this->suffix = substr($arg, 7);
×
1155
                self::$overriddenDefaults['suffix'] = true;
×
1156
            } else if (substr($arg, 0, 9) === 'parallel=') {
12✔
1157
                if (isset(self::$overriddenDefaults['parallel']) === true) {
×
1158
                    break;
×
1159
                }
1160

1161
                $this->parallel = max((int) substr($arg, 9), 1);
×
1162
                self::$overriddenDefaults['parallel'] = true;
×
1163
            } else if (substr($arg, 0, 9) === 'severity=') {
12✔
1164
                $this->errorSeverity   = (int) substr($arg, 9);
×
1165
                $this->warningSeverity = $this->errorSeverity;
×
1166
                if (isset(self::$overriddenDefaults['errorSeverity']) === false) {
×
1167
                    self::$overriddenDefaults['errorSeverity'] = true;
×
1168
                }
1169

1170
                if (isset(self::$overriddenDefaults['warningSeverity']) === false) {
×
1171
                    self::$overriddenDefaults['warningSeverity'] = true;
×
1172
                }
1173
            } else if (substr($arg, 0, 15) === 'error-severity=') {
12✔
1174
                if (isset(self::$overriddenDefaults['errorSeverity']) === true) {
×
1175
                    break;
×
1176
                }
1177

1178
                $this->errorSeverity = (int) substr($arg, 15);
×
1179
                self::$overriddenDefaults['errorSeverity'] = true;
×
1180
            } else if (substr($arg, 0, 17) === 'warning-severity=') {
12✔
1181
                if (isset(self::$overriddenDefaults['warningSeverity']) === true) {
×
1182
                    break;
×
1183
                }
1184

1185
                $this->warningSeverity = (int) substr($arg, 17);
×
1186
                self::$overriddenDefaults['warningSeverity'] = true;
×
1187
            } else if (substr($arg, 0, 7) === 'ignore=') {
12✔
1188
                if (isset(self::$overriddenDefaults['ignored']) === true) {
×
1189
                    break;
×
1190
                }
1191

1192
                // Split the ignore string on commas, unless the comma is escaped
1193
                // using 1 or 3 slashes (\, or \\\,).
1194
                $patterns = preg_split(
×
1195
                    '/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/',
×
1196
                    substr($arg, 7)
×
1197
                );
1198

1199
                $ignored = [];
×
1200
                foreach ($patterns as $pattern) {
×
1201
                    $pattern = trim($pattern);
×
1202
                    if ($pattern === '') {
×
1203
                        continue;
×
1204
                    }
1205

1206
                    $ignored[$pattern] = 'absolute';
×
1207
                }
1208

1209
                $this->ignored = $ignored;
×
1210
                self::$overriddenDefaults['ignored'] = true;
×
1211
            } else if (substr($arg, 0, 10) === 'generator='
12✔
1212
                && PHP_CODESNIFFER_CBF === false
12✔
1213
            ) {
4✔
1214
                if (isset(self::$overriddenDefaults['generator']) === true) {
12✔
1215
                    break;
3✔
1216
                }
1217

1218
                $this->generator = substr($arg, 10);
12✔
1219
                self::$overriddenDefaults['generator'] = true;
12✔
1220
            } else if (substr($arg, 0, 9) === 'encoding=') {
4✔
1221
                if (isset(self::$overriddenDefaults['encoding']) === true) {
×
1222
                    break;
×
1223
                }
1224

1225
                $this->encoding = strtolower(substr($arg, 9));
×
1226
                self::$overriddenDefaults['encoding'] = true;
×
1227
            } else if (substr($arg, 0, 10) === 'tab-width=') {
×
1228
                if (isset(self::$overriddenDefaults['tabWidth']) === true) {
×
1229
                    break;
×
1230
                }
1231

1232
                $this->tabWidth = (int) substr($arg, 10);
×
1233
                self::$overriddenDefaults['tabWidth'] = true;
×
1234
            } else {
1235
                if ($this->dieOnUnknownArg === false) {
×
1236
                    $eqPos = strpos($arg, '=');
×
1237
                    try {
1238
                        $unknown = $this->unknown;
×
1239

1240
                        if ($eqPos === false) {
×
1241
                            $unknown[$arg] = $arg;
×
1242
                        } else {
1243
                            $value         = substr($arg, ($eqPos + 1));
×
1244
                            $arg           = substr($arg, 0, $eqPos);
×
1245
                            $unknown[$arg] = $value;
×
1246
                        }
1247

1248
                        $this->unknown = $unknown;
×
1249
                    } catch (RuntimeException $e) {
×
1250
                        // Value is not valid, so just ignore it.
1251
                    }
1252
                } else {
1253
                    $this->processUnknownArgument('--'.$arg, $pos);
×
1254
                }
1255
            }//end if
1256
            break;
63✔
1257
        }//end switch
45✔
1258

1259
    }//end processLongArgument()
42✔
1260

1261

1262
    /**
1263
     * Processes an unknown command line argument.
1264
     *
1265
     * Assumes all unknown arguments are files and folders to check.
1266
     *
1267
     * @param string $arg The command line argument.
1268
     * @param int    $pos The position of the argument on the command line.
1269
     *
1270
     * @return void
1271
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
1272
     */
NEW
1273
    public function processUnknownArgument($arg, $pos)
×
1274
    {
1275
        // We don't know about any additional switches; just files.
NEW
1276
        if ($arg[0] === '-') {
×
NEW
1277
            if ($this->dieOnUnknownArg === false) {
×
NEW
1278
                return;
×
1279
            }
1280

NEW
1281
            $error  = "ERROR: option \"$arg\" not known".PHP_EOL.PHP_EOL;
×
NEW
1282
            $error .= $this->printShortUsage(true);
×
NEW
1283
            throw new DeepExitException($error, 3);
×
1284
        }
1285

NEW
1286
        $this->processFilePath($arg);
×
1287

1288
    }//end processUnknownArgument()
1289

1290

1291
    /**
1292
     * Processes a file path and add it to the file list.
1293
     *
1294
     * @param string $path The path to the file to add.
1295
     *
1296
     * @return void
1297
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
1298
     */
NEW
1299
    public function processFilePath($path)
×
1300
    {
1301
        // If we are processing STDIN, don't record any files to check.
NEW
1302
        if ($this->stdin === true) {
×
NEW
1303
            return;
×
1304
        }
1305

NEW
1306
        $file = Common::realpath($path);
×
NEW
1307
        if (file_exists($file) === false) {
×
NEW
1308
            if ($this->dieOnUnknownArg === false) {
×
NEW
1309
                return;
×
1310
            }
1311

NEW
1312
            $error  = 'ERROR: The file "'.$path.'" does not exist.'.PHP_EOL.PHP_EOL;
×
NEW
1313
            $error .= $this->printShortUsage(true);
×
NEW
1314
            throw new DeepExitException($error, 3);
×
1315
        } else {
1316
            // Can't modify the files array directly because it's not a real
1317
            // class member, so need to use this little get/modify/set trick.
NEW
1318
            $files       = $this->files;
×
NEW
1319
            $files[]     = $file;
×
NEW
1320
            $this->files = $files;
×
NEW
1321
            self::$overriddenDefaults['files'] = true;
×
1322
        }
1323

1324
    }//end processFilePath()
1325

1326

1327
    /**
1328
     * Prints out the usage information for this script.
1329
     *
1330
     * @return void
1331
     */
NEW
1332
    public function printUsage()
×
1333
    {
NEW
1334
        echo PHP_EOL;
×
1335

NEW
1336
        if (PHP_CODESNIFFER_CBF === true) {
×
NEW
1337
            $this->printPHPCBFUsage();
×
1338
        } else {
NEW
1339
            $this->printPHPCSUsage();
×
1340
        }
1341

NEW
1342
        echo PHP_EOL;
×
1343

1344
    }//end printUsage()
1345

1346

1347
    /**
1348
     * Prints out the short usage information for this script.
1349
     *
1350
     * @param bool $return If TRUE, the usage string is returned
1351
     *                     instead of output to screen.
1352
     *
1353
     * @return string|void
1354
     */
UNCOV
1355
    public function printShortUsage($return=false)
×
1356
    {
UNCOV
1357
        if (PHP_CODESNIFFER_CBF === true) {
×
UNCOV
1358
            $usage = 'Run "phpcbf --help" for usage information';
×
1359
        } else {
1360
            $usage = 'Run "phpcs --help" for usage information';
×
1361
        }
1362

1363
        $usage .= PHP_EOL.PHP_EOL;
×
1364

1365
        if ($return === true) {
×
UNCOV
1366
            return $usage;
×
1367
        }
1368

1369
        echo $usage;
×
1370

1371
    }//end printShortUsage()
1372

1373

1374
    /**
1375
     * Prints out the usage information for PHPCS.
1376
     *
1377
     * @return void
1378
     */
UNCOV
1379
    public function printPHPCSUsage()
×
1380
    {
UNCOV
1381
        $longOptions   = explode(',', Help::DEFAULT_LONG_OPTIONS);
×
UNCOV
1382
        $longOptions[] = 'cache';
×
UNCOV
1383
        $longOptions[] = 'no-cache';
×
UNCOV
1384
        $longOptions[] = 'report';
×
UNCOV
1385
        $longOptions[] = 'report-file';
×
1386
        $longOptions[] = 'report-report';
×
UNCOV
1387
        $longOptions[] = 'config-explain';
×
UNCOV
1388
        $longOptions[] = 'config-set';
×
1389
        $longOptions[] = 'config-delete';
×
1390
        $longOptions[] = 'config-show';
×
UNCOV
1391
        $longOptions[] = 'generator';
×
1392

1393
        $shortOptions = Help::DEFAULT_SHORT_OPTIONS.'aems';
×
1394

1395
        (new Help($this, $longOptions, $shortOptions))->display();
×
1396

1397
    }//end printPHPCSUsage()
1398

1399

1400
    /**
1401
     * Prints out the usage information for PHPCBF.
1402
     *
1403
     * @return void
1404
     */
1405
    public function printPHPCBFUsage()
×
1406
    {
1407
        $longOptions   = explode(',', Help::DEFAULT_LONG_OPTIONS);
×
1408
        $longOptions[] = 'suffix';
×
UNCOV
1409
        $shortOptions  = Help::DEFAULT_SHORT_OPTIONS;
×
1410

UNCOV
1411
        (new Help($this, $longOptions, $shortOptions))->display();
×
1412

1413
    }//end printPHPCBFUsage()
1414

1415

1416
    /**
1417
     * Get a single config value.
1418
     *
1419
     * @param string $key The name of the config value.
1420
     *
1421
     * @return string|null
1422
     * @see    setConfigData()
1423
     * @see    getAllConfigData()
1424
     */
1425
    public static function getConfigData($key)
6✔
1426
    {
1427
        $phpCodeSnifferConfig = self::getAllConfigData();
6✔
1428

1429
        if ($phpCodeSnifferConfig === null) {
6✔
UNCOV
1430
            return null;
×
1431
        }
1432

1433
        if (isset($phpCodeSnifferConfig[$key]) === false) {
6✔
1434
            return null;
6✔
1435
        }
1436

1437
        return $phpCodeSnifferConfig[$key];
6✔
1438

1439
    }//end getConfigData()
1440

1441

1442
    /**
1443
     * Get the path to an executable utility.
1444
     *
1445
     * @param string $name The name of the executable utility.
1446
     *
1447
     * @return string|null
1448
     * @see    getConfigData()
1449
     */
1450
    public static function getExecutablePath($name)
4✔
1451
    {
1452
        $data = self::getConfigData($name.'_path');
4✔
1453
        if ($data !== null) {
4✔
UNCOV
1454
            return $data;
×
1455
        }
1456

1457
        if ($name === "php") {
4✔
1458
            // For php, we know the executable path. There's no need to look it up.
UNCOV
1459
            return PHP_BINARY;
×
1460
        }
1461

1462
        if (array_key_exists($name, self::$executablePaths) === true) {
4✔
1463
            return self::$executablePaths[$name];
2✔
1464
        }
1465

1466
        if (stripos(PHP_OS, 'WIN') === 0) {
4✔
1467
            $cmd = 'where '.escapeshellarg($name).' 2> nul';
2✔
1468
        } else {
1✔
1469
            $cmd = 'which '.escapeshellarg($name).' 2> /dev/null';
2✔
1470
        }
1471

1472
        $result = exec($cmd, $output, $retVal);
4✔
1473
        if ($retVal !== 0) {
4✔
1474
            $result = null;
×
1475
        }
1476

1477
        self::$executablePaths[$name] = $result;
4✔
1478
        return $result;
4✔
1479

1480
    }//end getExecutablePath()
1481

1482

1483
    /**
1484
     * Set a single config value.
1485
     *
1486
     * @param string      $key   The name of the config value.
1487
     * @param string|null $value The value to set. If null, the config
1488
     *                           entry is deleted, reverting it to the
1489
     *                           default value.
1490
     * @param boolean     $temp  Set this config data temporarily for this
1491
     *                           script run. This will not write the config
1492
     *                           data to the config file.
1493
     *
1494
     * @return bool
1495
     * @see    getConfigData()
1496
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the config file can not be written.
1497
     */
1498
    public static function setConfigData($key, $value, $temp=false)
×
1499
    {
UNCOV
1500
        if (isset(self::$overriddenDefaults['runtime-set']) === true
×
UNCOV
1501
            && isset(self::$overriddenDefaults['runtime-set'][$key]) === true
×
1502
        ) {
UNCOV
1503
            return false;
×
1504
        }
1505

UNCOV
1506
        if ($temp === false) {
×
UNCOV
1507
            $path = '';
×
UNCOV
1508
            if (is_callable('\Phar::running') === true) {
×
UNCOV
1509
                $path = Phar::running(false);
×
1510
            }
1511

UNCOV
1512
            if ($path !== '') {
×
UNCOV
1513
                $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1514
            } else {
UNCOV
1515
                $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1516
            }
1517

UNCOV
1518
            if (is_file($configFile) === true
×
UNCOV
1519
                && is_writable($configFile) === false
×
1520
            ) {
UNCOV
1521
                $error = 'ERROR: Config file '.$configFile.' is not writable'.PHP_EOL.PHP_EOL;
×
UNCOV
1522
                throw new DeepExitException($error, 3);
×
1523
            }
1524
        }//end if
1525

UNCOV
1526
        $phpCodeSnifferConfig = self::getAllConfigData();
×
1527

UNCOV
1528
        if ($value === null) {
×
UNCOV
1529
            if (isset($phpCodeSnifferConfig[$key]) === true) {
×
UNCOV
1530
                unset($phpCodeSnifferConfig[$key]);
×
1531
            }
1532
        } else {
UNCOV
1533
            $phpCodeSnifferConfig[$key] = $value;
×
1534
        }
1535

UNCOV
1536
        if ($temp === false) {
×
UNCOV
1537
            $output  = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
×
UNCOV
1538
            $output .= var_export($phpCodeSnifferConfig, true);
×
UNCOV
1539
            $output .= ";\n?".'>';
×
1540

1541
            if (file_put_contents($configFile, $output) === false) {
×
UNCOV
1542
                $error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL;
×
UNCOV
1543
                throw new DeepExitException($error, 3);
×
1544
            }
1545

1546
            self::$configDataFile = $configFile;
×
1547
        }
1548

UNCOV
1549
        self::$configData = $phpCodeSnifferConfig;
×
1550

1551
        // If the installed paths are being set, make sure all known
1552
        // standards paths are added to the autoloader.
UNCOV
1553
        if ($key === 'installed_paths') {
×
UNCOV
1554
            $installedStandards = Standards::getInstalledStandardDetails();
×
UNCOV
1555
            foreach ($installedStandards as $details) {
×
UNCOV
1556
                Autoload::addSearchPath($details['path'], $details['namespace']);
×
1557
            }
1558
        }
1559

UNCOV
1560
        return true;
×
1561

1562
    }//end setConfigData()
1563

1564

1565
    /**
1566
     * Get all config data.
1567
     *
1568
     * @return array<string, string>
1569
     * @see    getConfigData()
1570
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the config file could not be read.
1571
     */
UNCOV
1572
    public static function getAllConfigData()
×
1573
    {
UNCOV
1574
        if (self::$configData !== null) {
×
UNCOV
1575
            return self::$configData;
×
1576
        }
1577

UNCOV
1578
        $path = '';
×
UNCOV
1579
        if (is_callable('\Phar::running') === true) {
×
UNCOV
1580
            $path = Phar::running(false);
×
1581
        }
1582

UNCOV
1583
        if ($path !== '') {
×
UNCOV
1584
            $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1585
        } else {
UNCOV
1586
            $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1587
            if (is_file($configFile) === false
×
1588
                && strpos('@data_dir@', '@data_dir') === false
×
1589
            ) {
1590
                $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
×
1591
            }
1592
        }
1593

1594
        if (is_file($configFile) === false) {
×
1595
            self::$configData = [];
×
1596
            return [];
×
1597
        }
1598

1599
        if (Common::isReadable($configFile) === false) {
×
1600
            $error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL;
×
UNCOV
1601
            throw new DeepExitException($error, 3);
×
1602
        }
1603

UNCOV
1604
        include $configFile;
×
1605
        self::$configDataFile = $configFile;
×
1606
        self::$configData     = $phpCodeSnifferConfig;
×
UNCOV
1607
        return self::$configData;
×
1608

1609
    }//end getAllConfigData()
1610

1611

1612
    /**
1613
     * Prints out the gathered config data.
1614
     *
1615
     * @param array $data The config data to print.
1616
     *
1617
     * @return void
1618
     */
UNCOV
1619
    public function printConfigData($data)
×
1620
    {
UNCOV
1621
        $max  = 0;
×
UNCOV
1622
        $keys = array_keys($data);
×
1623
        foreach ($keys as $key) {
×
1624
            $len = strlen($key);
×
1625
            if (strlen($key) > $max) {
×
1626
                $max = $len;
×
1627
            }
1628
        }
1629

1630
        if ($max === 0) {
×
UNCOV
1631
            return;
×
1632
        }
1633

UNCOV
1634
        $max += 2;
×
UNCOV
1635
        ksort($data);
×
1636
        foreach ($data as $name => $value) {
×
UNCOV
1637
            echo str_pad($name.': ', $max).$value.PHP_EOL;
×
1638
        }
1639

1640
    }//end printConfigData()
1641

1642

1643
    /**
1644
     * Parse supplied string into a list of validated sniff codes.
1645
     *
1646
     * @param string $input    Comma-separated string of sniff codes.
1647
     * @param string $argument The name of the argument which is being processed.
1648
     *
1649
     * @return string[]
1650
     * @throws DeepExitException When any of the provided codes are not valid as sniff codes.
1651
     */
1652
    private function parseSniffCodes($input, $argument)
114✔
1653
    {
1654
        $errors = [];
114✔
1655
        $sniffs = [];
114✔
1656

1657
        $possibleSniffs = array_filter(explode(',', $input));
114✔
1658

1659
        if ($possibleSniffs === []) {
114✔
1660
            $errors[] = 'No codes specified / empty argument';
18✔
1661
        }
6✔
1662

1663
        foreach ($possibleSniffs as $sniff) {
114✔
1664
            $sniff = trim($sniff);
96✔
1665

1666
            $partCount = substr_count($sniff, '.');
96✔
1667
            if ($partCount === 2) {
96✔
1668
                // Correct number of parts.
1669
                $sniffs[] = $sniff;
54✔
1670
                continue;
54✔
1671
            }
1672

1673
            if ($partCount === 0) {
54✔
1674
                $errors[] = 'Standard codes are not supported: '.$sniff;
12✔
1675
            } else if ($partCount === 1) {
46✔
1676
                $errors[] = 'Category codes are not supported: '.$sniff;
18✔
1677
            } else if ($partCount === 3) {
30✔
1678
                $errors[] = 'Message codes are not supported: '.$sniff;
18✔
1679
            } else {
6✔
1680
                $errors[] = 'Too many parts: '.$sniff;
12✔
1681
            }
1682

1683
            if ($partCount > 2) {
54✔
1684
                $parts    = explode('.', $sniff, 4);
24✔
1685
                $sniffs[] = $parts[0].'.'.$parts[1].'.'.$parts[2];
24✔
1686
            }
8✔
1687
        }//end foreach
38✔
1688

1689
        $sniffs = array_reduce(
114✔
1690
            $sniffs,
114✔
1691
            static function ($carry, $item) {
76✔
1692
                $lower = strtolower($item);
78✔
1693

1694
                foreach ($carry as $found) {
78✔
1695
                    if ($lower === strtolower($found)) {
36✔
1696
                        // This sniff is already in our list.
1697
                        return $carry;
24✔
1698
                    }
1699
                }
26✔
1700

1701
                $carry[] = $item;
78✔
1702

1703
                return $carry;
78✔
1704
            },
114✔
1705
            []
114✔
1706
        );
76✔
1707

1708
        if ($errors !== []) {
114✔
1709
            $error  = 'ERROR: The --'.$argument.' option only supports sniff codes.'.PHP_EOL;
72✔
1710
            $error .= 'Sniff codes are in the form "Standard.Category.Sniff".'.PHP_EOL;
72✔
1711
            $error .= PHP_EOL;
72✔
1712
            $error .= 'The following problems were detected:'.PHP_EOL;
72✔
1713
            $error .= '* '.implode(PHP_EOL.'* ', $errors).PHP_EOL;
72✔
1714

1715
            if ($sniffs !== []) {
72✔
1716
                $error .= PHP_EOL;
36✔
1717
                $error .= 'Perhaps try --'.$argument.'="'.implode(',', $sniffs).'" instead.'.PHP_EOL;
36✔
1718
            }
12✔
1719

1720
            $error .= PHP_EOL;
72✔
1721
            $error .= $this->printShortUsage(true);
72✔
1722
            throw new DeepExitException(ltrim($error), 3);
72✔
1723
        }
1724

1725
        return $sniffs;
42✔
1726

1727
    }//end parseSniffCodes()
1728

1729

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

© 2026 Coveralls, Inc