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

keradus / PHP-CS-Fixer / 18309234144

06 Oct 2025 10:44AM UTC coverage: 94.148% (-0.2%) from 94.308%
18309234144

push

github

web-flow
docs: more explicit docs on --rules (#9114)

28638 of 30418 relevant lines covered (94.15%)

45.13 hits per line

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

59.16
/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\Config;
18
use PhpCsFixer\Console\Application;
19
use PhpCsFixer\Console\ConfigurationResolver;
20
use PhpCsFixer\Differ\DiffConsoleFormatter;
21
use PhpCsFixer\Differ\FullDiffer;
22
use PhpCsFixer\Documentation\FixerDocumentGenerator;
23
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
24
use PhpCsFixer\Fixer\DeprecatedFixerInterface;
25
use PhpCsFixer\Fixer\ExperimentalFixerInterface;
26
use PhpCsFixer\Fixer\FixerInterface;
27
use PhpCsFixer\Fixer\InternalFixerInterface;
28
use PhpCsFixer\FixerConfiguration\AliasedFixerOption;
29
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
30
use PhpCsFixer\FixerConfiguration\DeprecatedFixerOption;
31
use PhpCsFixer\FixerDefinition\CodeSampleInterface;
32
use PhpCsFixer\FixerDefinition\FileSpecificCodeSampleInterface;
33
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSampleInterface;
34
use PhpCsFixer\FixerFactory;
35
use PhpCsFixer\Future;
36
use PhpCsFixer\Preg;
37
use PhpCsFixer\RuleSet\AutomaticRuleSetDefinitionInterface;
38
use PhpCsFixer\RuleSet\DeprecatedRuleSetDefinitionInterface;
39
use PhpCsFixer\RuleSet\RuleSet;
40
use PhpCsFixer\RuleSet\RuleSetDefinitionInterface;
41
use PhpCsFixer\RuleSet\RuleSets;
42
use PhpCsFixer\StdinFileInfo;
43
use PhpCsFixer\Tokenizer\Tokens;
44
use PhpCsFixer\ToolInfo;
45
use PhpCsFixer\Utils;
46
use PhpCsFixer\WordMatcher;
47
use Symfony\Component\Console\Attribute\AsCommand;
48
use Symfony\Component\Console\Command\Command;
49
use Symfony\Component\Console\Exception\RuntimeException;
50
use Symfony\Component\Console\Formatter\OutputFormatter;
51
use Symfony\Component\Console\Input\InputArgument;
52
use Symfony\Component\Console\Input\InputInterface;
53
use Symfony\Component\Console\Input\InputOption;
54
use Symfony\Component\Console\Output\ConsoleOutputInterface;
55
use Symfony\Component\Console\Output\OutputInterface;
56
use Symfony\Component\Console\Style\SymfonyStyle;
57

58
/**
59
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
60
 *
61
 * @internal
62
 *
63
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
64
 */
65
#[AsCommand(name: 'describe', description: 'Describe rule / ruleset.')]
66
final class DescribeCommand extends Command
67
{
68
    /** @TODO PHP 8.0 - remove the property */
69
    protected static $defaultName = 'describe';
70

71
    /** @TODO PHP 8.0 - remove the property */
72
    protected static $defaultDescription = 'Describe rule / ruleset.';
73

74
    /**
75
     * @var ?list<string>
76
     */
77
    private ?array $setNames = null;
78

79
    private FixerFactory $fixerFactory;
80

81
    /**
82
     * @var null|array<string, FixerInterface>
83
     */
84
    private ?array $fixers = null;
85

86
    public function __construct(?FixerFactory $fixerFactory = null)
87
    {
88
        parent::__construct();
14✔
89

90
        if (null === $fixerFactory) {
14✔
91
            $fixerFactory = new FixerFactory();
14✔
92
            $fixerFactory->registerBuiltInFixers();
14✔
93
        }
94

95
        $this->fixerFactory = $fixerFactory;
14✔
96
    }
97

98
    protected function configure(): void
99
    {
100
        $this->setDefinition(
14✔
101
            [
14✔
102
                new InputArgument('name', InputArgument::OPTIONAL, 'Name of rule / set.', null, fn () => array_merge($this->getSetNames(), array_keys($this->getFixers()))),
14✔
103
                new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a .php-cs-fixer.php file.'),
14✔
104
                new InputOption('expand', '', InputOption::VALUE_NONE, 'Shall nested sets be expanded into nested rules.'),
14✔
105
            ]
14✔
106
        );
14✔
107
    }
108

109
    protected function execute(InputInterface $input, OutputInterface $output): int
110
    {
111
        if ($output instanceof ConsoleOutputInterface) {
14✔
112
            $stdErr = $output->getErrorOutput();
×
113
            $stdErr->writeln(Application::getAboutWithRuntime(true));
×
114
        }
115

116
        $resolver = new ConfigurationResolver(
14✔
117
            new Config(),
14✔
118
            ['config' => $input->getOption('config')],
14✔
119
            getcwd(), // @phpstan-ignore argument.type
14✔
120
            new ToolInfo()
14✔
121
        );
14✔
122

123
        $this->fixerFactory->registerCustomFixers($resolver->getConfig()->getCustomFixers());
14✔
124

125
        /** @var ?string $name */
126
        $name = $input->getArgument('name');
14✔
127
        $expand = $input->getOption('expand');
14✔
128

129
        if (null === $name) {
14✔
130
            if (false === $input->isInteractive()) {
1✔
131
                throw new RuntimeException('Not enough arguments (missing: "name") when not running interactively.');
1✔
132
            }
133

134
            $io = new SymfonyStyle($input, $output);
×
135
            $shallDescribeConfigInUse = 'yes' === $io->choice(
×
136
                'Do you want to describe used configuration? (alias:`@`',
×
137
                ['yes', 'no'],
×
138
                'yes',
×
139
            );
×
140
            if ($shallDescribeConfigInUse) {
×
141
                $name = '@'; // '@' means "describe config file"
×
142
            } else {
143
                $name = $io->choice(
×
144
                    'Please select rule / set to describe',
×
145
                    array_merge($this->getSetNames(), array_keys($this->getFixers()))
×
146
                );
×
147
            }
148
        }
149

150
        if (!str_starts_with($name, '@')) {
13✔
151
            if (true === $expand) {
12✔
152
                throw new \InvalidArgumentException(
×
153
                    'The "--expand" option is available only when describing a set (name starting with "@").',
×
154
                );
×
155
            }
156
        }
157

158
        try {
159
            if (str_starts_with($name, '@')) {
13✔
160
                $this->describeSet($input, $output, $name, $resolver);
1✔
161

162
                return 0;
×
163
            }
164

165
            $this->describeRule($output, $name);
12✔
166
        } catch (DescribeNameNotFoundException $e) {
3✔
167
            $matcher = new WordMatcher(
3✔
168
                'set' === $e->getType() ? $this->getSetNames() : array_keys($this->getFixers())
3✔
169
            );
3✔
170

171
            $alternative = $matcher->match($name);
3✔
172

173
            $this->describeList($output, $e->getType());
3✔
174

175
            throw new \InvalidArgumentException(\sprintf(
3✔
176
                '%s "%s" not found.%s',
3✔
177
                ucfirst($e->getType()),
3✔
178
                $name,
3✔
179
                null === $alternative ? '' : ' Did you mean "'.$alternative.'"?'
3✔
180
            ));
3✔
181
        }
182

183
        return 0;
10✔
184
    }
185

186
    private function describeRule(OutputInterface $output, string $name): void
187
    {
188
        $fixers = $this->getFixers();
12✔
189

190
        if (!isset($fixers[$name])) {
12✔
191
            throw new DescribeNameNotFoundException($name, 'rule');
2✔
192
        }
193

194
        $fixer = $fixers[$name];
10✔
195

196
        $definition = $fixer->getDefinition();
10✔
197

198
        $output->writeln(\sprintf('<fg=blue>Description of the <info>`%s`</info> rule.</>', $name));
10✔
199
        $output->writeln('');
10✔
200

201
        if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
10✔
202
            $output->writeln(\sprintf('Fixer class: <comment>%s</comment>.', \get_class($fixer)));
1✔
203
            $output->writeln('');
1✔
204
        }
205

206
        if ($fixer instanceof DeprecatedFixerInterface) {
10✔
207
            $successors = $fixer->getSuccessorsNames();
3✔
208
            $message = [] === $successors
3✔
209
                ? \sprintf('it will be removed in version %d.0', Application::getMajorVersion() + 1)
×
210
                : \sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors));
3✔
211

212
            $endMessage = '. '.ucfirst($message);
3✔
213
            Future::triggerDeprecation(new \RuntimeException(str_replace('`', '"', "Rule \"{$name}\" is deprecated{$endMessage}.")));
3✔
214
            $message = Preg::replace('/(`[^`]+`)/', '<info>$1</info>', $message);
3✔
215
            $output->writeln(\sprintf('<error>DEPRECATED</error>: %s.', $message));
3✔
216
            $output->writeln('');
3✔
217
        }
