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

PHP-CS-Fixer / PHP-CS-Fixer / 3825623956

pending completion
3825623956

Pull #6734

github

GitHub
Merge f402171a0 into f5726f543
Pull Request #6734: bug: Fix type error when using paths intersection mode

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

22556 of 24273 relevant lines covered (92.93%)

39.1 hits per line

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

98.91
/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\FixerConfiguration\FixerConfigurationResolver;
20
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
21
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
22
use PhpCsFixer\FixerDefinition\CodeSample;
23
use PhpCsFixer\FixerDefinition\FixerDefinition;
24
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
25
use PhpCsFixer\Tokenizer\Token;
26
use PhpCsFixer\Tokenizer\Tokens;
27

28
/**
29
 * @author Dave van der Brugge <dmvdbrugge@gmail.com>
30
 */
31
final class OrderedInterfacesFixer extends AbstractFixer implements ConfigurableFixerInterface
32
{
33
    /** @internal */
34
    public const OPTION_DIRECTION = 'direction';
35

36
    /** @internal */
37
    public const OPTION_ORDER = 'order';
38

39
    /** @internal */
40
    public const DIRECTION_ASCEND = 'ascend';
41

42
    /** @internal */
43
    public const DIRECTION_DESCEND = 'descend';
44

45
    /** @internal */
46
    public const ORDER_ALPHA = 'alpha';
47

48
    /** @internal */
49
    public const ORDER_LENGTH = 'length';
50

51
    /**
52
     * Array of supported directions in configuration.
53
     *
54
     * @var string[]
55
     */
56
    private const SUPPORTED_DIRECTION_OPTIONS = [
57
        self::DIRECTION_ASCEND,
58
        self::DIRECTION_DESCEND,
59
    ];
60

61
    /**
62
     * Array of supported orders in configuration.
63
     *
64
     * @var string[]
65
     */
66
    private const SUPPORTED_ORDER_OPTIONS = [
67
        self::ORDER_ALPHA,
68
        self::ORDER_LENGTH,
69
    ];
70

71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function getDefinition(): FixerDefinitionInterface
75
    {
76
        return new FixerDefinition(
3✔
77
            'Orders the interfaces in an `implements` or `interface extends` clause.',
3✔
78
            [
3✔
79
                new CodeSample(
3✔
80
                    "<?php\n\nfinal class ExampleA implements Gamma, Alpha, Beta {}\n\ninterface ExampleB extends Gamma, Alpha, Beta {}\n"
3✔
81
                ),
3✔
82
                new CodeSample(
3✔
83
                    "<?php\n\nfinal class ExampleA implements Gamma, Alpha, Beta {}\n\ninterface ExampleB extends Gamma, Alpha, Beta {}\n",
3✔
84
                    [self::OPTION_DIRECTION => self::DIRECTION_DESCEND]
3✔
85
                ),
3✔
86
                new CodeSample(
3✔
87
                    "<?php\n\nfinal class ExampleA implements MuchLonger, Short, Longer {}\n\ninterface ExampleB extends MuchLonger, Short, Longer {}\n",
3✔
88
                    [self::OPTION_ORDER => self::ORDER_LENGTH]
3✔
89
                ),
3✔
90
                new CodeSample(
3✔
91
                    "<?php\n\nfinal class ExampleA implements MuchLonger, Short, Longer {}\n\ninterface ExampleB extends MuchLonger, Short, Longer {}\n",
3✔
92
                    [
3✔
93
                        self::OPTION_ORDER => self::ORDER_LENGTH,
3✔
94
                        self::OPTION_DIRECTION => self::DIRECTION_DESCEND,
3✔
95
                    ]
3✔
96
                ),
3✔
97
            ],
3✔
98
        );
3✔
99
    }
100

101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function isCandidate(Tokens $tokens): bool
105
    {
106
        return $tokens->isTokenKindFound(T_IMPLEMENTS)
22✔
107
            || $tokens->isAllTokenKindsFound([T_INTERFACE, T_EXTENDS]);
22✔
108
    }
109

110
    /**
111
     * {@inheritdoc}
112
     */
113
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
114
    {
115
        foreach ($tokens as $index => $token) {
22✔
116
            if (!$token->isGivenKind(T_IMPLEMENTS)) {
22✔
117
                if (!$token->isGivenKind(T_EXTENDS)) {
22✔
118
                    continue;
22✔
119
                }
120

121
                $nameTokenIndex = $tokens->getPrevMeaningfulToken($index);
2✔
122
                $interfaceTokenIndex = $tokens->getPrevMeaningfulToken($nameTokenIndex);
2✔
123
                $interfaceToken = $tokens[$interfaceTokenIndex];
2✔
124

125
                if (!$interfaceToken->isGivenKind(T_INTERFACE)) {
2✔
126
                    continue;
×
127
                }
128
            }
129

130
            $implementsStart = $index + 1;
22✔
131
            $implementsEnd = $tokens->getPrevNonWhitespace($tokens->getNextTokenOfKind($implementsStart, ['{']));
22✔
132

133
            $interfaces = $this->getInterfaces($tokens, $implementsStart, $implementsEnd);
22✔
134

135
            if (1 === \count($interfaces)) {
22✔
136
                continue;
4✔
137
            }
138

139
            foreach ($interfaces as $interfaceIndex => $interface) {
18✔
140
                $interfaceTokens = Tokens::fromArray($interface, false);
18✔
141
                $normalized = '';
18✔
142
                $actualInterfaceIndex = $interfaceTokens->getNextMeaningfulToken(-1);
18✔
143

144
                while ($interfaceTokens->offsetExists($actualInterfaceIndex)) {
18✔
145
                    $token = $interfaceTokens[$actualInterfaceIndex];
18✔
146

147
                    if ($token->isComment() || $token->isWhitespace()) {
18✔
148
                        break;
1✔
149
                    }
150

151
                    $normalized .= str_replace('\\', ' ', $token->getContent());
18✔
152
                    ++$actualInterfaceIndex;
18✔
153
                }
154

155
                $interfaces[$interfaceIndex] = [
18✔
156
                    'tokens' => $interface,
18✔
157
                    'normalized' => $normalized,
18✔
158
                    'originalIndex' => $interfaceIndex,
18✔
159
                ];
18✔
160
            }
161

162
            usort($interfaces, function (array $first, array $second): int {
18✔
163
                $score = self::ORDER_LENGTH === $this->configuration[self::OPTION_ORDER]
18✔
164
                    ? \strlen($first['normalized']) - \strlen($second['normalized'])
7✔
165
                    : strcasecmp($first['normalized'], $second['normalized']);
12✔
166

167
                if (self::DIRECTION_DESCEND === $this->configuration[self::OPTION_DIRECTION]) {
18✔
168
                    $score *= -1;
6✔
169
                }
170

171
                return $score;
18✔
172
            });
18✔
173

174
            $changed = false;
18✔
175

176
            foreach ($interfaces as $interfaceIndex => $interface) {
18✔
177
                if ($interface['originalIndex'] !== $interfaceIndex) {
18✔
178
                    $changed = true;
18✔
179

180
                    break;
18✔
181
                }
182
            }
183

184
            if (!$changed) {
18✔
185
                continue;
17✔
186
            }
187

188
            $newTokens = array_shift($interfaces)['tokens'];
18✔
189

190
            foreach ($interfaces as $interface) {
18✔
191
                array_push($newTokens, new Token(','), ...$interface['tokens']);
18✔
192
            }
193

194
            $tokens->overrideRange($implementsStart, $implementsEnd, $newTokens);
18✔
195
        }
196
    }
197

198
    /**
199
     * {@inheritdoc}
200
     */
201
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
202
    {
203
        return new FixerConfigurationResolver([
27✔
204
            (new FixerOptionBuilder(self::OPTION_ORDER, 'How the interfaces should be ordered'))
27✔
205
                ->setAllowedValues(self::SUPPORTED_ORDER_OPTIONS)
27✔
206
                ->setDefault(self::ORDER_ALPHA)
27✔
207
                ->getOption(),
27✔
208
            (new FixerOptionBuilder(self::OPTION_DIRECTION, 'Which direction the interfaces should be ordered'))
27✔
209
                ->setAllowedValues(self::SUPPORTED_DIRECTION_OPTIONS)
27✔
210
                ->setDefault(self::DIRECTION_ASCEND)
27✔
211
                ->getOption(),
27✔
212
        ]);
27✔
213
    }
214

215
    /**
216
     * @return array<int, list<Token>>
217
     */
218
    private function getInterfaces(Tokens $tokens, int $implementsStart, int $implementsEnd): array
219
    {
220
        $interfaces = [];
22✔
221
        $interfaceIndex = 0;
22✔
222

223
        for ($i = $implementsStart; $i <= $implementsEnd; ++$i) {
22✔
224
            if ($tokens[$i]->equals(',')) {
22✔
225
                ++$interfaceIndex;
18✔
226
                $interfaces[$interfaceIndex] = [];
18✔
227

228
                continue;
18✔
229
            }
230

231
            $interfaces[$interfaceIndex][] = $tokens[$i];
22✔
232
        }
233

234
        return $interfaces;
22✔
235
    }
236
}
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