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

PHP-CS-Fixer / PHP-CS-Fixer / 3721300657

pending completion
3721300657

push

github

GitHub
minor: Follow PSR12 ordered imports in Symfony ruleset (#6712)

9 of 9 new or added lines in 2 files covered. (100.0%)

22674 of 24281 relevant lines covered (93.38%)

39.08 hits per line

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

91.28
/src/Console/ConfigurationResolver.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of PHP CS Fixer.
7
 *
8
 * (c) Fabien Potencier <fabien@symfony.com>
9
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14

15
namespace PhpCsFixer\Console;
16

17
use PhpCsFixer\Cache\CacheManagerInterface;
18
use PhpCsFixer\Cache\Directory;
19
use PhpCsFixer\Cache\DirectoryInterface;
20
use PhpCsFixer\Cache\FileCacheManager;
21
use PhpCsFixer\Cache\FileHandler;
22
use PhpCsFixer\Cache\NullCacheManager;
23
use PhpCsFixer\Cache\Signature;
24
use PhpCsFixer\ConfigInterface;
25
use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
26
use PhpCsFixer\Console\Command\HelpCommand;
27
use PhpCsFixer\Console\Report\FixReport\ReporterFactory;
28
use PhpCsFixer\Console\Report\FixReport\ReporterInterface;
29
use PhpCsFixer\Differ\DifferInterface;
30
use PhpCsFixer\Differ\NullDiffer;
31
use PhpCsFixer\Differ\UnifiedDiffer;
32
use PhpCsFixer\Finder;
33
use PhpCsFixer\Fixer\DeprecatedFixerInterface;
34
use PhpCsFixer\Fixer\FixerInterface;
35
use PhpCsFixer\FixerFactory;
36
use PhpCsFixer\Linter\Linter;
37
use PhpCsFixer\Linter\LinterInterface;
38
use PhpCsFixer\RuleSet\RuleSet;
39
use PhpCsFixer\RuleSet\RuleSetInterface;
40
use PhpCsFixer\StdinFileInfo;
41
use PhpCsFixer\ToolInfoInterface;
42
use PhpCsFixer\Utils;
43
use PhpCsFixer\WhitespacesFixerConfig;
44
use PhpCsFixer\WordMatcher;
45
use Symfony\Component\Console\Output\OutputInterface;
46
use Symfony\Component\Filesystem\Filesystem;
47
use Symfony\Component\Finder\Finder as SymfonyFinder;
48

49
/**
50
 * The resolver that resolves configuration to use by command line options and config.
51
 *
52
 * @author Fabien Potencier <fabien@symfony.com>
53
 * @author Katsuhiro Ogawa <ko.fivestar@gmail.com>
54
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
55
 *
56
 * @internal
57
 */
58
final class ConfigurationResolver
59
{
60
    public const PATH_MODE_OVERRIDE = 'override';
61
    public const PATH_MODE_INTERSECTION = 'intersection';
62

63
    /**
64
     * @var null|bool
65
     */
66
    private $allowRisky;
67

68
    /**
69
     * @var null|ConfigInterface
70
     */
71
    private $config;
72

73
    /**
74
     * @var null|string
75
     */
76
    private $configFile;
77

78
    private string $cwd;
79

80
    private ConfigInterface $defaultConfig;
81

82
    /**
83
     * @var null|ReporterInterface
84
     */
85
    private $reporter;
86

87
    /**
88
     * @var null|bool
89
     */
90
    private $isStdIn;
91

92
    /**
93
     * @var null|bool
94
     */
95
    private $isDryRun;
96

97
    /**
98
     * @var null|FixerInterface[]
99
     */
100
    private $fixers;
101

102
    /**
103
     * @var null|bool
104
     */
105
    private $configFinderIsOverridden;
106

107
    private ToolInfoInterface $toolInfo;
108

109
    /**
110
     * @var array<string, mixed>
111
     */
112
    private array $options = [
113
        'allow-risky' => null,
114
        'cache-file' => null,
115
        'config' => null,
116
        'diff' => null,
117
        'dry-run' => null,
118
        'format' => null,
119
        'path' => [],
120
        'path-mode' => self::PATH_MODE_OVERRIDE,
121
        'rules' => null,
122
        'show-progress' => null,
123
        'stop-on-violation' => null,
124
        'using-cache' => null,
125
        'verbosity' => null,
126
    ];
127

128
    /**
129
     * @var null|string
130
     */
131
    private $cacheFile;
132

133
    /**
134
     * @var null|CacheManagerInterface
135
     */
136
    private $cacheManager;
137

138
    /**
139
     * @var null|DifferInterface
140
     */
141
    private $differ;
142

143
    /**
144
     * @var null|Directory
145
     */
146
    private $directory;
147

148
    /**
149
     * @var null|iterable<\SplFileInfo>
150
     */
151
    private ?iterable $finder = null;
152

153
    private ?string $format = null;
154

155
    /**
156
     * @var null|Linter
157
     */
158
    private $linter;
159

160
    /**
161
     * @var null|list<string>
162
     */
163
    private ?array $path = null;
164

165
    /**
166
     * @var null|string
167
     */
168
    private $progress;
169

170
    /**
171
     * @var null|RuleSet
172
     */
173
    private $ruleSet;
174

175
    /**
176
     * @var null|bool
177
     */
178
    private $usingCache;
179

180
    /**
181
     * @var FixerFactory
182
     */
183
    private $fixerFactory;
184

185
    /**
186
     * @param array<string, mixed> $options
187
     */
188
    public function __construct(
189
        ConfigInterface $config,
190
        array $options,
191
        string $cwd,
192
        ToolInfoInterface $toolInfo
193
    ) {
194
        $this->defaultConfig = $config;
109✔
195
        $this->cwd = $cwd;
109✔
196
        $this->toolInfo = $toolInfo;
109✔
197

198
        foreach ($options as $name => $value) {
109✔
199
            $this->setOption($name, $value);
94✔
200
        }
201
    }
202

203
    public function getCacheFile(): ?string
204
    {
205
        if (!$this->getUsingCache()) {
9✔
206
            return null;
4✔
207
        }
208

209
        if (null === $this->cacheFile) {
5✔
210
            if (null === $this->options['cache-file']) {
5✔
211
                $this->cacheFile = $this->getConfig()->getCacheFile();
3✔
212
            } else {
213
                $this->cacheFile = $this->options['cache-file'];
2✔
214
            }
215
        }
216

217
        return $this->cacheFile;
5✔
218
    }
219

220
    public function getCacheManager(): CacheManagerInterface
221
    {
222
        if (null === $this->cacheManager) {
1✔
223
            $cacheFile = $this->getCacheFile();
1✔
224

225
            if (null === $cacheFile) {
1✔
226
                $this->cacheManager = new NullCacheManager();
1✔
227
            } else {
228
                $this->cacheManager = new FileCacheManager(
×
229
                    new FileHandler($cacheFile),
×
230
                    new Signature(
×
231
                        PHP_VERSION,
×
232
                        $this->toolInfo->getVersion(),
×
233
                        $this->getConfig()->getIndent(),
×
234
                        $this->getConfig()->getLineEnding(),
×
235
                        $this->getRules()
×
236
                    ),
×
237
                    $this->isDryRun(),
×
238
                    $this->getDirectory()
×
239
                );
×
240
            }
241
        }
242

243
        return $this->cacheManager;
1✔
244
    }
245

246
    public function getConfig(): ConfigInterface
247
    {
248
        if (null === $this->config) {
66✔
249
            foreach ($this->computeConfigFiles() as $configFile) {
66✔
250
                if (!file_exists($configFile)) {
65✔
251
                    continue;
51✔
252
                }
253

254
                $configFileBasename = basename($configFile);
19✔
255
                $deprecatedConfigs = [
19✔
256
                    '.php_cs' => '.php-cs-fixer.php',
19✔
257
                    '.php_cs.dist' => '.php-cs-fixer.dist.php',
19✔
258
                ];
19✔
259

260
                if (isset($deprecatedConfigs[$configFileBasename])) {
19✔
261
                    throw new InvalidConfigurationException("Configuration file `{$configFileBasename}` is outdated, rename to `{$deprecatedConfigs[$configFileBasename]}`.");
×
262
                }
263

264
                $this->config = self::separatedContextLessInclude($configFile);
19✔
265
                $this->configFile = $configFile;
18✔
266

267
                break;
18✔
268
            }
269

270
            if (null === $this->config) {
64✔
271
                $this->config = $this->defaultConfig;
46✔
272
            }
273
        }
274

275
        return $this->config;
64✔
276
    }
277

278
    public function getConfigFile(): ?string
279
    {
280
        if (null === $this->configFile) {
18✔
281
            $this->getConfig();
13✔
282
        }
283

284
        return $this->configFile;
18✔
285
    }
286

287
    public function getDiffer(): DifferInterface
288
    {
289
        if (null === $this->differ) {
4✔
290
            if ($this->options['diff']) {
4✔
291
                $this->differ = new UnifiedDiffer();
2✔
292
            } else {
293
                $this->differ = new NullDiffer();
2✔
294
            }
295
        }
296

297
        return $this->differ;
4✔
298
    }
299

300
    public function getDirectory(): DirectoryInterface
301
    {
302
        if (null === $this->directory) {
3✔
303
            $path = $this->getCacheFile();
3✔
304
            if (null === $path) {
3✔
305
                $absolutePath = $this->cwd;
1✔
306
            } else {
307
                $filesystem = new Filesystem();
2✔
308

309
                $absolutePath = $filesystem->isAbsolutePath($path)
2✔
310
                    ? $path
1✔
311
                    : $this->cwd.\DIRECTORY_SEPARATOR.$path;
1✔
312
            }
313

314
            $this->directory = new Directory(\dirname($absolutePath));
3✔
315
        }
316

317
        return $this->directory;
3✔
318
    }
319

320
    /**
321
     * @return FixerInterface[] An array of FixerInterface
322
     */
323
    public function getFixers(): array
324
    {
325
        if (null === $this->fixers) {
3✔
326
            $this->fixers = $this->createFixerFactory()
3✔
327
                ->useRuleSet($this->getRuleSet())
3✔
328
                ->setWhitespacesConfig(new WhitespacesFixerConfig($this->config->getIndent(), $this->config->getLineEnding()))
3✔
329
                ->getFixers()
3✔
330
            ;
3✔
331

332
            if (false === $this->getRiskyAllowed()) {
3✔
333
                $riskyFixers = array_map(
3✔
334
                    static function (FixerInterface $fixer): string {
3✔
335
                        return $fixer->getName();
×
336
                    },
3✔
337
                    array_filter(
3✔
338
                        $this->fixers,
3✔
339
                        static function (FixerInterface $fixer): bool {
3✔
340
                            return $fixer->isRisky();
2✔
341
                        }
3✔
342
                    )
3✔
343
                );
3✔
344

345
                if (\count($riskyFixers) > 0) {
3✔
346
                    throw new InvalidConfigurationException(sprintf('The rules contain risky fixers ("%s"), but they are not allowed to run. Perhaps you forget to use --allow-risky=yes option?', implode('", "', $riskyFixers)));
×
347
                }
348
            }
349
        }
350

351
        return $this->fixers;
3✔
352
    }
353

354
    public function getLinter(): LinterInterface
355
    {
356
        if (null === $this->linter) {
1✔
357
            $this->linter = new Linter();
1✔
358
        }
359

360
        return $this->linter;
1✔
361
    }
362

363
    /**
364
     * Returns path.
365
     *
366
     * @return string[]
367
     */
368
    public function getPath(): array
369
    {
370
        if (null === $this->path) {
81✔
371
            $filesystem = new Filesystem();
81✔
372
            $cwd = $this->cwd;
81✔
373

374
            if (1 === \count($this->options['path']) && '-' === $this->options['path'][0]) {
81✔
375
                $this->path = $this->options['path'];
×
376
            } else {
377
                $this->path = array_map(
81✔
378
                    static function (string $rawPath) use ($cwd, $filesystem): string {
81✔
379
                        $path = trim($rawPath);
46✔
380

381
                        if ('' === $path) {
46✔
382
                            throw new InvalidConfigurationException("Invalid path: \"{$rawPath}\".");
6✔
383
                        }
384

385
                        $absolutePath = $filesystem->isAbsolutePath($path)
42✔
386
                            ? $path
37✔
387
                            : $cwd.\DIRECTORY_SEPARATOR.$path;
5✔
388

389
                        if (!file_exists($absolutePath)) {
42✔
390
                            throw new InvalidConfigurationException(sprintf(
5✔
391
                                'The path "%s" is not readable.',
5✔
392
                                $path
5✔
393
                            ));
5✔
394
                        }
395

396
                        return $absolutePath;
37✔
397
                    },
81✔
398
                    $this->options['path']
81✔
399
                );
81✔
400
            }
401
        }
402

403
        return $this->path;
70✔
404
    }
405

406
    /**
407
     * @throws InvalidConfigurationException
408
     */
409
    public function getProgress(): string
410
    {
411
        if (null === $this->progress) {
9✔
412
            if (OutputInterface::VERBOSITY_VERBOSE <= $this->options['verbosity'] && 'txt' === $this->getFormat()) {
9✔
413
                $progressType = $this->options['show-progress'];
7✔
414
                $progressTypes = ['none', 'dots'];
7✔
415

416
                if (null === $progressType) {
7✔
417
                    $progressType = $this->getConfig()->getHideProgress() ? 'none' : 'dots';
2✔
418
                } elseif (!\in_array($progressType, $progressTypes, true)) {
5✔
419
                    throw new InvalidConfigurationException(sprintf(
1✔
420
                        'The progress type "%s" is not defined, supported are "%s".',
1✔
421
                        $progressType,
1✔
422
                        implode('", "', $progressTypes)
1✔
423
                    ));
1✔
424
                }
425

426
                $this->progress = $progressType;
6✔
427
            } else {
428
                $this->progress = 'none';
2✔
429
            }
430
        }
431

432
        return $this->progress;
8✔
433
    }
434

435
    public function getReporter(): ReporterInterface
436
    {
437
        if (null === $this->reporter) {
3✔
438
            $reporterFactory = new ReporterFactory();
3✔
439
            $reporterFactory->registerBuiltInReporters();
3✔
440

441
            $format = $this->getFormat();
3✔
442

443
            try {
444
                $this->reporter = $reporterFactory->getReporter($format);
3✔
445
            } catch (\UnexpectedValueException $e) {
1✔
446
                $formats = $reporterFactory->getFormats();
1✔
447
                sort($formats);
1✔
448

449
                throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are "%s".', $format, implode('", "', $formats)));
1✔
450
            }
451
        }
452

453
        return $this->reporter;
2✔
454
    }
455

456
    public function getRiskyAllowed(): bool
457
    {
458
        if (null === $this->allowRisky) {
16✔
459
            if (null === $this->options['allow-risky']) {
16✔
460
                $this->allowRisky = $this->getConfig()->getRiskyAllowed();
8✔
461
            } else {
462
                $this->allowRisky = $this->resolveOptionBooleanValue('allow-risky');
8✔
463
            }
464
        }
465

466
        return $this->allowRisky;
15✔
467
    }
468

469
    /**
470
     * Returns rules.
471
     *
472
     * @return array<string, array<string, mixed>|bool>
473
     */
474
    public function getRules(): array
475
    {
476
        return $this->getRuleSet()->getRules();
11✔
477
    }
478

479
    public function getUsingCache(): bool
480
    {
481
        if (null === $this->usingCache) {
17✔
482
            if (null === $this->options['using-cache']) {
17✔
483
                $this->usingCache = $this->getConfig()->getUsingCache();
12✔
484
            } else {
485
                $this->usingCache = $this->resolveOptionBooleanValue('using-cache');
5✔
486
            }
487
        }
488

489
        $this->usingCache = $this->usingCache && ($this->toolInfo->isInstalledAsPhar() || $this->toolInfo->isInstalledByComposer());
17✔
490

491
        return $this->usingCache;
17✔
492
    }
493

494
    /**
495
     * @return iterable<\SplFileInfo>
496
     */
497
    public function getFinder(): iterable
498
    {
499
        if (null === $this->finder) {
31✔
500
            $this->finder = $this->resolveFinder();
31✔
501
        }
502

503
        return $this->finder;
26✔
504
    }
505

506
    /**
507
     * Returns dry-run flag.
508
     */
509
    public function isDryRun(): bool
510
    {
511
        if (null === $this->isDryRun) {
4✔
512
            if ($this->isStdIn()) {
4✔
513
                // Can't write to STDIN
514
                $this->isDryRun = true;
1✔
515
            } else {
516
                $this->isDryRun = $this->options['dry-run'];
3✔
517
            }
518
        }
519

520
        return $this->isDryRun;
4✔
521
    }
522

523
    public function shouldStopOnViolation(): bool
524
    {
525
        return $this->options['stop-on-violation'];
1✔
526
    }
527

528
    public function configFinderIsOverridden(): bool
529
    {
530
        if (null === $this->configFinderIsOverridden) {
7✔
531
            $this->resolveFinder();
7✔
532
        }
533

534
        return $this->configFinderIsOverridden;
7✔
535
    }
536

537
    /**
538
     * Compute file candidates for config file.
539
     *
540
     * @return string[]
541
     */
542
    private function computeConfigFiles(): array
543
    {
544
        $configFile = $this->options['config'];
66✔
545

546
        if (null !== $configFile) {
66✔
547
            if (false === file_exists($configFile) || false === is_readable($configFile)) {
10✔
548
                throw new InvalidConfigurationException(sprintf('Cannot read config file "%s".', $configFile));
×
549
            }
550

551
            return [$configFile];
10✔
552
        }
553

554
        $path = $this->getPath();
56✔
555

556
        if ($this->isStdIn() || 0 === \count($path)) {
56✔
557
            $configDir = $this->cwd;
33✔
558
        } elseif (1 < \count($path)) {
23✔
559
            throw new InvalidConfigurationException('For multiple paths config parameter is required.');
1✔
560
        } elseif (!is_file($path[0])) {
22✔
561
            $configDir = $path[0];
12✔
562
        } else {
563
            $dirName = pathinfo($path[0], PATHINFO_DIRNAME);
10✔
564
            $configDir = $dirName ?: $path[0];
10✔
565
        }
566

567
        $candidates = [
55✔
568
            $configDir.\DIRECTORY_SEPARATOR.'.php-cs-fixer.php',
55✔
569
            $configDir.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php',
55✔
570
            $configDir.\DIRECTORY_SEPARATOR.'.php_cs', // old v2 config, present here only to throw nice error message later
55✔
571
            $configDir.\DIRECTORY_SEPARATOR.'.php_cs.dist', // old v2 config, present here only to throw nice error message later
55✔
572
        ];
55✔
573

574
        if ($configDir !== $this->cwd) {
55✔
575
            $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php-cs-fixer.php';
22✔
576
            $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php';
22✔
577
            $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs'; // old v2 config, present here only to throw nice error message later
22✔
578
            $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs.dist'; // old v2 config, present here only to throw nice error message later
22✔
579
        }
580

581
        return $candidates;
55✔
582
    }
583

584
    private function createFixerFactory(): FixerFactory
585
    {
586
        if (null === $this->fixerFactory) {
13✔
587
            $fixerFactory = new FixerFactory();
13✔
588
            $fixerFactory->registerBuiltInFixers();
13✔
589
            $fixerFactory->registerCustomFixers($this->getConfig()->getCustomFixers());
13✔
590

591
            $this->fixerFactory = $fixerFactory;
13✔
592
        }
593

594
        return $this->fixerFactory;
13✔
595
    }
596

597
    private function getFormat(): string
598
    {
599
        if (null === $this->format) {
10✔
600
            $this->format = $this->options['format'] ?? $this->getConfig()->getFormat();
10✔
601
        }
602

603
        return $this->format;
10✔
604
    }
605

606
    private function getRuleSet(): RuleSetInterface
607
    {
608
        if (null === $this->ruleSet) {
14✔
609
            $rules = $this->parseRules();
14✔
610
            $this->validateRules($rules);
13✔
611

612
            $this->ruleSet = new RuleSet($rules);
8✔
613
        }
614

615
        return $this->ruleSet;
8✔
616
    }
617

618
    private function isStdIn(): bool
619
    {
620
        if (null === $this->isStdIn) {
74✔
621
            $this->isStdIn = 1 === \count($this->options['path']) && '-' === $this->options['path'][0];
74✔
622
        }
623

624
        return $this->isStdIn;
74✔
625
    }
626

627
    /**
628
     * @template T
629
     *
630
     * @param iterable<T> $iterable
631
     *
632
     * @return \Traversable<T>
633
     */
634
    private function iterableToTraversable(iterable $iterable): \Traversable
635
    {
636
        return \is_array($iterable) ? new \ArrayIterator($iterable) : $iterable;
25✔
637
    }
638

639
    /**
640
     * @return array<mixed>
641
     */
642
    private function parseRules(): array
643
    {
644
        if (null === $this->options['rules']) {
14✔
645
            return $this->getConfig()->getRules();
5✔
646
        }
647

648
        $rules = trim($this->options['rules']);
9✔
649
        if ('' === $rules) {
9✔
650
            throw new InvalidConfigurationException('Empty rules value is not allowed.');
1✔
651
        }
652

653
        if (str_starts_with($rules, '{')) {
8✔
654
            $rules = json_decode($rules, true);
×
655

656
            if (JSON_ERROR_NONE !== json_last_error()) {
×
657
                throw new InvalidConfigurationException(sprintf('Invalid JSON rules input: "%s".', json_last_error_msg()));
×
658
            }
659

660
            return $rules;
×
661
        }
662

663
        $rules = [];
8✔
664

665
        foreach (explode(',', $this->options['rules']) as $rule) {
8✔
666
            $rule = trim($rule);
8✔
667

668
            if ('' === $rule) {
8✔
669
                throw new InvalidConfigurationException('Empty rule name is not allowed.');
×
670
            }
671

672
            if (str_starts_with($rule, '-')) {
8✔
673
                $rules[substr($rule, 1)] = false;
2✔
674
            } else {
675
                $rules[$rule] = true;
8✔
676
            }
677
        }
678

679
        return $rules;
8✔
680
    }
681

682
    /**
683
     * @param array<mixed> $rules
684
     *
685
     * @throws InvalidConfigurationException
686
     */
687
    private function validateRules(array $rules): void
688
    {
689
        /**
690
         * Create a ruleset that contains all configured rules, even when they originally have been disabled.
691
         *
692
         * @see RuleSet::resolveSet()
693
         */
694
        $ruleSet = [];
13✔
695

696
        foreach ($rules as $key => $value) {
13✔
697
            if (\is_int($key)) {
13✔
698
                throw new InvalidConfigurationException(sprintf('Missing value for "%s" rule/set.', $value));
×
699
            }
700

701
            $ruleSet[$key] = true;
13✔
702
        }
703

704
        $ruleSet = new RuleSet($ruleSet);
13✔
705

706
        $configuredFixers = array_keys($ruleSet->getRules());
13✔
707

708
        $fixers = $this->createFixerFactory()->getFixers();
13✔
709

710
        $availableFixers = array_map(static fn (FixerInterface $fixer): string => $fixer->getName(), $fixers);
13✔
711

712
        $unknownFixers = array_diff($configuredFixers, $availableFixers);
13✔
713

714
        if (\count($unknownFixers) > 0) {
13✔
715
            $renamedRules = [
5✔
716
                'blank_line_before_return' => [
5✔
717
                    'new_name' => 'blank_line_before_statement',
5✔
718
                    'config' => ['statements' => ['return']],
5✔
719
                ],
5✔
720
                'final_static_access' => [
5✔
721
                    'new_name' => 'self_static_accessor',
5✔
722
                ],
5✔
723
                'hash_to_slash_comment' => [
5✔
724
                    'new_name' => 'single_line_comment_style',
5✔
725
                    'config' => ['comment_types' => ['hash']],
5✔
726
                ],
5✔
727
                'lowercase_constants' => [
5✔
728
                    'new_name' => 'constant_case',
5✔
729
                    'config' => ['case' => 'lower'],
5✔
730
                ],
5✔
731
                'no_extra_consecutive_blank_lines' => [
5✔
732
                    'new_name' => 'no_extra_blank_lines',
5✔
733
                ],
5✔
734
                'no_multiline_whitespace_before_semicolons' => [
5✔
735
                    'new_name' => 'multiline_whitespace_before_semicolons',
5✔
736
                ],
5✔
737
                'no_short_echo_tag' => [
5✔
738
                    'new_name' => 'echo_tag_syntax',
5✔
739
                    'config' => ['format' => 'long'],
5✔
740
                ],
5✔
741
                'php_unit_ordered_covers' => [
5✔
742
                    'new_name' => 'phpdoc_order_by_value',
5✔
743
                    'config' => ['annotations' => ['covers']],
5✔
744
                ],
5✔
745
                'phpdoc_inline_tag' => [
5✔
746
                    'new_name' => 'general_phpdoc_tag_rename, phpdoc_inline_tag_normalizer and phpdoc_tag_type',
5✔
747
                ],
5✔
748
                'pre_increment' => [
5✔
749
                    'new_name' => 'increment_style',
5✔
750
                    'config' => ['style' => 'pre'],
5✔
751
                ],
5✔
752
                'psr0' => [
5✔
753
                    'new_name' => 'psr_autoloading',
5✔
754
                    'config' => ['dir' => 'x'],
5✔
755
                ],
5✔
756
                'psr4' => [
5✔
757
                    'new_name' => 'psr_autoloading',
5✔
758
                ],
5✔
759
                'silenced_deprecation_error' => [
5✔
760
                    'new_name' => 'error_suppression',
5✔
761
                ],
5✔
762
                'trailing_comma_in_multiline_array' => [
5✔
763
                    'new_name' => 'trailing_comma_in_multiline',
5✔
764
                    'config' => ['elements' => ['arrays']],
5✔
765
                ],
5✔
766
            ];
5✔
767

768
            $message = 'The rules contain unknown fixers: ';
5✔
769
            $hasOldRule = false;
5✔
770

771
            foreach ($unknownFixers as $unknownFixer) {
5✔
772
                if (isset($renamedRules[$unknownFixer])) { // Check if present as old renamed rule
5✔
773
                    $hasOldRule = true;
4✔
774
                    $message .= sprintf(
4✔
775
                        '"%s" is renamed (did you mean "%s"?%s), ',
4✔
776
                        $unknownFixer,
4✔
777
                        $renamedRules[$unknownFixer]['new_name'],
4✔
778
                        isset($renamedRules[$unknownFixer]['config']) ? ' (note: use configuration "'.HelpCommand::toString($renamedRules[$unknownFixer]['config']).'")' : ''
4✔
779
                    );
4✔
780
                } else { // Go to normal matcher if it is not a renamed rule
781
                    $matcher = new WordMatcher($availableFixers);
2✔
782
                    $alternative = $matcher->match($unknownFixer);
2✔
783
                    $message .= sprintf(
2✔
784
                        '"%s"%s, ',
2✔
785
                        $unknownFixer,
2✔
786
                        null === $alternative ? '' : ' (did you mean "'.$alternative.'"?)'
2✔
787
                    );
2✔
788
                }
789
            }
790

791
            $message = substr($message, 0, -2).'.';
5✔
792

793
            if ($hasOldRule) {
5✔
794
                $message .= "\nFor more info about updating see: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.0.0/UPGRADE-v3.md#renamed-ruless.";
4✔
795
            }
796

797
            throw new InvalidConfigurationException($message);
5✔
798
        }
799

800
        foreach ($fixers as $fixer) {
8✔
801
            $fixerName = $fixer->getName();
8✔
802
            if (isset($rules[$fixerName]) && $fixer instanceof DeprecatedFixerInterface) {
8✔
803
                $successors = $fixer->getSuccessorsNames();
3✔
804
                $messageEnd = [] === $successors
3✔
805
                    ? sprintf(' and will be removed in version %d.0.', Application::getMajorVersion() + 1)
×
806
                    : sprintf('. Use %s instead.', str_replace('`', '"', Utils::naturalLanguageJoinWithBackticks($successors)));
3✔
807

808
                Utils::triggerDeprecation(new \RuntimeException("Rule \"{$fixerName}\" is deprecated{$messageEnd}"));
3✔
809
            }
810
        }
811
    }
812

813
    /**
814
     * Apply path on config instance.
815
     *
816
     * @return iterable<\SplFileInfo>
817
     */
818
    private function resolveFinder(): iterable
819
    {
820
        $this->configFinderIsOverridden = false;
31✔
821

822
        if ($this->isStdIn()) {
31✔
823
            return new \ArrayIterator([new StdinFileInfo()]);
×
824
        }
825

826
        $modes = [self::PATH_MODE_OVERRIDE, self::PATH_MODE_INTERSECTION];
31✔
827

828
        if (!\in_array(
31✔
829
            $this->options['path-mode'],
31✔
830
            $modes,
31✔
831
            true
31✔
832
        )) {
31✔
833
            throw new InvalidConfigurationException(sprintf(
×
834
                'The path-mode "%s" is not defined, supported are "%s".',
×
835
                $this->options['path-mode'],
×
836
                implode('", "', $modes)
×
837
            ));
×
838
        }
839

840
        $isIntersectionPathMode = self::PATH_MODE_INTERSECTION === $this->options['path-mode'];
31✔
841

842
        $paths = array_filter(array_map(
31✔
843
            static function (string $path) {
31✔
844
                return realpath($path);
21✔
845
            },
31✔
846
            $this->getPath()
31✔
847
        ));
31✔
848

849
        if (0 === \count($paths)) {
26✔
850
            if ($isIntersectionPathMode) {
5✔
851
                return new \ArrayIterator([]);
1✔
852
            }
853

854
            return $this->iterableToTraversable($this->getConfig()->getFinder());
4✔
855
        }
856

857
        $pathsByType = [
21✔
858
            'file' => [],
21✔
859
            'dir' => [],
21✔
860
        ];
21✔
861

862
        foreach ($paths as $path) {
21✔
863
            if (is_file($path)) {
21✔
864
                $pathsByType['file'][] = $path;
11✔
865
            } else {
866
                $pathsByType['dir'][] = $path.\DIRECTORY_SEPARATOR;
12✔
867
            }
868
        }
869

870
        $nestedFinder = null;
21✔
871
        $currentFinder = $this->iterableToTraversable($this->getConfig()->getFinder());
21✔
872

873
        try {
874
            $nestedFinder = $currentFinder instanceof \IteratorAggregate ? $currentFinder->getIterator() : $currentFinder;
21✔
875
        } catch (\Exception $e) {
4✔
876
        }
877

878
        if ($isIntersectionPathMode) {
21✔
879
            if (null === $nestedFinder) {
11✔
880
                throw new InvalidConfigurationException(
×
881
                    'Cannot create intersection with not-fully defined Finder in configuration file.'
×
882
                );
×
883
            }
884

885
            return new \CallbackFilterIterator(
11✔
886
                new \IteratorIterator($nestedFinder),
11✔
887
                static function (\SplFileInfo $current) use ($pathsByType): bool {
11✔
888
                    $currentRealPath = $current->getRealPath();
10✔
889

890
                    if (\in_array($currentRealPath, $pathsByType['file'], true)) {
10✔
891
                        return true;
3✔
892
                    }
893

894
                    foreach ($pathsByType['dir'] as $path) {
10✔
895
                        if (str_starts_with($currentRealPath, $path)) {
5✔
896
                            return true;
4✔
897
                        }
898
                    }
899

900
                    return false;
10✔
901
                }
11✔
902
            );
11✔
903
        }
904

905
        if (null !== $this->getConfigFile() && null !== $nestedFinder) {
10✔
906
            $this->configFinderIsOverridden = true;
3✔
907
        }
908

909
        if ($currentFinder instanceof SymfonyFinder && null === $nestedFinder) {
10✔
910
            // finder from configuration Symfony finder and it is not fully defined, we may fulfill it
911
            return $currentFinder->in($pathsByType['dir'])->append($pathsByType['file']);
4✔
912
        }
913

914
        return Finder::create()->in($pathsByType['dir'])->append($pathsByType['file']);
6✔
915
    }
916

917
    /**
918
     * Set option that will be resolved.
919
     *
920
     * @param mixed $value
921
     */
922
    private function setOption(string $name, $value): void
923
    {
924
        if (!\array_key_exists($name, $this->options)) {
94✔
925
            throw new InvalidConfigurationException(sprintf('Unknown option name: "%s".', $name));
1✔
926
        }
927

928
        $this->options[$name] = $value;
93✔
929
    }
930

931
    private function resolveOptionBooleanValue(string $optionName): bool
932
    {
933
        $value = $this->options[$optionName];
12✔
934

935
        if (!\is_string($value)) {
12✔
936
            throw new InvalidConfigurationException(sprintf('Expected boolean or string value for option "%s".', $optionName));
×
937
        }
938

939
        if ('yes' === $value) {
12✔
940
            return true;
6✔
941
        }
942

943
        if ('no' === $value) {
7✔
944
            return false;
6✔
945
        }
946

947
        throw new InvalidConfigurationException(sprintf('Expected "yes" or "no" for option "%s", got "%s".', $optionName, $value));
1✔
948
    }
949

950
    private static function separatedContextLessInclude(string $path): ConfigInterface
951
    {
952
        $config = include $path;
19✔
953

954
        // verify that the config has an instance of Config
955
        if (!$config instanceof ConfigInterface) {
19✔
956
            throw new InvalidConfigurationException(sprintf('The config file: "%s" does not return a "PhpCsFixer\ConfigInterface" instance. Got: "%s".', $path, \is_object($config) ? \get_class($config) : \gettype($config)));
1✔
957
        }
958

959
        return $config;
18✔
960
    }
961
}
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