218

219
        $output->writeln($definition->getSummary());
10✔
220

221
        $description = $definition->getDescription();
10✔
222

223
        if (null !== $description) {
10✔
224
            $output->writeln($description);
7✔
225
        }
226

227
        $output->writeln('');
10✔
228

229
        if ($fixer instanceof ExperimentalFixerInterface) {
10✔
230
            $output->writeln('<error>Fixer applying this rule is EXPERIMENTAL.</error>.');
×
231
            $output->writeln('It is not covered with backward compatibility promise and may produce unstable or unexpected results.');
×
232

233
            $output->writeln('');
×
234
        }
235

236
        if ($fixer instanceof InternalFixerInterface) {
10✔
237
            $output->writeln('<error>Fixer applying this rule is INTERNAL.</error>.');
×
238
            $output->writeln('It is expected to be used only on PHP CS Fixer project itself.');
×
239

240
            $output->writeln('');
×
241
        }
242

243
        if ($fixer->isRisky()) {
10✔
244
            $output->writeln('<error>Fixer applying this rule is RISKY.</error>');
4✔
245

246
            $riskyDescription = $definition->getRiskyDescription();
4✔
247

248
            if (null !== $riskyDescription) {
4✔
249
                $output->writeln($riskyDescription);
3✔
250
            }
251

252
            $output->writeln('');
4✔
253
        }
