• 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

71.14
/src/Console/Command/DescribeCommand.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\Command;
16

17
use PhpCsFixer\Differ\DiffConsoleFormatter;
18
use PhpCsFixer\Differ\FullDiffer;
19
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
20
use PhpCsFixer\Fixer\DeprecatedFixerInterface;
21
use PhpCsFixer\Fixer\FixerInterface;
22
use PhpCsFixer\FixerConfiguration\AliasedFixerOption;
23
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
24
use PhpCsFixer\FixerConfiguration\DeprecatedFixerOption;
25
use PhpCsFixer\FixerDefinition\CodeSampleInterface;
26
use PhpCsFixer\FixerDefinition\FileSpecificCodeSampleInterface;
27
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSampleInterface;
28
use PhpCsFixer\FixerFactory;
29
use PhpCsFixer\Preg;
30
use PhpCsFixer\RuleSet\RuleSets;
31
use PhpCsFixer\StdinFileInfo;
32
use PhpCsFixer\Tokenizer\Tokens;
33
use PhpCsFixer\Utils;
34
use PhpCsFixer\WordMatcher;
35
use Symfony\Component\Console\Attribute\AsCommand;
36
use Symfony\Component\Console\Command\Command;
37
use Symfony\Component\Console\Formatter\OutputFormatter;
38
use Symfony\Component\Console\Input\InputArgument;
39
use Symfony\Component\Console\Input\InputInterface;
40
use Symfony\Component\Console\Output\ConsoleOutputInterface;
41
use Symfony\Component\Console\Output\OutputInterface;
42

43
/**
44
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
45
 *
46
 * @internal
47
 */
48
#[AsCommand(name: 'describe')]
49
final class DescribeCommand extends Command
50
{
51
    /**
52
     * @var string
53
     */
54
    protected static $defaultName = 'describe';
55

56
    /**
57
     * @var string[]
58
     */
59
    private $setNames;
60

61
    private FixerFactory $fixerFactory;
62

63
    /**
64
     * @var array<string, FixerInterface>
65
     */
66
    private $fixers;
67

68
    public function __construct(?FixerFactory $fixerFactory = null)
69
    {
70
        parent::__construct();
8✔
71

72
        if (null === $fixerFactory) {
8✔
73
            $fixerFactory = new FixerFactory();
8✔
74
            $fixerFactory->registerBuiltInFixers();
8✔
75
        }
76

77
        $this->fixerFactory = $fixerFactory;
8✔
78
    }
79

80
    /**
81
     * {@inheritdoc}
82
     */
83
    protected function configure(): void
84
    {
85
        $this
8✔
86
            ->setDefinition(
8✔
87
                [
8✔
88
                    new InputArgument('name', InputArgument::REQUIRED, 'Name of rule / set.'),
8✔
89
                ]
8✔
90
            )
8✔
91
            ->setDescription('Describe rule / ruleset.')
8✔
92
        ;
8✔
93
    }
94

95
    /**
96
     * {@inheritdoc}
97
     */
98
    protected function execute(InputInterface $input, OutputInterface $output): int
99
    {
100
        if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity() && $output instanceof ConsoleOutputInterface) {
7✔
101
            $stdErr = $output->getErrorOutput();
×
102
            $stdErr->writeln($this->getApplication()->getLongVersion());
×
103
        }
104

105
        $name = $input->getArgument('name');
7✔
106

107
        try {
108
            if (str_starts_with($name, '@')) {
7✔
109
                $this->describeSet($output, $name);
1✔
110

111
                return 0;
×
112
            }
113

114
            $this->describeRule($output, $name);
6✔
115
        } catch (DescribeNameNotFoundException $e) {
3✔
116
            $matcher = new WordMatcher(
3✔
117
                'set' === $e->getType() ? $this->getSetNames() : array_keys($this->getFixers())
3✔
118
            );
3✔
119

120
            $alternative = $matcher->match($name);
3✔
121

122
            $this->describeList($output, $e->getType());
3✔
123

124
            throw new \InvalidArgumentException(sprintf(
3✔
125
                '%s "%s" not found.%s',
3✔
126
                ucfirst($e->getType()),
3✔
127
                $name,
3✔
128
                null === $alternative ? '' : ' Did you mean "'.$alternative.'"?'
3✔
129
            ));
3✔
130
        }
131

132
        return 0;
4✔
133
    }
