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

PHPCSStandards / PHP_CodeSniffer / 12315490458

13 Dec 2024 12:17PM UTC coverage: 78.196% (+0.007%) from 78.189%
12315490458

Pull #771

github

web-flow
Merge 7141e6d5f into 3d14d0042
Pull Request #771: Config: display user-friendly message when invalid generator is passed

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

296 existing lines in 1 file now uncovered.

24427 of 31238 relevant lines covered (78.2%)

65.24 hits per line

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

28.66
/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✔
UNCOV
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✔
UNCOV
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✔
UNCOV
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✔
UNCOV
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✔
UNCOV
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
     */
UNCOV
317
    public function __isset($name)
×
318
    {
UNCOV
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
     */
UNCOV
331
    public function __unset($name)
×
332
    {
UNCOV
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
     */
UNCOV
343
    public function getSettings()
×
344
    {
UNCOV
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
     */
UNCOV
357
    public function setSettings($settings)
×
358
    {
UNCOV
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
     */
UNCOV
373
    public function __construct(array $cliArgs=[], $dieOnUnknownArg=true)
×
374
    {
UNCOV
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.
UNCOV
378
            $this->dieOnUnknownArg = false;
×
379
        } else {
UNCOV
380
            $this->dieOnUnknownArg = $dieOnUnknownArg;
×
381
        }
382

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

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

UNCOV
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.
UNCOV
394
            $currentDir = getcwd();
×
395

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

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

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

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

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

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

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

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

443
            if (trim($fileContents) !== '') {
×
UNCOV
444
                $this->stdin        = true;
×
445
                $this->stdinContent = $fileContents;
×
446
                self::$overriddenDefaults['stdin']        = true;
×
UNCOV
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
     */
UNCOV
463
    public function setCommandLineValues($args)
×
464
    {
UNCOV
465
        $this->cliArgs = $args;
×
466
        $numArgs       = count($args);
×
467

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

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

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

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

UNCOV
496
                        $this->processShortArgument($switch, $i);
×
497
                    }
498
                }
499
            } else {
UNCOV
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✔
UNCOV
563
            $this->reports = [$reportFormat => null];
×
564
        }
565

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

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

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

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

587
        $severity = self::getConfigData('warning_severity');
9✔
588
        if ($severity !== null) {
9✔
UNCOV
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✔
UNCOV
607
            $this->showProgress = (bool) $showProgress;
×
608
        }
609

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

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

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

UNCOV
626
            $parallel = self::getConfigData('parallel');
×
627
            if ($parallel !== null) {
×
UNCOV
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
     */
UNCOV
644
    public function processShortArgument($arg, $pos)
×
645
    {
646
        switch ($arg) {
UNCOV
647
        case 'h':
×
UNCOV
648
        case '?':
×
UNCOV
649
            ob_start();
×
UNCOV
650
            $this->printUsage();
×
UNCOV
651
            $output = ob_get_contents();
×
UNCOV
652
            ob_end_clean();
×
UNCOV
653
            throw new DeepExitException($output, 0);
×
UNCOV
654
        case 'i' :
×
UNCOV
655
            ob_start();
×
UNCOV
656
            Standards::printInstalledStandards();
×
UNCOV
657
            $output = ob_get_contents();
×
UNCOV
658
            ob_end_clean();
×
659
            throw new DeepExitException($output, 0);
×
UNCOV
660
        case 'v' :
×
UNCOV
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;
×
UNCOV
677
        case 'a' :
×
678
            $this->interactive = true;
×
UNCOV
679
            self::$overriddenDefaults['interactive'] = true;
×
UNCOV
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;
×
UNCOV
702
        case 'm' :
×
703
            $this->recordErrors = false;
×
UNCOV
704
            self::$overriddenDefaults['recordErrors'] = true;
×
UNCOV
705
            break;
×
706
        case 'd' :
×
707
            $ini = explode('=', $this->cliArgs[($pos + 1)]);
×
708
            $this->cliArgs[($pos + 1)] = '';
×
709
            if (isset($ini[1]) === true) {
×
UNCOV
710
                ini_set($ini[0], $ini[1]);
×
711
            } else {
712
                ini_set($ini[0], true);
×
713
            }
UNCOV
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
            }
UNCOV
726
            break;
×
727
        default:
UNCOV
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)
87✔
750
    {
751
        switch ($arg) {
29✔
752
        case 'help':
87✔
UNCOV
753
            ob_start();
×
UNCOV
754
            $this->printUsage();
×
UNCOV
755
            $output = ob_get_contents();
×
UNCOV
756
            ob_end_clean();
×
UNCOV
757
            throw new DeepExitException($output, 0);
×
758
        case 'version':
87✔
UNCOV
759
            $output  = 'PHP_CodeSniffer version '.self::VERSION.' ('.self::STABILITY.') ';
×
UNCOV
760
            $output .= 'by Squiz and PHPCSStandards'.PHP_EOL;
×
UNCOV
761
            throw new DeepExitException($output, 0);
×
762
        case 'colors':
87✔
UNCOV
763
            if (isset(self::$overriddenDefaults['colors']) === true) {
×
UNCOV
764
                break;
×
765
            }
766

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

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

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

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

UNCOV
801
            $this->annotations = false;
×
802
            self::$overriddenDefaults['annotations'] = true;
×
UNCOV
803
            break;
×
804
        case 'config-set':
87✔
805
            if (isset($this->cliArgs[($pos + 1)]) === false
×
UNCOV
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)];
×
UNCOV
814
            $value   = $this->cliArgs[($pos + 2)];
×
UNCOV
815
            $current = self::getConfigData($key);
×
816

817
            try {
818
                $this->setConfigData($key, $value);
×
UNCOV
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) {
×
UNCOV
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':
87✔
UNCOV
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);
×
UNCOV
842
            if ($current === null) {
×
843
                $output .= "Config value \"$key\" has not been set".PHP_EOL;
×
844
            } else {
845
                try {
UNCOV
846
                    $this->setConfigData($key, null);
×
847
                } catch (Exception $e) {
×
848
                    throw new DeepExitException($e->getMessage().PHP_EOL, 3);
×
849
                }
850

UNCOV
851
                $output .= "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL;
×
852
            }
853
            throw new DeepExitException($output, 0);
×
854
        case 'config-show':
87✔
855
            ob_start();
×
856
            $data = self::getAllConfigData();
×
857
            echo 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
×
858
            $this->printConfigData($data);
×
UNCOV
859
            $output = ob_get_contents();
×
UNCOV
860
            ob_end_clean();
×
861
            throw new DeepExitException($output, 0);
×
862
        case 'runtime-set':
87✔
863
            if (isset($this->cliArgs[($pos + 1)]) === false
×
UNCOV
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;
×
UNCOV
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) {
×
UNCOV
877
                self::$overriddenDefaults['runtime-set'] = [];
×
878
            }
879

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

888
                $sniffs = explode(',', substr($arg, 7));
27✔
889
                foreach ($sniffs as $sniff) {
27✔
890
                    if (substr_count($sniff, '.') !== 2) {
27✔
891
                        $error  = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL;
18✔
892
                        $error .= $this->printShortUsage(true);
18✔
893
                        throw new DeepExitException($error, 3);
18✔
894
                    }
895
                }
4✔
896

897
                $this->sniffs = $sniffs;
9✔
898
                self::$overriddenDefaults['sniffs'] = true;
9✔
899
            } else if (substr($arg, 0, 8) === 'exclude=') {
63✔
900
                if (isset(self::$overriddenDefaults['exclude']) === true) {
27✔
901
                    break;
3✔
902
                }
903

904
                $sniffs = explode(',', substr($arg, 8));
27✔
905
                foreach ($sniffs as $sniff) {
27✔
906
                    if (substr_count($sniff, '.') !== 2) {
27✔
907
                        $error  = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL;
18✔
908
                        $error .= $this->printShortUsage(true);
18✔
909
                        throw new DeepExitException($error, 3);
18✔
910
                    }
911
                }
4✔
912

913
                $this->exclude = $sniffs;
9✔
914
                self::$overriddenDefaults['exclude'] = true;
9✔
915
            } else if (defined('PHP_CODESNIFFER_IN_TESTS') === false
36✔
916
                && substr($arg, 0, 6) === 'cache='
33✔
917
            ) {
11✔
UNCOV
918
                if ((isset(self::$overriddenDefaults['cache']) === true
×
UNCOV
919
                    && $this->cache === false)
×
UNCOV
920
                    || isset(self::$overriddenDefaults['cacheFile']) === true
×
921
                ) {
UNCOV
922
                    break;
×
923
                }
924

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

UNCOV
929
                $this->cacheFile = Common::realpath(substr($arg, 6));
×
930

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

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

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

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

UNCOV
960
                self::$overriddenDefaults['cacheFile'] = true;
×
961

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

978
                    $bootstrap[] = $path;
×
979
                }
980

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

UNCOV
992
                $files = file($path);
×
993
                foreach ($files as $inputFile) {
×
UNCOV
994
                    $inputFile = trim($inputFile);
×
995

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

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

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

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

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

UNCOV
1021
                $this->reportFile = Common::realpath(substr($arg, 12));
×
1022

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

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

UNCOV
1034
                    $this->reportFile = $dir.'/'.basename($this->reportFile);
×
1035
                }//end if
1036

UNCOV
1037
                self::$overriddenDefaults['reportFile'] = true;
×
1038

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

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

1056
                self::$overriddenDefaults['basepath'] = true;
×
1057

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

UNCOV
1063
                $this->basepath = Common::realpath(substr($arg, 9));
×
1064

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

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

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

1097
                            $output = $dir.'/'.basename($output);
×
1098

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

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

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

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

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

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

UNCOV
1141
                self::$overriddenDefaults['standards'] = true;
×
1142
            } else if (substr($arg, 0, 11) === 'extensions=') {
24✔
UNCOV
1143
                if (isset(self::$overriddenDefaults['extensions']) === true) {
×
1144
                    break;
×
1145
                }
1146

UNCOV
1147
                $extensions    = explode(',', substr($arg, 11));
×
1148
                $newExtensions = [];
×
1149
                foreach ($extensions as $ext) {
×
UNCOV
1150
                    $slash = strpos($ext, '/');
×
1151
                    if ($slash !== false) {
×
1152
                        // They specified the tokenizer too.
1153
                        list($ext, $tokenizer) = explode('/', $ext);
×
UNCOV
1154
                        $newExtensions[$ext]   = strtoupper($tokenizer);
×
UNCOV
1155
                        continue;
×
1156
                    }
1157

1158
                    if (isset($this->extensions[$ext]) === true) {
×
1159
                        $newExtensions[$ext] = $this->extensions[$ext];
×
1160
                    } else {
UNCOV
1161
                        $newExtensions[$ext] = 'PHP';
×
1162
                    }
1163
                }
1164

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

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

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

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

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

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

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

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

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

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

1236
                $generatorName   = substr($arg, 10);
24✔
1237
                $validGenerators = [
8✔
1238
                    'Text',
24✔
1239
                    'HTML',
16✔
1240
                    'Markdown',
16✔
1241
                ];
16✔
1242

1243
                if (in_array($generatorName, $validGenerators, true) === false) {
24✔
1244
                    $error  = 'ERROR: "'.$generatorName.'" is not a valid generator. Valid options are: Text, HTML, and Markdown.'.PHP_EOL.PHP_EOL;
12✔
1245
                    $error .= $this->printShortUsage(true);
12✔
1246
                    throw new DeepExitException($error, 3);
12✔
1247
                }
1248

1249
                $this->generator = $generatorName;
12✔
1250
                self::$overriddenDefaults['generator'] = true;
12✔
1251
            } else if (substr($arg, 0, 9) === 'encoding=') {
4✔
NEW
1252
                if (isset(self::$overriddenDefaults['encoding']) === true) {
×
NEW
1253
                    break;
×
1254
                }
1255

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

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

1271
                        if ($eqPos === false) {
×
UNCOV
1272
                            $unknown[$arg] = $arg;
×
1273
                        } else {
1274
                            $value         = substr($arg, ($eqPos + 1));
×
1275
                            $arg           = substr($arg, 0, $eqPos);
×
1276
                            $unknown[$arg] = $value;
×
1277
                        }
1278

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

1290
    }//end processLongArgument()
26✔
1291

1292

1293
    /**
1294
     * Processes an unknown command line argument.
1295
     *
1296
     * Assumes all unknown arguments are files and folders to check.
1297
     *
1298
     * @param string $arg The command line argument.
1299
     * @param int    $pos The position of the argument on the command line.
1300
     *
1301
     * @return void
1302
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
1303
     */
UNCOV
1304
    public function processUnknownArgument($arg, $pos)
×
1305
    {
1306
        // We don't know about any additional switches; just files.
UNCOV
1307
        if ($arg[0] === '-') {
×
UNCOV
1308
            if ($this->dieOnUnknownArg === false) {
×
UNCOV
1309
                return;
×
1310
            }
1311

UNCOV
1312
            $error  = "ERROR: option \"$arg\" not known".PHP_EOL.PHP_EOL;
×
UNCOV
1313
            $error .= $this->printShortUsage(true);
×
UNCOV
1314
            throw new DeepExitException($error, 3);
×
1315
        }
1316

UNCOV
1317
        $this->processFilePath($arg);
×
1318

1319
    }//end processUnknownArgument()
1320

1321

1322
    /**
1323
     * Processes a file path and add it to the file list.
1324
     *
1325
     * @param string $path The path to the file to add.
1326
     *
1327
     * @return void
1328
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
1329
     */
1330
    public function processFilePath($path)
×
1331
    {
1332
        // If we are processing STDIN, don't record any files to check.
UNCOV
1333
        if ($this->stdin === true) {
×
UNCOV
1334
            return;
×
1335
        }
1336

UNCOV
1337
        $file = Common::realpath($path);
×
UNCOV
1338
        if (file_exists($file) === false) {
×
UNCOV
1339
            if ($this->dieOnUnknownArg === false) {
×
UNCOV
1340
                return;
×
1341
            }
1342

UNCOV
1343
            $error  = 'ERROR: The file "'.$path.'" does not exist.'.PHP_EOL.PHP_EOL;
×
UNCOV
1344
            $error .= $this->printShortUsage(true);
×
UNCOV
1345
            throw new DeepExitException($error, 3);
×
1346
        } else {
1347
            // Can't modify the files array directly because it's not a real
1348
            // class member, so need to use this little get/modify/set trick.
UNCOV
1349
            $files       = $this->files;
×
UNCOV
1350
            $files[]     = $file;
×
1351
            $this->files = $files;
×
1352
            self::$overriddenDefaults['files'] = true;
×
1353
        }
1354

1355
    }//end processFilePath()
1356

1357

1358
    /**
1359
     * Prints out the usage information for this script.
1360
     *
1361
     * @return void
1362
     */
1363
    public function printUsage()
×
1364
    {
UNCOV
1365
        echo PHP_EOL;
×
1366

1367
        if (PHP_CODESNIFFER_CBF === true) {
×
1368
            $this->printPHPCBFUsage();
×
1369
        } else {
1370
            $this->printPHPCSUsage();
×
1371
        }
1372

UNCOV
1373
        echo PHP_EOL;
×
1374

1375
    }//end printUsage()
1376

1377

1378
    /**
1379
     * Prints out the short usage information for this script.
1380
     *
1381
     * @param bool $return If TRUE, the usage string is returned
1382
     *                     instead of output to screen.
1383
     *
1384
     * @return string|void
1385
     */
1386
    public function printShortUsage($return=false)
×
1387
    {
1388
        if (PHP_CODESNIFFER_CBF === true) {
×
UNCOV
1389
            $usage = 'Run "phpcbf --help" for usage information';
×
1390
        } else {
1391
            $usage = 'Run "phpcs --help" for usage information';
×
1392
        }
1393

UNCOV
1394
        $usage .= PHP_EOL.PHP_EOL;
×
1395

UNCOV
1396
        if ($return === true) {
×
UNCOV
1397
            return $usage;
×
1398
        }
1399

UNCOV
1400
        echo $usage;
×
1401

1402
    }//end printShortUsage()
1403

1404

1405
    /**
1406
     * Prints out the usage information for PHPCS.
1407
     *
1408
     * @return void
1409
     */
UNCOV
1410
    public function printPHPCSUsage()
×
1411
    {
1412
        $longOptions   = explode(',', Help::DEFAULT_LONG_OPTIONS);
×
UNCOV
1413
        $longOptions[] = 'cache';
×
1414
        $longOptions[] = 'no-cache';
×
1415
        $longOptions[] = 'report';
×
UNCOV
1416
        $longOptions[] = 'report-file';
×
UNCOV
1417
        $longOptions[] = 'report-report';
×
1418
        $longOptions[] = 'config-explain';
×
UNCOV
1419
        $longOptions[] = 'config-set';
×
UNCOV
1420
        $longOptions[] = 'config-delete';
×
UNCOV
1421
        $longOptions[] = 'config-show';
×
UNCOV
1422
        $longOptions[] = 'generator';
×
1423

UNCOV
1424
        $shortOptions = Help::DEFAULT_SHORT_OPTIONS.'aems';
×
1425

UNCOV
1426
        (new Help($this, $longOptions, $shortOptions))->display();
×
1427

1428
    }//end printPHPCSUsage()
1429

1430

1431
    /**
1432
     * Prints out the usage information for PHPCBF.
1433
     *
1434
     * @return void
1435
     */
1436
    public function printPHPCBFUsage()
×
1437
    {
1438
        $longOptions   = explode(',', Help::DEFAULT_LONG_OPTIONS);
×
1439
        $longOptions[] = 'suffix';
×
1440
        $shortOptions  = Help::DEFAULT_SHORT_OPTIONS;
×
1441

1442
        (new Help($this, $longOptions, $shortOptions))->display();
×
1443

1444
    }//end printPHPCBFUsage()
1445

1446

1447
    /**
1448
     * Get a single config value.
1449
     *
1450
     * @param string $key The name of the config value.
1451
     *
1452
     * @return string|null
1453
     * @see    setConfigData()
1454
     * @see    getAllConfigData()
1455
     */
1456
    public static function getConfigData($key)
6✔
1457
    {
1458
        $phpCodeSnifferConfig = self::getAllConfigData();
6✔
1459

1460
        if ($phpCodeSnifferConfig === null) {
6✔
UNCOV
1461
            return null;
×
1462
        }
1463

1464
        if (isset($phpCodeSnifferConfig[$key]) === false) {
6✔
1465
            return null;
6✔
1466
        }
1467

1468
        return $phpCodeSnifferConfig[$key];
6✔
1469

1470
    }//end getConfigData()
1471

1472

1473
    /**
1474
     * Get the path to an executable utility.
1475
     *
1476
     * @param string $name The name of the executable utility.
1477
     *
1478
     * @return string|null
1479
     * @see    getConfigData()
1480
     */
1481
    public static function getExecutablePath($name)
4✔
1482
    {
1483
        $data = self::getConfigData($name.'_path');
4✔
1484
        if ($data !== null) {
4✔
UNCOV
1485
            return $data;
×
1486
        }
1487

1488
        if ($name === "php") {
4✔
1489
            // For php, we know the executable path. There's no need to look it up.
UNCOV
1490
            return PHP_BINARY;
×
1491
        }
1492

1493
        if (array_key_exists($name, self::$executablePaths) === true) {
4✔
1494
            return self::$executablePaths[$name];
2✔
1495
        }
1496

1497
        if (stripos(PHP_OS, 'WIN') === 0) {
4✔
1498
            $cmd = 'where '.escapeshellarg($name).' 2> nul';
2✔
1499
        } else {
1✔
1500
            $cmd = 'which '.escapeshellarg($name).' 2> /dev/null';
2✔
1501
        }
1502

1503
        $result = exec($cmd, $output, $retVal);
4✔
1504
        if ($retVal !== 0) {
4✔
UNCOV
1505
            $result = null;
×
1506
        }
1507

1508
        self::$executablePaths[$name] = $result;
4✔
1509
        return $result;
4✔
1510

1511
    }//end getExecutablePath()
1512

1513

1514
    /**
1515
     * Set a single config value.
1516
     *
1517
     * @param string      $key   The name of the config value.
1518
     * @param string|null $value The value to set. If null, the config
1519
     *                           entry is deleted, reverting it to the
1520
     *                           default value.
1521
     * @param boolean     $temp  Set this config data temporarily for this
1522
     *                           script run. This will not write the config
1523
     *                           data to the config file.
1524
     *
1525
     * @return bool
1526
     * @see    getConfigData()
1527
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the config file can not be written.
1528
     */
UNCOV
1529
    public static function setConfigData($key, $value, $temp=false)
×
1530
    {
UNCOV
1531
        if (isset(self::$overriddenDefaults['runtime-set']) === true
×
UNCOV
1532
            && isset(self::$overriddenDefaults['runtime-set'][$key]) === true
×
1533
        ) {
UNCOV
1534
            return false;
×
1535
        }
1536

UNCOV
1537
        if ($temp === false) {
×
UNCOV
1538
            $path = '';
×
UNCOV
1539
            if (is_callable('\Phar::running') === true) {
×
UNCOV
1540
                $path = Phar::running(false);
×
1541
            }
1542

UNCOV
1543
            if ($path !== '') {
×
UNCOV
1544
                $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1545
            } else {
UNCOV
1546
                $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1547
            }
1548

1549
            if (is_file($configFile) === true
×
1550
                && is_writable($configFile) === false
×
1551
            ) {
1552
                $error = 'ERROR: Config file '.$configFile.' is not writable'.PHP_EOL.PHP_EOL;
×
UNCOV
1553
                throw new DeepExitException($error, 3);
×
1554
            }
1555
        }//end if
1556

1557
        $phpCodeSnifferConfig = self::getAllConfigData();
×
1558

UNCOV
1559
        if ($value === null) {
×
UNCOV
1560
            if (isset($phpCodeSnifferConfig[$key]) === true) {
×
1561
                unset($phpCodeSnifferConfig[$key]);
×
1562
            }
1563
        } else {
1564
            $phpCodeSnifferConfig[$key] = $value;
×
1565
        }
1566

1567
        if ($temp === false) {
×
1568
            $output  = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
×
UNCOV
1569
            $output .= var_export($phpCodeSnifferConfig, true);
×
1570
            $output .= ";\n?".'>';
×
1571

UNCOV
1572
            if (file_put_contents($configFile, $output) === false) {
×
UNCOV
1573
                $error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL;
×
UNCOV
1574
                throw new DeepExitException($error, 3);
×
1575
            }
1576

1577
            self::$configDataFile = $configFile;
×
1578
        }
1579

UNCOV
1580
        self::$configData = $phpCodeSnifferConfig;
×
1581

1582
        // If the installed paths are being set, make sure all known
1583
        // standards paths are added to the autoloader.
UNCOV
1584
        if ($key === 'installed_paths') {
×
1585
            $installedStandards = Standards::getInstalledStandardDetails();
×
1586
            foreach ($installedStandards as $details) {
×
1587
                Autoload::addSearchPath($details['path'], $details['namespace']);
×
1588
            }
1589
        }
1590

1591
        return true;
×
1592

1593
    }//end setConfigData()
1594

1595

1596
    /**
1597
     * Get all config data.
1598
     *
1599
     * @return array<string, string>
1600
     * @see    getConfigData()
1601
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the config file could not be read.
1602
     */
1603
    public static function getAllConfigData()
×
1604
    {
1605
        if (self::$configData !== null) {
×
UNCOV
1606
            return self::$configData;
×
1607
        }
1608

1609
        $path = '';
×
UNCOV
1610
        if (is_callable('\Phar::running') === true) {
×
UNCOV
1611
            $path = Phar::running(false);
×
1612
        }
1613

UNCOV
1614
        if ($path !== '') {
×
UNCOV
1615
            $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
1616
        } else {
UNCOV
1617
            $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
×
UNCOV
1618
            if (is_file($configFile) === false
×
UNCOV
1619
                && strpos('@data_dir@', '@data_dir') === false
×
1620
            ) {
1621
                $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
×
1622
            }
1623
        }
1624

UNCOV
1625
        if (is_file($configFile) === false) {
×
UNCOV
1626
            self::$configData = [];
×
1627
            return [];
×
1628
        }
1629

UNCOV
1630
        if (Common::isReadable($configFile) === false) {
×
UNCOV
1631
            $error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL;
×
1632
            throw new DeepExitException($error, 3);
×
1633
        }
1634

1635
        include $configFile;
×
1636
        self::$configDataFile = $configFile;
×
1637
        self::$configData     = $phpCodeSnifferConfig;
×
UNCOV
1638
        return self::$configData;
×
1639

1640
    }//end getAllConfigData()
1641

1642

1643
    /**
1644
     * Prints out the gathered config data.
1645
     *
1646
     * @param array $data The config data to print.
1647
     *
1648
     * @return void
1649
     */
1650
    public function printConfigData($data)
×
1651
    {
UNCOV
1652
        $max  = 0;
×
1653
        $keys = array_keys($data);
×
1654
        foreach ($keys as $key) {
×
1655
            $len = strlen($key);
×
1656
            if (strlen($key) > $max) {
×
UNCOV
1657
                $max = $len;
×
1658
            }
1659
        }
1660

UNCOV
1661
        if ($max === 0) {
×
UNCOV
1662
            return;
×
1663
        }
1664

UNCOV
1665
        $max += 2;
×
UNCOV
1666
        ksort($data);
×
UNCOV
1667
        foreach ($data as $name => $value) {
×
1668
            echo str_pad($name.': ', $max).$value.PHP_EOL;
×
1669
        }
1670

1671
    }//end printConfigData()
1672

1673

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