254

255
        if ($fixer instanceof ConfigurableFixerInterface) {
10✔
256
            $configurationDefinition = $fixer->getConfigurationDefinition();
4✔
257
            $options = $configurationDefinition->getOptions();
4✔
258

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

261
            foreach ($options as $option) {
4✔
262
                $line = '* <info>'.OutputFormatter::escape($option->getName()).'</info>';
4✔
263
                $allowed = HelpCommand::getDisplayableAllowedValues($option);
4✔
264

265
                if (null === $allowed) {
4✔
266
                    $allowedTypes = $option->getAllowedTypes();
4✔
267
                    if (null !== $allowedTypes) {
4✔
268
                        $allowed = array_map(
4✔
269
                            static fn (string $type): string => '<comment>'.$type.'</comment>',
4✔
270
                            $allowedTypes,
4✔
271
                        );
4✔
272
                    }
273
                } else {
274
                    $allowed = array_map(static fn ($value): string => $value instanceof AllowedValueSubset
4✔
275
                        ? 'a subset of <comment>'.Utils::toString($value->getAllowedValues()).'</comment>'
3✔
276
                        : '<comment>'.Utils::toString($value).'</comment>', $allowed);
4✔
277
                }
278

279
                if (null !== $allowed) {
4✔
280
                    $line .= ' ('.Utils::naturalLanguageJoin($allowed, '').')';
4✔
281
                }
282

283
                $description = Preg::replace('/(`.+?`)/', '<info>$1</info>', OutputFormatter::escape($option->getDescription()));
4✔
284
                $line .= ': '.lcfirst(Preg::replace('/\.$/', '', $description)).'; ';
4✔
285

286
                if ($option->hasDefault()) {
4✔
287
                    $line .= \sprintf(
4✔
288
                        'defaults to <comment>%s</comment>',
4✔
289
                        Utils::toString($option->getDefault())
4✔
290
                    );
4✔
291
                } else {
292
                    $line .= '<comment>required</comment>';
×
293
                }
294

295
                if ($option instanceof DeprecatedFixerOption) {
4✔
296
                    $line .= '. <error>DEPRECATED</error>: '.Preg::replace(
3✔
297
                        '/(`.+?`)/',
3✔
298
                        '<info>$1</info>',
3✔
299
                        OutputFormatter::escape(lcfirst($option->getDeprecationMessage()))
3✔
300
                    );
3✔
301
                }
302

303
                if ($option instanceof AliasedFixerOption) {
4✔
304
                    $line .= '; <error>DEPRECATED</error> alias: <comment>'.$option->getAlias().'</comment>';
3✔
305
                }
306

307
                $output->writeln($line);
4✔
308
            }
309

310
            $output->writeln('');
4✔
311
        }