134

135
    private function describeRule(OutputInterface $output, string $name): void
136
    {
137
        $fixers = $this->getFixers();
6✔
138

139
        if (!isset($fixers[$name])) {
6✔
140
            throw new DescribeNameNotFoundException($name, 'rule');
2✔
141
        }
142

143
        /** @var FixerInterface $fixer */
144
        $fixer = $fixers[$name];
4✔
145

146
        $definition = $fixer->getDefinition();
4✔
147

148
        $summary = $definition->getSummary();
4✔
149

150
        if ($fixer instanceof DeprecatedFixerInterface) {
4✔
151
            $successors = $fixer->getSuccessorsNames();
3✔
152
            $message = [] === $successors
3✔
153
                ? 'will be removed on next major version'
×
154
                : sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors));
3✔
155
            $message = Preg::replace('/(`.+?`)/', '<info>$1</info>', $message);
3✔
156
            $summary .= sprintf(' <error>DEPRECATED</error>: %s.', $message);
3✔
157
        }
158

159
        $output->writeln(sprintf('<info>Description of</info> %s <info>rule</info>.', $name));
4✔
160

161
        if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
4✔
162
            $output->writeln(sprintf('Fixer class: <comment>%s</comment>.', \get_class($fixer)));
1✔
163
        }
164

165
        $output->writeln($summary);
4✔
166

167
        $description = $definition->getDescription();
4✔
168

169
        if (null !== $description) {
4✔
170
            $output->writeln($description);
3✔
171
        }
172

173
        $output->writeln('');
4✔
174

175
        if ($fixer->isRisky()) {
4✔
176
            $output->writeln('<error>Fixer applying this rule is risky.</error>');
4✔
177

178
            $riskyDescription = $definition->getRiskyDescription();
4✔
179

180
            if (null !== $riskyDescription) {
4✔
181
                $output->writeln($riskyDescription);
3✔
182
            }
183

184
            $output->writeln('');
4✔
185
        }
186

187
        if ($fixer instanceof ConfigurableFixerInterface) {
4✔
188
            $configurationDefinition = $fixer->getConfigurationDefinition();
3✔
189
            $options = $configurationDefinition->getOptions();
3✔
190

191
            $output->writeln(sprintf('Fixer is configurable using following option%s:', 1 === \count($options) ? '' : 's'));
3✔
192

193
            foreach ($options as $option) {
3✔
194
                $line = '* <info>'.OutputFormatter::escape($option->getName()).'</info>';
3✔
195
                $allowed = HelpCommand::getDisplayableAllowedValues($option);
3✔
196

197
                if (null === $allowed) {
3✔
198
                    $allowed = array_map(
3✔
199
                        static fn (string $type): string => '<comment>'.$type.'</comment>',
3✔
200
                        $option->getAllowedTypes(),
3✔
201
                    );
3✔
202
                } else {
203
                    $allowed = array_map(static function ($value): string {
3✔
204
                        return $value instanceof AllowedValueSubset
3✔
205
                            ? 'a subset of <comment>'.HelpCommand::toString($value->getAllowedValues()).'</comment>'
3✔
206
                            : '<comment>'.HelpCommand::toString($value).'</comment>';
3✔
207
                    }, $allowed);
3✔
208
                }
209

210
                $line .= ' ('.implode(', ', $allowed).')';
3✔
211

212
                $description = Preg::replace('/(`.+?`)/', '<info>$1</info>', OutputFormatter::escape($option->getDescription()));
3✔
213
                $line .= ': '.lcfirst(Preg::replace('/\.$/', '', $description)).'; ';
3✔
214

215
                if ($option->hasDefault()) {
3✔
216
                    $line .= sprintf(
3✔
217
                        'defaults to <comment>%s</comment>',
3✔
218
                        HelpCommand::toString($option->getDefault())
3✔
219
                    );
3✔
220
                } else {
221
                    $line .= '<comment>required</comment>';
×
222
                }
223

224
                if ($option instanceof DeprecatedFixerOption) {
3✔
225
                    $line .= '. <error>DEPRECATED</error>: '.Preg::replace(
3✔
226
                        '/(`.+?`)/',
3✔
227
                        '<info>$1</info>',
3✔
228
                        OutputFormatter::escape(lcfirst($option->getDeprecationMessage()))
3✔
229
                    );
3✔
230
                }
231

232
                if ($option instanceof AliasedFixerOption) {
3✔
233
                    $line .= '; <error>DEPRECATED</error> alias: <comment>'.$option->getAlias().'</comment>';
3✔
234
                }
235

236
                $output->writeln($line);
3✔
237
            }
238

239
            $output->writeln('');
3✔
240
        }
