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

keradus / PHP-CS-Fixer / 16428450756

21 Jul 2025 06:19PM UTC coverage: 94.756% (-0.002%) from 94.758%
16428450756

push

github

web-flow
fix: always reach 100% of checked files (#8861)

4 of 4 new or added lines in 1 file covered. (100.0%)

111 existing lines in 19 files now uncovered.

28186 of 29746 relevant lines covered (94.76%)

45.93 hits per line

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

98.26
/src/Fixer/ClassNotation/OrderedInterfacesFixer.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\Fixer\ClassNotation;
16

17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
19
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
20
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
21
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
22
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
23
use PhpCsFixer\FixerDefinition\CodeSample;
24
use PhpCsFixer\FixerDefinition\FixerDefinition;
25
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
26
use PhpCsFixer\Tokenizer\Token;
27
use PhpCsFixer\Tokenizer\Tokens;
28

29
/**
30
 * @phpstan-type _AutogeneratedInputConfiguration array{
31
 *  case_sensitive?: bool,
32
 *  direction?: 'ascend'|'descend',
33
 *  order?: 'alpha'|'length',
34
 * }
35
 * @phpstan-type _AutogeneratedComputedConfiguration array{
36
 *  case_sensitive: bool,
37
 *  direction: 'ascend'|'descend',
38
 *  order: 'alpha'|'length',
39
 * }
40
 *
41
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
42
 *
43
 * @author Dave van der Brugge <dmvdbrugge@gmail.com>
44
 */
45
final class OrderedInterfacesFixer extends AbstractFixer implements ConfigurableFixerInterface
46
{
47
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
48
    use ConfigurableFixerTrait;
49

50
    /** @internal */
51
    public const OPTION_DIRECTION = 'direction';
52

53
    /** @internal */
54
    public const OPTION_ORDER = 'order';
55

56
    /** @internal */
57
    public const DIRECTION_ASCEND = 'ascend';
58

59
    /** @internal */
60
    public const DIRECTION_DESCEND = 'descend';
61

62
    /** @internal */
63
    public const ORDER_ALPHA = 'alpha';
64

65
    /** @internal */
66
    public const ORDER_LENGTH = 'length';
67

68
    /**
69
     * Array of supported directions in configuration.
70
     *
71
     * @var list<string>
72
     */
73
    private const SUPPORTED_DIRECTION_OPTIONS = [
74
        self::DIRECTION_ASCEND,
75
        self::DIRECTION_DESCEND,
76
    ];
77

78
    /**
79
     * Array of supported orders in configuration.
80
     *
81
     * @var list<string>
82
     */
83
    private const SUPPORTED_ORDER_OPTIONS = [
84
        self::ORDER_ALPHA,
85
        self::ORDER_LENGTH,
86
    ];
87

88
    public function getDefinition(): FixerDefinitionInterface
89
    {
90
        return new FixerDefinition(
3✔
91
            'Orders the interfaces in an `implements` or `interface extends` clause.',
3✔
92
            [
3✔
93
                new CodeSample(
3✔
94
                    "<?php\n\nfinal class ExampleA implements Gamma, Alpha, Beta {}\n\ninterface ExampleB extends Gamma, Alpha, Beta {}\n"
3✔
95
                ),
3✔
96
                new CodeSample(
3✔
97
                    "<?php\n\nfinal class ExampleA implements Gamma, Alpha, Beta {}\n\ninterface ExampleB extends Gamma, Alpha, Beta {}\n",
3✔
98
                    [self::OPTION_DIRECTION => self::DIRECTION_DESCEND]
3✔
99
                ),
3✔
100
                new CodeSample(
3✔
101
                    "<?php\n\nfinal class ExampleA implements MuchLonger, Short, Longer {}\n\ninterface ExampleB extends MuchLonger, Short, Longer {}\n",
3✔
102
                    [self::OPTION_ORDER => self::ORDER_LENGTH]
3✔
103
                ),
3✔
104
                new CodeSample(
3✔
105
                    "<?php\n\nfinal class ExampleA implements MuchLonger, Short, Longer {}\n\ninterface ExampleB extends MuchLonger, Short, Longer {}\n",
3✔
106
                    [
3✔
107
                        self::OPTION_ORDER => self::ORDER_LENGTH,
3✔
108
                        self::OPTION_DIRECTION => self::DIRECTION_DESCEND,
3✔
109
                    ]
3✔
110
                ),
3✔
111
                new CodeSample(
3✔
112
                    "<?php\n\nfinal class ExampleA implements IgnorecaseB, IgNoReCaSeA, IgnoreCaseC {}\n\ninterface ExampleB extends IgnorecaseB, IgNoReCaSeA, IgnoreCaseC {}\n",
3✔
113
                    [
3✔
114
                        self::OPTION_ORDER => self::ORDER_ALPHA,
3✔
115
                    ]
3✔
116
                ),
3✔
117
                new CodeSample(
3✔
118
                    "<?php\n\nfinal class ExampleA implements Casesensitivea, CaseSensitiveA, CasesensitiveA {}\n\ninterface ExampleB extends Casesensitivea, CaseSensitiveA, CasesensitiveA {}\n",
3✔
119
                    [
3✔
120
                        self::OPTION_ORDER => self::ORDER_ALPHA,
3✔
121
                        'case_sensitive' => true,
3✔
122
                    ]
3✔
123
                ),
3✔
124
            ],
3✔
125
        );
3✔
126
    }
127

128
    /**
129
     * {@inheritdoc}
130
     *
131
     * Must run after FullyQualifiedStrictTypesFixer.
132
     */
133
    public function getPriority(): int
134
    {
135
        return 0;
1✔
136
    }
137

138
    public function isCandidate(Tokens $tokens): bool
139
    {
140
        return $tokens->isTokenKindFound(\T_IMPLEMENTS)
27✔
141
            || $tokens->isAllTokenKindsFound([\T_INTERFACE, \T_EXTENDS]);
27✔
142
    }
143

144
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
145
    {
146
        foreach ($tokens as $index => $token) {
27✔
147
            if (!$token->isGivenKind(\T_IMPLEMENTS)) {
27✔
148
                if (!$token->isGivenKind(\T_EXTENDS)) {
27✔
149
                    continue;
27✔
150
                }
151

152
                $nameTokenIndex = $tokens->getPrevMeaningfulToken($index);
2✔
153
                $interfaceTokenIndex = $tokens->getPrevMeaningfulToken($nameTokenIndex);
2✔
154
                $interfaceToken = $tokens[$interfaceTokenIndex];
2✔
155

156
                if (!$interfaceToken->isGivenKind(\T_INTERFACE)) {
2✔
157
                    continue;
×
158
                }
159
            }
160

161
            $implementsStart = $index + 1;
27✔
162
            $implementsEnd = $tokens->getPrevMeaningfulToken($tokens->getNextTokenOfKind($implementsStart, ['{']));
27✔
163

164
            $interfacesTokens = $this->getInterfaces($tokens, $implementsStart, $implementsEnd);
27✔
165

166
            if (1 === \count($interfacesTokens)) {
27✔
167
                continue;
5✔
168
            }
169

170
            $interfaces = [];
22✔
171
            foreach ($interfacesTokens as $interfaceIndex => $interface) {
22✔
172
                $interfaceTokens = Tokens::fromArray($interface);
22✔
173
                $normalized = '';
22✔
174
                $actualInterfaceIndex = $interfaceTokens->getNextMeaningfulToken(-1);
22✔
175

176
                while ($interfaceTokens->offsetExists($actualInterfaceIndex)) {
22✔
177
                    $token = $interfaceTokens[$actualInterfaceIndex];
22✔
178

179
                    if ($token->isComment() || $token->isWhitespace()) {
22✔
UNCOV
180
                        break;
×
181
                    }
182

183
                    $normalized .= str_replace('\\', ' ', $token->getContent());
22✔
184
                    ++$actualInterfaceIndex;
22✔
185
                }
186

187
                $interfaces[$interfaceIndex] = [
22✔
188
                    'tokens' => $interface,
22✔
189
                    'normalized' => $normalized,
22✔
190
                    'originalIndex' => $interfaceIndex,
22✔
191
                ];
22✔
192
            }
193

194
            usort($interfaces, function (array $first, array $second): int {
22✔
195
                $score = self::ORDER_LENGTH === $this->configuration[self::OPTION_ORDER]
22✔
196
                    ? \strlen($first['normalized']) - \strlen($second['normalized'])
7✔
197
                    : (
22✔
198
                        true === $this->configuration['case_sensitive']
16✔
199
                        ? $first['normalized'] <=> $second['normalized']
4✔
200
                        : strcasecmp($first['normalized'], $second['normalized'])
16✔
201
                    );
22✔
202

203
                if (self::DIRECTION_DESCEND === $this->configuration[self::OPTION_DIRECTION]) {
22✔
204
                    $score *= -1;
6✔
205
                }
206

207
                return $score;
22✔
208
            });
22✔
209

210
            $changed = false;
22✔
211

212
            foreach ($interfaces as $interfaceIndex => $interface) {
22✔
213
                if ($interface['originalIndex'] !== $interfaceIndex) {
22✔
214
                    $changed = true;
22✔
215

216
                    break;
22✔
217
                }
218
            }
219

220
            if (!$changed) {
22✔
221
                continue;
21✔
222
            }
223

224
            $newTokens = array_shift($interfaces)['tokens'];
22✔
225

226
            foreach ($interfaces as $interface) {
22✔
227
                array_push($newTokens, new Token(','), ...$interface['tokens']);
22✔
228
            }
229

230
            $tokens->overrideRange($implementsStart, $implementsEnd, $newTokens);
22✔
231
        }
232
    }
233

234
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
235
    {
236
        return new FixerConfigurationResolver([
36✔
237
            (new FixerOptionBuilder(self::OPTION_ORDER, 'How the interfaces should be ordered.'))
36✔
238
                ->setAllowedValues(self::SUPPORTED_ORDER_OPTIONS)
36✔
239
                ->setDefault(self::ORDER_ALPHA)
36✔
240
                ->getOption(),
36✔
241
            (new FixerOptionBuilder(self::OPTION_DIRECTION, 'Which direction the interfaces should be ordered.'))
36✔
242
                ->setAllowedValues(self::SUPPORTED_DIRECTION_OPTIONS)
36✔
243
                ->setDefault(self::DIRECTION_ASCEND)
36✔
244
                ->getOption(),
36✔
245
            (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.'))
36✔
246
                ->setAllowedTypes(['bool'])
36✔
247
                ->setDefault(false)
36✔
248
                ->getOption(),
36✔
249
        ]);
36✔
250
    }
251

252
    /**
253
     * @return array<int, list<Token>>
254
     */
255
    private function getInterfaces(Tokens $tokens, int $implementsStart, int $implementsEnd): array
256
    {
257
        $interfaces = [];
27✔
258
        $interfaceIndex = 0;
27✔
259

260
        for ($i = $implementsStart; $i <= $implementsEnd; ++$i) {
27✔
261
            if ($tokens[$i]->equals(',')) {
27✔
262
                ++$interfaceIndex;
22✔
263
                $interfaces[$interfaceIndex] = [];
22✔
264

265
                continue;
22✔
266
            }
267

268
            $interfaces[$interfaceIndex][] = $tokens[$i];
27✔
269
        }
270

271
        return $interfaces;
27✔
272
    }
273
}
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