312

313
        $codeSamples = array_filter($definition->getCodeSamples(), static function (CodeSampleInterface $codeSample): bool {
10✔
314
            if ($codeSample instanceof VersionSpecificCodeSampleInterface) {
8✔
315
                return $codeSample->isSuitableFor(\PHP_VERSION_ID);
2✔
316
            }
317

318
            return true;
7✔
319
        });
10✔
320

321
        if (0 === \count($definition->getCodeSamples())) {
10✔
322
            $output->writeln([
2✔
323
                'Fixing examples are not available for this rule.',
2✔
324
                '',
2✔
325
            ]);
2✔
326
        } elseif (0 === \count($codeSamples)) {
8✔
327
            $output->writeln([
1✔
328
                'Fixing examples <error>cannot be</error> demonstrated on the current PHP version.',
1✔
329
                '',
1✔
330
            ]);
1✔
331
        } else {
332
            $output->writeln('Fixing examples:');
7✔
333

334
            $differ = new FullDiffer();
7✔
335
            $diffFormatter = new DiffConsoleFormatter(
7✔
336
                $output->isDecorated(),
7✔
337
                \sprintf(
7✔
338
                    '<comment>   ---------- begin diff ----------</comment>%s%%s%s<comment>   ----------- end diff -----------</comment>',
7✔
339
                    \PHP_EOL,
7✔
340
                    \PHP_EOL
7✔
341
                )
7✔
342
            );
7✔
343

344
            foreach ($codeSamples as $index => $codeSample) {
7✔
345
                $old = $codeSample->getCode();
7✔
346
                $tokens = Tokens::fromCode($old);
7✔
347

348
                $configuration = $codeSample->getConfiguration();
7✔
349

350
                if ($fixer instanceof ConfigurableFixerInterface) {
7✔
351
                    $fixer->configure($configuration ?? []);
4✔
352
                }
353

354
                $file = $codeSample instanceof FileSpecificCodeSampleInterface
7✔
355
                    ? $codeSample->getSplFileInfo()
×
356
                    : new StdinFileInfo();
7✔
357

358
                $fixer->fix($file, $tokens);
7✔
359

360
                $diff = $differ->diff($old, $tokens->generateCode());
7✔
361

362
                if ($fixer instanceof ConfigurableFixerInterface) {
7✔
363
                    if (null === $configuration) {
4✔
364
                        $output->writeln(\sprintf(' * Example #%d. Fixing with the <comment>default</comment> configuration.', $index + 1));
4✔
365
                    } else {
366
                        $output->writeln(\sprintf(' * Example #%d. Fixing with configuration: <comment>%s</comment>.', $index + 1, Utils::toString($codeSample->getConfiguration())));
4✔
367
                    }
368
                } else {
369
                    $output->writeln(\sprintf(' * Example #%d.', $index + 1));
3✔
370
                }
371

372
                $output->writeln([$diffFormatter->format($diff, '   %s'), '']);
7✔
373
            }
374
        }