241

242
        /** @var CodeSampleInterface[] $codeSamples */
243
        $codeSamples = array_filter($definition->getCodeSamples(), static function (CodeSampleInterface $codeSample): bool {
4✔
244
            if ($codeSample instanceof VersionSpecificCodeSampleInterface) {
3✔
245
                return $codeSample->isSuitableFor(\PHP_VERSION_ID);
×
246
            }
247

248
            return true;
3✔
249
        });
4✔
250

251
        if (0 === \count($codeSamples)) {
4✔
252
            $output->writeln([
1✔
253
                'Fixing examples cannot be demonstrated on the current PHP version.',
1✔
254
                '',
1✔
255
            ]);
1✔
256
        } else {
257
            $output->writeln('Fixing examples:');
3✔
258

259
            $differ = new FullDiffer();
3✔
260
            $diffFormatter = new DiffConsoleFormatter(
3✔
261
                $output->isDecorated(),
3✔
262
                sprintf(
3✔
263
                    '<comment>   ---------- begin diff ----------</comment>%s%%s%s<comment>   ----------- end diff -----------</comment>',
3✔
264
                    PHP_EOL,
3✔
265
                    PHP_EOL
3✔
266
                )
3✔
267
            );
3✔
268

269
            foreach ($codeSamples as $index => $codeSample) {
3✔
270
                $old = $codeSample->getCode();
3✔
271
                $tokens = Tokens::fromCode($old);
3✔
272

273
                $configuration = $codeSample->getConfiguration();
3✔
274

275
                if ($fixer instanceof ConfigurableFixerInterface) {
3✔
276
                    $fixer->configure($configuration ?? []);
3✔
277
                }
278

279
                $file = $codeSample instanceof FileSpecificCodeSampleInterface
3✔
280
                    ? $codeSample->getSplFileInfo()
×
281
                    : new StdinFileInfo();
3✔
282

283
                $fixer->fix($file, $tokens);
3✔
284

285
                $diff = $differ->diff($old, $tokens->generateCode());
3✔
286

287
                if ($fixer instanceof ConfigurableFixerInterface) {
3✔
288
                    if (null === $configuration) {
3✔
289
                        $output->writeln(sprintf(' * Example #%d. Fixing with the <comment>default</comment> configuration.', $index + 1));
3✔
290
                    } else {
291
                        $output->writeln(sprintf(' * Example #%d. Fixing with configuration: <comment>%s</comment>.', $index + 1, HelpCommand::toString($codeSample->getConfiguration())));
3✔
292
                    }
293
                } else {
294
                    $output->writeln(sprintf(' * Example #%d.', $index + 1));
×
295
                }
296

297
                $output->writeln([$diffFormatter->format($diff, '   %s'), '']);
3✔
298
            }
299
        }
300
    }
301

302
    private function describeSet(OutputInterface $output, string $name): void
