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

keradus / PHP-CS-Fixer / 17975696777

24 Sep 2025 11:13AM UTC coverage: 94.365% (-0.03%) from 94.396%
17975696777

push

github

web-flow
CI: Test docs generation only once per CI pipeline (#9089)

28500 of 30202 relevant lines covered (94.36%)

45.32 hits per line

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

93.59
/src/Documentation/RuleSetDocumentationGenerator.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\Fixer\FixerInterface;
18
use PhpCsFixer\Preg;
19
use PhpCsFixer\RuleSet\AutomaticRuleSetDescriptionInterface;
20
use PhpCsFixer\RuleSet\DeprecatedRuleSetDescriptionInterface;
21
use PhpCsFixer\RuleSet\RuleSetDescriptionInterface;
22
use PhpCsFixer\Utils;
23

24
/**
25
 * @readonly
26
 *
27
 * @internal
28
 *
29
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
30
 */
31
final class RuleSetDocumentationGenerator
32
{
33
    private DocumentationLocator $locator;
34

35
    public function __construct(DocumentationLocator $locator)
36
    {
37
        $this->locator = $locator;
3✔
38
    }
39

40
    /**
41
     * @param list<FixerInterface> $fixers
42
     */
43
    public function generateRuleSetsDocumentation(RuleSetDescriptionInterface $definition, array $fixers): string
44
    {
45
        $fixerNames = [];
2✔
46

47
        foreach ($fixers as $fixer) {
2✔
48
            $fixerNames[$fixer->getName()] = $fixer;
2✔
49
        }
50

51
        $title = "Rule set ``{$definition->getName()}``";
2✔
52
        $titleLine = str_repeat('=', \strlen($title));
2✔
53
        $doc = "{$titleLine}\n{$title}\n{$titleLine}\n\n".$definition->getDescription();
2✔
54

55
        $warnings = [];
2✔
56
        if ($definition instanceof DeprecatedRuleSetDescriptionInterface) {
2✔
57
            $deprecationDescription = <<<'RST'
1✔
58

59
                This rule set is deprecated and will be removed in the next major version
60
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
61
                RST;
1✔
62
            $alternatives = $definition->getSuccessorsNames();
1✔
63

64
            if (0 !== \count($alternatives)) {
1✔
65
                $deprecationDescription .= RstUtils::toRst(
1✔
66
                    \sprintf(
1✔
67
                        "\n\nYou should use %s instead.",
1✔
68
                        Utils::naturalLanguageJoinWithBackticks($alternatives)
1✔
69
                    ),
1✔
70
                    0
1✔
71
                );
1✔
72
            } else {
73
                $deprecationDescription .= 'No replacement available.';
×
74
            }
75

76
            $warnings[] = $deprecationDescription;
1✔
77
        }
78

79
        if ($definition->isRisky()) {
2✔
80
            $warnings[] = <<<'RST'
1✔
81

82
                This set contains rules that are risky
83
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
84

85
                Using this rule set may lead to changes in your code's logic and behaviour. Use it with caution and review changes before incorporating them into your code base.
86
                RST;
1✔
87
        }
88

89
        $header = static function (string $message, string $underline = '-'): string {
2✔
90
            $line = str_repeat($underline, \strlen($message));
2✔
91

92
            return "{$message}\n{$line}\n";
2✔
93
        };
2✔
94

95
        if ($definition instanceof AutomaticRuleSetDescriptionInterface) {
2✔
96
            $warnings[] = "\n".$header('Automatic rule set', '~')."\n⚡ ".strip_tags(AutomaticRuleSetDescriptionInterface::WARNING_MESSAGE_DECORATED);
×
97
        }
98

99
        if ([] !== $warnings) {
2✔
100
            $warningsHeader = 1 === \count($warnings) ? 'Warning' : 'Warnings';
2✔
101

102
            $doc .= "\n\n".$header($warningsHeader).implode("\n", $warnings);
2✔
103
        }
104

105
        $rules = $definition instanceof AutomaticRuleSetDescriptionInterface
2✔
106
                ? $definition->getRulesCandidates()
×
107
                : $definition->getRules();
2✔
108

109
        if ([] === $rules) {
2✔
110
            $doc .= "\n\nThis is an empty set.";
×
111
        } else {
112
            $enabledRules = array_filter($rules, static fn ($config) => false !== $config);
2✔
113
            $disabledRules = array_filter($rules, static fn ($config) => false === $config);
2✔
114

115
            $listRules = function (array $rules) use (&$doc, $fixerNames): void {
2✔
116
                foreach ($rules as $rule => $config) {
2✔
117
                    if (str_starts_with($rule, '@')) {
2✔
118
                        $ruleSetPath = $this->locator->getRuleSetsDocumentationFilePath($rule);
2✔
119
                        $ruleSetPath = substr($ruleSetPath, strrpos($ruleSetPath, '/'));
2✔
120

121
                        $doc .= "\n- `{$rule} <.{$ruleSetPath}>`_";
2✔
122
                    } else {
123
                        $path = Preg::replace(
1✔
124
                            '#^'.preg_quote($this->locator->getFixersDocumentationDirectoryPath(), '#').'/#',
1✔
125
                            './../rules/',
1✔
126
                            $this->locator->getFixerDocumentationFilePath($fixerNames[$rule])
1✔
127
                        );
1✔
128

129
                        $doc .= "\n- `{$rule} <{$path}>`_";
1✔
130
                    }
131

132
                    if (!\is_bool($config)) {
2✔
133
                        $doc .= " with config:\n\n  ``".Utils::toString($config)."``\n";
1✔
134
                    }
135
                }
136
            };
2✔
137

138
            $rulesCandidatesDescriptionHeader = $definition instanceof AutomaticRuleSetDescriptionInterface
2✔
139
                ? ' candidates'
×
140
                : '';
2✔
141

142
            if ([] !== $enabledRules) {
2✔
143
                $doc .= "\n\n".$header("Rules{$rulesCandidatesDescriptionHeader}");
2✔
144
                $listRules($enabledRules);
2✔
145
            }
146

147
            if ([] !== $disabledRules) {
2✔
148
                $doc .= "\n\n".$header("Disabled rules{$rulesCandidatesDescriptionHeader}");
1✔
149

150
                $listRules($disabledRules);
1✔
151
            }
152
        }
153

154
        return $doc."\n";
2✔
155
    }
156

157
    /**
158
     * @param array<string, RuleSetDescriptionInterface> $setDefinitions
159
     */
160
    public function generateRuleSetsDocumentationIndex(array $setDefinitions): string
161
    {
162
        $documentation = <<<'RST'
1✔
163
            ===========================
164
            List of Available Rule sets
165
            ===========================
166
            RST;
1✔
167

168
        foreach ($setDefinitions as $path => $definition) {
1✔
169
            $path = substr($path, strrpos($path, '/'));
1✔
170

171
            $attributes = [];
1✔
172

173
            if ($definition instanceof DeprecatedRuleSetDescriptionInterface) {
1✔
174
                $attributes[] = 'deprecated';
1✔
175
            }
176

177
            $attributes = 0 === \count($attributes)
1✔
178
                ? ''
1✔
179
                : ' *('.implode(', ', $attributes).')*';
1✔
180

181
            $documentation .= "\n- `{$definition->getName()} <.{$path}>`_{$attributes}";
1✔
182
        }
183

184
        return $documentation."\n";
1✔
185
    }
186
}
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