375

376
        $ruleSetConfigs = FixerDocumentGenerator::getSetsOfRule($name);
10✔
377

378
        if ([] !== $ruleSetConfigs) {
10✔
379
            ksort($ruleSetConfigs);
1✔
380
            $plural = 1 !== \count($ruleSetConfigs) ? 's' : '';
1✔
381
            $output->writeln("The fixer is part of the following rule set{$plural}:");
1✔
382

383
            $ruleSetDefinitions = RuleSets::getSetDefinitions();
1✔
384

385
            foreach ($ruleSetConfigs as $set => $config) {
1✔
386
                \assert(isset($ruleSetDefinitions[$set]));
1✔
387
                $ruleSetDefinition = $ruleSetDefinitions[$set];
1✔
388

389
                if ($ruleSetDefinition instanceof AutomaticRuleSetDefinitionInterface) {
1✔
390
                    continue;
1✔
391
                }
392

393
                $deprecatedDesc = ($ruleSetDefinition instanceof DeprecatedRuleSetDefinitionInterface) ? ' *(deprecated)*' : '';
1✔
394
                if (null !== $config) {
1✔
395
                    $output->writeln(\sprintf('* <info>%s</info> with config: <comment>%s</comment>', $set.$deprecatedDesc, Utils::toString($config)));
1✔
396
                } else {
397
                    $output->writeln(\sprintf('* <info>%s</info> with <comment>default</comment> config', $set.$deprecatedDesc));
1✔
398
                }
399
            }
400

401
            $output->writeln('');
1✔
402
        }
403
    }
404

405
    private function describeSet(InputInterface $input, OutputInterface $output, string $name, ConfigurationResolver $resolver): void
406
    {
407
        if ('@' !== $name && !\in_array($name, $this->getSetNames(), true)) {
1✔
408
            throw new DescribeNameNotFoundException($name, 'set');
1✔
409
        }
410

411
        if ('@' === $name) {
×
412
            $defaultRuleSetDefinition = $this->createRuleSetDefinition(
×
413
                null,
×
414
                [],
×
415
                [
×
416
                    'getDescription' => null === $resolver->getConfigFile() ? 'Default rules, no config file.' : 'Rules defined in used config.',
×
417
                    'getName' => \sprintf('@ - %s', $resolver->getConfig()->getName()),
×
418
                    'getRules' => $resolver->getConfig()->getRules(),
×
419
                    'isRisky' => $resolver->getRiskyAllowed(),
×
420
                ]
×
421
            );
×
422
        }
423

424
        $ruleSetDefinitions = RuleSets::getSetDefinitions();
×
425
        $ruleSetDefinition = $defaultRuleSetDefinition ?? $ruleSetDefinitions[$name];
×
426
        $fixers = $this->getFixers();
×
427

428
        if (true === $input->getOption('expand')) {
×
429
            $ruleSetDefinition = $this->createRuleSetDefinition($ruleSetDefinition, ['expand'], []);
×
430
        } else {
431
            $output->writeln("You may the '--expand' option to see nested sets expanded into nested rules.");
×
432
        }
433

434
        $output->writeln(\sprintf('<fg=blue>Description of the <info>`%s`</info> set.</>', $ruleSetDefinition->getName()));
×
435
        $output->writeln('');
×
436

437
        $output->writeln($this->replaceRstLinks($ruleSetDefinition->getDescription()));
×
438
        $output->writeln('');
×
439

440
        if ($ruleSetDefinition instanceof DeprecatedRuleSetDefinitionInterface) {
×
441
            $successors = $ruleSetDefinition->getSuccessorsNames();
×
442
            $message = [] === $successors
×
443
                ? \sprintf('it will be removed in version %d.0', Application::getMajorVersion() + 1)
×
444
                : \sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors));