303
    {
304
        if (!\in_array($name, $this->getSetNames(), true)) {
1✔
305
            throw new DescribeNameNotFoundException($name, 'set');
1✔
306
        }
307

308
        $ruleSetDefinitions = RuleSets::getSetDefinitions();
×
309
        $fixers = $this->getFixers();
×
310

311
        $output->writeln(sprintf('<info>Description of the</info> %s <info>set.</info>', $ruleSetDefinitions[$name]->getName()));
×
312
        $output->writeln($this->replaceRstLinks($ruleSetDefinitions[$name]->getDescription()));
×
313

314
        if ($ruleSetDefinitions[$name]->isRisky()) {
×
315
            $output->writeln('This set contains <error>risky</error> rules.');
×
316
        }
317

318
        $output->writeln('');
×
319

320
        $help = '';
×
321

322
        foreach ($ruleSetDefinitions[$name]->getRules() as $rule => $config) {
×
323
            if (str_starts_with($rule, '@')) {
×
324
                $set = $ruleSetDefinitions[$rule];
×
325
                $help .= sprintf(
×
326
                    " * <info>%s</info>%s\n   | %s\n\n",
×
327
                    $rule,
×
328
                    $set->isRisky() ? ' <error>risky</error>' : '',
×
329
                    $this->replaceRstLinks($set->getDescription())
×
330
                );
×
331

332
                continue;
×
333
            }
334

335
            /** @var FixerInterface $fixer */
336
            $fixer = $fixers[$rule];
×
337

338
            $definition = $fixer->getDefinition();
×
339
            $help .= sprintf(
×
340
                " * <info>%s</info>%s\n   | %s\n%s\n",
×
341
                $rule,
×
342
                $fixer->isRisky() ? ' <error>risky</error>' : '',
×
343
                $definition->getSummary(),
×
344
                true !== $config ? sprintf("   <comment>| Configuration: %s</comment>\n", HelpCommand::toString($config)) : ''
×
345
            );
×
346
        }
347

348
        $output->write($help);
×
349
    }
350

351
    /**
352
     * @return array<string, FixerInterface>
353
     */
354
    private function getFixers(): array
355
    {
356
        if (null !== $this->fixers) {
6✔
357
            return $this->fixers;
2✔
358
        }
359

360
        $fixers = [];
6✔
361

362
        foreach ($this->fixerFactory->getFixers() as $fixer) {
6✔
363
            $fixers[$fixer->getName()] = $fixer;
5✔
364
        }
365

366
        $this->fixers = $fixers;
6✔
367
        ksort($this->fixers);
6✔
368

369
        return $this->fixers;
6✔
370
    }
371

372
    /**
373
     * @return string[]
374
     */
375
    private function getSetNames(): array
376
    {
377
        if (null !== $this->setNames) {
1✔
378
            return $this->setNames;
1✔
379
        }
380

381
        $this->setNames = RuleSets::getSetDefinitionNames();
1✔
382

383
        return $this->setNames;
1✔
384
    }
385

386
    /**
387
     * @param string $type 'rule'|'set'
388
     */
389
    private function describeList(OutputInterface $output, string $type): void
390
    {
391
        if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) {
3✔
392
            $describe = [
×
393
                'sets' => $this->getSetNames(),
×
394
                'rules' => $this->getFixers(),
×
395
            ];
×
396
        } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
3✔
397
            $describe = 'set' === $type ? ['sets' => $this->getSetNames()] : ['rules' => $this->getFixers()];
×
398
        } else {
399
            return;
3✔
400
        }
401

402
        /** @var string[] $items */
403
        foreach ($describe as $list => $items) {
×
404
            $output->writeln(sprintf('<comment>Defined %s:</comment>', $list));
×
405

406
            foreach ($items as $name => $item) {
×
407
                $output->writeln(sprintf('* <info>%s</info>', \is_string($name) ? $name : $item));
×
408
            }
409
        }
410
    }
411

412
    private function replaceRstLinks(string $content): string
413
    {
414
        return Preg::replaceCallback(
×
415
            '/(`[^<]+<[^>]+>`_)/',
×
416
            static function (array $matches) {
×
417
                return Preg::replaceCallback(
×
418
                    '/`(.*)<(.*)>`_/',
×
419
                    static function (array $matches): string {
×
420
                        return $matches[1].'('.$matches[2].')';
×
421
                    },
×
422
                    $matches[1]
×
423
                );
×
424
            },
×
425
            $content
×
426
        );
×
427
    }
428
}
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