• 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

0.0
/src/Documentation/FixerDocumentGenerator.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\Documentation;
16

17
use PhpCsFixer\Console\Command\HelpCommand;
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\DeprecatedFixerOptionInterface;
25
use PhpCsFixer\FixerDefinition\CodeSampleInterface;
26
use PhpCsFixer\FixerDefinition\FileSpecificCodeSampleInterface;
27
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSampleInterface;
28
use PhpCsFixer\Preg;
29
use PhpCsFixer\RuleSet\RuleSet;
30
use PhpCsFixer\RuleSet\RuleSets;
31
use PhpCsFixer\StdinFileInfo;
32
use PhpCsFixer\Tokenizer\Tokens;
33
use PhpCsFixer\Utils;
34

35
/**
36
 * @internal
37
 */
38
final class FixerDocumentGenerator
39
{
40
    private DocumentationLocator $locator;
41

42
    private FullDiffer $differ;
43

44
    public function __construct(DocumentationLocator $locator)
45
    {
46
        $this->locator = $locator;
×
47
        $this->differ = new FullDiffer();
×
48
    }
49

50
    public function generateFixerDocumentation(FixerInterface $fixer): string
51
    {
52
        $name = $fixer->getName();
×
53
        $title = "Rule ``{$name}``";
×
54
        $titleLine = str_repeat('=', \strlen($title));
×
55
        $doc = "{$titleLine}\n{$title}\n{$titleLine}";
×
56

57
        $definition = $fixer->getDefinition();
×
58
        $doc .= "\n\n".RstUtils::toRst($definition->getSummary());
×
59

60
        $description = $definition->getDescription();
×
61

62
        if (null !== $description) {
×
63
            $description = RstUtils::toRst($description);
×
64
            $doc .= <<<RST
×
65

66

67
Description
68
-----------
69

70
{$description}
×
71
RST;
×
72
        }
73

74
        $deprecationDescription = '';
×
75

76
        if ($fixer instanceof DeprecatedFixerInterface) {
×
77
            $deprecationDescription = <<<'RST'
×
78

79
This rule is deprecated and will be removed on next major version
80
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
81
RST;
×
82
            $alternatives = $fixer->getSuccessorsNames();
×
83

84
            if (0 !== \count($alternatives)) {
×
85
                $deprecationDescription .= RstUtils::toRst(sprintf(
×
86
                    "\n\nYou should use %s instead.",
×
87
                    Utils::naturalLanguageJoinWithBackticks($alternatives)
×
88
                ), 0);
×
89
            }
90
        }
91

92
        $riskyDescription = '';
×
93
        $riskyDescriptionRaw = $definition->getRiskyDescription();
×
94

95
        if (null !== $riskyDescriptionRaw) {
×
96
            $riskyDescriptionRaw = RstUtils::toRst($riskyDescriptionRaw, 0);
×
97
            $riskyDescription = <<<RST
×
98

99
Using this rule is risky
100
~~~~~~~~~~~~~~~~~~~~~~~~
101

102
{$riskyDescriptionRaw}
×
103
RST;
×
104
        }
105

106
        if ($deprecationDescription || $riskyDescription) {
×
107
            $warningsHeader = 'Warning';
×
108

109
            if ($deprecationDescription && $riskyDescription) {
×
110
                $warningsHeader = 'Warnings';
×
111
            }
112

113
            $warningsHeaderLine = str_repeat('-', \strlen($warningsHeader));
×
114
            $doc .= "\n\n".implode("\n", array_filter([
×
115
                $warningsHeader,
×
116
                $warningsHeaderLine,
×
117
                $deprecationDescription,
×
118
                $riskyDescription,
×
119
            ]));
×
120
        }
121

122
        if ($fixer instanceof ConfigurableFixerInterface) {
×
123
            $doc .= <<<'RST'
×
124

125

126
Configuration
127
-------------
128
RST;
×
129

130
            $configurationDefinition = $fixer->getConfigurationDefinition();
×
131

132
            foreach ($configurationDefinition->getOptions() as $option) {
×
133
                $optionInfo = "``{$option->getName()}``";
×
134
                $optionInfo .= "\n".str_repeat('~', \strlen($optionInfo));
×
135

136
                if ($option instanceof DeprecatedFixerOptionInterface) {
×
137
                    $deprecationMessage = RstUtils::toRst($option->getDeprecationMessage());
×
138
                    $optionInfo .= "\n\n.. warning:: This option is deprecated and will be removed on next major version. {$deprecationMessage}";
×
139
                }
140

141
                $optionInfo .= "\n\n".RstUtils::toRst($option->getDescription());
×
142

143
                if ($option instanceof AliasedFixerOption) {
×
144
                    $optionInfo .= "\n\n.. note:: The previous name of this option was ``{$option->getAlias()}`` but it is now deprecated and will be removed on next major version.";
×
145
                }
146

147
                $allowed = HelpCommand::getDisplayableAllowedValues($option);
×
148

149
                if (null === $allowed) {
×
150
                    $allowedKind = 'Allowed types';
×
151
                    $allowed = array_map(
×
152
                        static fn ($value): string => '``'.$value.'``',
×
153
                        $option->getAllowedTypes(),
×
154
                    );
×
155
                } else {
156
                    $allowedKind = 'Allowed values';
×
157
                    $allowed = array_map(static function ($value): string {
×
158
                        return $value instanceof AllowedValueSubset
×
159
                            ? 'a subset of ``'.HelpCommand::toString($value->getAllowedValues()).'``'
×
160
                            : '``'.HelpCommand::toString($value).'``';
×
161
                    }, $allowed);
×
162
                }
163

164
                $allowed = implode(', ', $allowed);
×
165
                $optionInfo .= "\n\n{$allowedKind}: {$allowed}";
×
166

167
                if ($option->hasDefault()) {
×
168
                    $default = HelpCommand::toString($option->getDefault());
×
169
                    $optionInfo .= "\n\nDefault value: ``{$default}``";
×
170
                } else {
171
                    $optionInfo .= "\n\nThis option is required.";
×
172
                }
173

174
                $doc .= "\n\n{$optionInfo}";
×
175
            }
176
        }
177

178
        $samples = $definition->getCodeSamples();
×
179

180
        if (0 !== \count($samples)) {
×
181
            $doc .= <<<'RST'
×
182

183

184
Examples
185
--------
186
RST;
×
187

188
            foreach ($samples as $index => $sample) {
×
189
                $title = sprintf('Example #%d', $index + 1);
×
190
                $titleLine = str_repeat('~', \strlen($title));
×
191
                $doc .= "\n\n{$title}\n{$titleLine}";
×
192

193
                if ($fixer instanceof ConfigurableFixerInterface) {
×
194
                    if (null === $sample->getConfiguration()) {
×
195
                        $doc .= "\n\n*Default* configuration.";
×
196
                    } else {
197
                        $doc .= sprintf(
×
198
                            "\n\nWith configuration: ``%s``.",
×
199
                            HelpCommand::toString($sample->getConfiguration())
×
200
                        );
×
201
                    }
202
                }
203

204
                $doc .= "\n".$this->generateSampleDiff($fixer, $sample, $index + 1, $name);
×
205
            }
206
        }
207

208
        $ruleSetConfigs = [];
×
209

210
        foreach (RuleSets::getSetDefinitionNames() as $set) {
×
211
            $ruleSet = new RuleSet([$set => true]);
×
212

213
            if ($ruleSet->hasRule($name)) {
×
214
                $ruleSetConfigs[$set] = $ruleSet->getRuleConfiguration($name);
×
215
            }
216
        }
217

218
        if ([] !== $ruleSetConfigs) {
×
219
            $plural = 1 !== \count($ruleSetConfigs) ? 's' : '';
×
220
            $doc .= <<<RST
×
221

222

223
Rule sets
224
---------
225

226
The rule is part of the following rule set{$plural}:
×
227
RST;
×
228

229
            foreach ($ruleSetConfigs as $set => $config) {
×
230
                $ruleSetPath = $this->locator->getRuleSetsDocumentationFilePath($set);
×
231
                $ruleSetPath = substr($ruleSetPath, strrpos($ruleSetPath, '/'));
×
232

233
                $doc .= <<<RST
×
234

235

236
{$set}
×
237
  Using the `{$set} <./../../ruleSets{$ruleSetPath}>`_ rule set will enable the ``{$name}`` rule
×
238
RST;
×
239

240
                if (null !== $config) {
×
241
                    $doc .= " with the config below:\n\n  ``".HelpCommand::toString($config).'``';
×
242
                } elseif ($fixer instanceof ConfigurableFixerInterface) {
×
243
                    $doc .= ' with the default config.';
×
244
                } else {
245
                    $doc .= '.';
×
246
                }
247
            }
248
        }
249

250
        return "{$doc}\n";
×
251
    }
252

253
    /**
254
     * @param FixerInterface[] $fixers
255
     */
256
    public function generateFixersDocumentationIndex(array $fixers): string
257
    {
258
        $overrideGroups = [
×
259
            'PhpUnit' => 'PHPUnit',
×
260
            'PhpTag' => 'PHP Tag',
×
261
            'Phpdoc' => 'PHPDoc',
×
262
        ];
×
263

264
        usort($fixers, static function (FixerInterface $a, FixerInterface $b): int {
×
265
            return strcmp(\get_class($a), \get_class($b));
×
266
        });
×
267

268
        $documentation = <<<'RST'
×
269
=======================
270
List of Available Rules
271
=======================
272
RST;
×
273

274
        $currentGroup = null;
×
275

276
        foreach ($fixers as $fixer) {
×
277
            $namespace = Preg::replace('/^.*\\\\(.+)\\\\.+Fixer$/', '$1', \get_class($fixer));
×
278
            $group = $overrideGroups[$namespace] ?? Preg::replace('/(?<=[[:lower:]])(?=[[:upper:]])/', ' ', $namespace);
×
279

280
            if ($group !== $currentGroup) {
×
281
                $underline = str_repeat('-', \strlen($group));
×
282
                $documentation .= "\n\n{$group}\n{$underline}\n";
×
283

284
                $currentGroup = $group;
×
285
            }
286

287
            $path = './'.$this->locator->getFixerDocumentationFileRelativePath($fixer);
×
288

289
            $attributes = [];
×
290

291
            if ($fixer instanceof DeprecatedFixerInterface) {
×
292
                $attributes[] = 'deprecated';
×
293
            }
294

295
            if ($fixer->isRisky()) {
×
296
                $attributes[] = 'risky';
×
297
            }
298

299
            $attributes = 0 === \count($attributes)
×
300
                ? ''
×
301
                : ' *('.implode(', ', $attributes).')*'
×
302
            ;
×
303

304
            $summary = str_replace('`', '``', $fixer->getDefinition()->getSummary());
×
305

306
            $documentation .= <<<RST
×
307

308
- `{$fixer->getName()} <{$path}>`_{$attributes}
×
309

310
  {$summary}
×
311
RST;
×
312
        }
313

314
        return "{$documentation}\n";
×
315
    }
316

317
    private function generateSampleDiff(FixerInterface $fixer, CodeSampleInterface $sample, int $sampleNumber, string $ruleName): string
318
    {
319
        if ($sample instanceof VersionSpecificCodeSampleInterface && !$sample->isSuitableFor(\PHP_VERSION_ID)) {
×
320
            $existingFile = @file_get_contents($this->locator->getFixerDocumentationFilePath($fixer));
×
321

322
            if (false !== $existingFile) {
×
323
                Preg::match("/\\RExample #{$sampleNumber}\\R.+?(?<diff>\\R\\.\\. code-block:: diff\\R\\R.*?)\\R(?:\\R\\S|$)/s", $existingFile, $matches);
×
324

325
                if (isset($matches['diff'])) {
×
326
                    return $matches['diff'];
×
327
                }
328
            }
329

330
            $error = <<<RST
×
331

332
.. error::
333
   Cannot generate diff for code sample #{$sampleNumber} of rule {$ruleName}:
×
334
   the sample is not suitable for current version of PHP (%s).
335
RST;
×
336

337
            return sprintf($error, PHP_VERSION);
×
338
        }
339

340
        $old = $sample->getCode();
×
341

342
        $tokens = Tokens::fromCode($old);
×
343
        $file = $sample instanceof FileSpecificCodeSampleInterface
×
344
            ? $sample->getSplFileInfo()
×
345
            : new StdinFileInfo()
×
346
        ;
×
347

348
        if ($fixer instanceof ConfigurableFixerInterface) {
×
349
            $fixer->configure($sample->getConfiguration() ?? []);
×
350
        }
351

352
        $fixer->fix($file, $tokens);
×
353

354
        $diff = $this->differ->diff($old, $tokens->generateCode());
×
355
        $diff = Preg::replace('/@@[ \+\-\d,]+@@\n/', '', $diff);
×
356
        $diff = Preg::replace('/\r/', '^M', $diff);
×
357
        $diff = Preg::replace('/^ $/m', '', $diff);
×
358
        $diff = Preg::replace('/\n$/', '', $diff);
×
359
        $diff = RstUtils::indent($diff, 3);
×
360

361
        return <<<RST
×
362

363
.. code-block:: diff
364

365
   {$diff}
×
366
RST;
×
367
    }
368
}
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