×
445

446
            Future::triggerDeprecation(new \RuntimeException(str_replace('`', '"', "Set \"{$name}\" is deprecated, {$message}.")));
×
447
            $message = Preg::replace('/(`[^`]+`)/', '<info>$1</info>', $message);
×
448
            $output->writeln(\sprintf('<error>DEPRECATED</error>: %s.', $message));
×
449
            $output->writeln('');
×
450
        }
451

452
        if ($ruleSetDefinition->isRisky()) {
×
453
            $output->writeln('<error>This set contains risky rules.</error>');
×
454
            $output->writeln('');
×
455
        }
456

457
        if ($ruleSetDefinition instanceof AutomaticRuleSetDefinitionInterface) {
×
458
            $output->writeln(AutomaticRuleSetDefinitionInterface::WARNING_MESSAGE_DECORATED);
×
459
            $output->writeln('');
×
460
        }
461

462
        $help = '';
×
463

464
        foreach ($ruleSetDefinition->getRules() as $rule => $config) {
×
465
            if (str_starts_with($rule, '@')) {
×
466
                $set = $ruleSetDefinitions[$rule];
×
467
                $help .= \sprintf(
×
468
                    " * <info>%s</info>%s\n   | %s\n\n",
×
469
                    $rule,
×
470
                    $set->isRisky() ? ' <error>risky</error>' : '',
×
471
                    $this->replaceRstLinks($set->getDescription())
×
472
                );
×
473

474
                continue;
×
475
            }
476

477
            $fixer = $fixers[$rule];
×
478

479
            $definition = $fixer->getDefinition();
×
480
            $help .= \sprintf(
×
481
                " * <info>%s</info>%s\n   | %s\n%s\n",
×
482
                $rule,
×
483
                $fixer->isRisky() ? ' <error>risky</error>' : '',
×
484
                $definition->getSummary(),
×
485
                true !== $config ? \sprintf("   <comment>| Configuration: %s</comment>\n", Utils::toString($config)) : ''
×
486
            );
×
487
        }
488

489
        $output->write($help);
×
490
    }
491

492
    /**
493
     * @return array<string, FixerInterface>
494
     */
495
    private function getFixers(): array
496
    {
497
        if (null !== $this->fixers) {
12✔
498
            return $this->fixers;
2✔
499
        }
500

501
        $fixers = [];
12✔
502

503
        foreach ($this->fixerFactory->getFixers() as $fixer) {
12✔
504
            $fixers[$fixer->getName()] = $fixer;
12✔
505
        }
506

507
        $this->fixers = $fixers;
12✔
508
        ksort($this->fixers);
12✔
509

510
        return $this->fixers;
12✔
511
    }
512

513
    /**
514
     * @return list<string>
515
     */
516
    private function getSetNames(): array
517
    {
518
        if (null !== $this->setNames) {
1✔
519
            return $this->setNames;
1✔
520
        }
521

522
        $this->setNames = RuleSets::getSetDefinitionNames();
1✔
523

524
        return $this->setNames;
1✔
525
    }
526

527
    /**
528
     * @param string $type 'rule'|'set'
529
     */
530
    private function describeList(OutputInterface $output, string $type): void
531
    {
532
        if ($output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
3✔
533
            return;
3✔
534
        }
535

536
        if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE || 'set' === $type) {
×
537
            $output->writeln('<comment>Defined sets:</comment>');
×
538

539
            $items = $this->getSetNames();
×
540
            foreach ($items as $item) {
×
541
                $output->writeln(\sprintf('* <info>%s</info>', $item));
×
542
            }
543
        }
544

545
        if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE || 'rule' === $type) {
×
546
            $output->writeln('<comment>Defined rules:</comment>');
×
547

548
            $items = array_keys($this->getFixers());
×
549
            foreach ($items as $item) {
×
550
                $output->writeln(\sprintf('* <info>%s</info>', $item));
×
551
            }
552
        }
553
    }
554

555
    private function replaceRstLinks(string $content): string
556
    {
557
        return Preg::replaceCallback(
×
558
            '/(`[^<]+<[^>]+>`_)/',
×
559
            static fn (array $matches) => Preg::replaceCallback(
×
560
                '/`(.*)<(.*)>`_/',
×
561
                static fn (array $matches): string => $matches[1].'('.$matches[2].')',
×
562
                $matches[1]
×
563
            ),
×
564
            $content
×
565
        );
×
566
    }
567

568
    /**
569
     * @param list<'expand'>                                                                                                        $adjustments
570
     * @param array{getDescription?: string, getName?: string, getRules?: array<string, array<string, mixed>|bool>, isRisky?: bool} $overrides
571
     */
572
    private function createRuleSetDefinition(?RuleSetDefinitionInterface $ruleSetDefinition, array $adjustments, array $overrides): RuleSetDefinitionInterface
573
    {
574
        return new class($ruleSetDefinition, $adjustments, $overrides) implements RuleSetDefinitionInterface {
×
575
            private ?RuleSetDefinitionInterface $original;
576

577
            /** @var list<'expand'> */
578
            private array $adjustments;
579

580
            /** @var array{getDescription?: string, getName?: string, getRules?: array<string, array<string, mixed>|bool>, isRisky?: bool} */
581
            private array $overrides;
582

583
            /**
584
             * @param list<'expand'>                                                                                                        $adjustments
585
             * @param array{getDescription?: string, getName?: string, getRules?: array<string, array<string, mixed>|bool>, isRisky?: bool} $overrides
586
             */
587
            public function __construct(
588
                ?RuleSetDefinitionInterface $original,
589
                array $adjustments,
590
                array $overrides
591
            ) {
592
                $this->original = $original;
×
593
                $this->adjustments = $adjustments;
×
594
                $this->overrides = $overrides;
×
595
            }
596

597
            public function getDescription(): string
598
            {
599
                return $this->overrides[__FUNCTION__]
×
600
                    ?? (null !== $this->original ? $this->original->{__FUNCTION__}() : 'unknown description'); // @phpstan-ignore method.dynamicName
×
601
            }
602

603
            public function getName(): string
604
            {
605
                $value = $this->overrides[__FUNCTION__]
×
606
                    ?? (null !== $this->original ? $this->original->{__FUNCTION__}() : 'unknown name'); // @phpstan-ignore method.dynamicName
×
607

608
                if (\in_array('expand', $this->adjustments, true)) {
×
609
                    $value .= ' (expanded)';
×
610
                }
611

612
                return $value;
×
613
            }
614

615
            public function getRules(): array
616
            {
617
                $value = $this->overrides[__FUNCTION__]
×
618
                    ?? (null !== $this->original ? $this->original->{__FUNCTION__}() : null); // @phpstan-ignore method.dynamicName
×
619

620
                if (null === $value) {
×
621
                    throw new \LogicException('Cannot get rules from unknown original rule set and missing overrides.');
×
622
                }
623

624
                if (\in_array('expand', $this->adjustments, true)) {
×
625
                    $value = (new RuleSet($value))->getRules();
×
626
                }
627

628
                return $value;
×
629
            }
630

631
            public function isRisky(): bool
632
            {
633
                $value = $this->overrides[__FUNCTION__]
×
634
                    ?? (null !== $this->original ? $this->original->{__FUNCTION__}() : null); // @phpstan-ignore method.dynamicName
×
635

636
                if (null === $value) {
×
637
                    throw new \LogicException('Cannot get isRisky from unknown original rule set and missing overrides.');
×
638
                }
639

640
                return $value;
×
641
            }
642
        };
×
643
    }
644
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc